[
  {
    "path": ".editorconfig",
    "content": "# http://editorconfig.org\n\nroot = true\n\n[*]\ncharset = utf-8\nindent_style = space\nindent_size = 2\nend_of_line = lf\ninsert_final_newline = true\ntrim_trailing_whitespace = true\n\n[*.md]\ninsert_final_newline = false\ntrim_trailing_whitespace = false\n"
  },
  {
    "path": ".github/ISSUE_TEMPLATE.md",
    "content": "**Note: for support questions, please use one of these channels:**\n\nhttps://forum.ionicframework.com/\nhttp://ionicworldwide.herokuapp.com/\n\n\n#### Short description of the problem:\n\n\n#### What behavior are you expecting?\n\n\n**Steps to reproduce:**\n1.\n2.\n3.\n\n```\ninsert any relevant code between the above and below backticks\n```\n\n**Which @ionic/app-scripts version are you using?**\n\n\n**Other information:** (e.g. stacktraces, related issues, suggestions how to fix, stackoverflow links, forum links, etc)\n\n"
  },
  {
    "path": ".github/PULL_REQUEST_TEMPLATE.md",
    "content": "#### Short description of what this resolves:\n\n\n#### Changes proposed in this pull request:\n\n-\n-\n-\n\n**Fixes**: #\n"
  },
  {
    "path": ".gitignore",
    "content": ".DS_Store\n\n# Logs\nlogs\n*.log\nnpm-debug.log*\n\n# Runtime data\npids\n*.pid\n*.seed\n\n# Directory for instrumented libs generated by jscoverage/JSCover\nlib-cov\n\n# Coverage directory used by tools like istanbul\ncoverage\n\n\n# nyc test coverage\n.nyc_output\n\n# Grunt intermediate storage (http://gruntjs.com/creating-plugins#storing-task-files)\n.grunt\n\n# node-waf configuration\n.lock-wscript\n\n# Compiled binary addons (http://nodejs.org/api/addons.html)\nbuild/Release\n\n# Dependency directories\nnode_modules\njspm_packages\n\n# Optional npm cache directory\n.npm\n\n# Optional REPL history\n.node_repl_history\n\n# various\ndist\n\n.vscode\n*.sw[mnpcod]\n\nbin/ion-dev.css\n\n# WebStorm\n.idea\n"
  },
  {
    "path": ".npmrc",
    "content": "package-lock=false\n"
  },
  {
    "path": "CHANGELOG.md",
    "content": "<a name=\"3.2.4\"></a>\n## [3.2.4](https://github.com/ionic-team/ionic-app-scripts/compare/v3.2.3...v3.2.4) (2019-05-24)\n\n\n### Bug Fixes\n\n*  **livereload:** always serve latest file changes ([#1521](https://github.com/ionic-team/ionic-app-scripts/issues/1521)) ([266a871](https://github.com/ionic-team/ionic-app-scripts/commit/266a871))\n\n\n\n<a name=\"3.2.3\"></a>\n## [3.2.3](https://github.com/ionic-team/ionic-app-scripts/compare/v3.2.2...v3.2.3) (2019-03-01)\n\n\n### Bug Fixes\n\n* **livereload:** fix issue with files not reloading([8870a17](https://github.com/ionic-team/ionic-app-scripts/commit/8870a17))\n\n\n\n<a name=\"3.2.2\"></a>\n## [3.2.2](https://github.com/ionic-team/ionic-app-scripts/compare/v3.2.1...v3.2.2) (2019-01-22)\n\n* Added support for Node 10\n\n<a name=\"3.2.1\"></a>\n\n## [3.2.1](https://github.com/ionic-team/ionic-app-scripts/compare/v3.2.0...v3.2.1) (2018-11-26)\n\n* Security release for dependencies for `node-sass`\n\n<a name=\"3.2.0\"></a>\n# [3.2.0](https://github.com/ionic-team/ionic-app-scripts/compare/v3.1.11...v3.2.0) (2018-08-24)\n\n\n### Bug Fixes\n\n* **sass:** remove PostCSS warning ([#1364](https://github.com/ionic-team/ionic-app-scripts/issues/1364)) ([1e2035e](https://github.com/ionic-team/ionic-app-scripts/commit/1e2035e)), closes [#1359](https://github.com/ionic-team/ionic-app-scripts/issues/1359) [#13763](https://github.com/ionic-team/ionic-app-scripts/issues/13763)\n* **serve:** use wss protocol for secure websocket when page is using https ([#1358](https://github.com/ionic-team/ionic-app-scripts/issues/1358)) ([29c3e23](https://github.com/ionic-team/ionic-app-scripts/commit/29c3e23))\n\n\n### Features\n\n* **environments:** configuration via process.env.VAR replacement ([#1471](https://github.com/ionic-team/ionic-app-scripts/issues/1471)) ([53fc341](https://github.com/ionic-team/ionic-app-scripts/commit/53fc341))\n\n\n\n<a name=\"3.1.11\"></a>\n## [3.1.11](https://github.com/ionic-team/ionic-app-scripts/compare/v3.1.10...v3.1.11) (2018-07-12)\n\n\n### Bug Fixes\n\n* **serve:** fix EADDRINUSE issue with dev logger server ([271a6e1](https://github.com/ionic-team/ionic-app-scripts/commit/271a6e1))\n\n\n\n<a name=\"3.1.10\"></a>\n## [3.1.10](https://github.com/ionic-team/ionic-app-scripts/compare/v3.1.9...v3.1.10) (2018-06-11)\n\n\n\n<a name=\"3.1.9\"></a>\n## [3.1.9](https://github.com/ionic-team/ionic-app-scripts/compare/v3.1.8...v3.1.9) (2018-04-18)\n\n\n### Bug Fixes\n\n* **2889:** fix build error with --prod ([94e5f7c](https://github.com/ionic-team/ionic-app-scripts/commit/94e5f7c))\n* **live-server:** update android platform path ([#1407](https://github.com/ionic-team/ionic-app-scripts/issues/1407)) ([1591c81](https://github.com/ionic-team/ionic-app-scripts/commit/1591c81))\n* **serve:** start listening when watch is ready ([06bbd06](https://github.com/ionic-team/ionic-app-scripts/commit/06bbd06))\n\n\n\n<a name=\"3.1.8\"></a>\n## [3.1.8](https://github.com/ionic-team/ionic-app-scripts/compare/v3.1.7...v3.1.8) (2018-01-18)\n\nThis release includes a bump in the version of node-sass. This adds support for node 9.\n\n\n<a name=\"3.1.7\"></a>\n## [3.1.7](https://github.com/ionic-team/ionic-app-scripts/compare/v3.1.6...v3.1.7) (2017-12-27)\n\n\n### Bug Fixes\n\n* pin uglify-es ([dacf080](https://github.com/ionic-team/ionic-app-scripts/commit/dacf080)), closes [#1353](https://github.com/ionic-team/ionic-app-scripts/issues/1353)\n\n\n\n<a name=\"3.1.6\"></a>\n## [3.1.6](https://github.com/ionic-team/ionic-app-scripts/compare/v3.1.5...v3.1.6) (2017-12-18)\n\n\n### Bug Fixes\n\n* pin ws version ([db0cc4d](https://github.com/ionic-team/ionic-app-scripts/commit/db0cc4d))\n\n\n\n<a name=\"3.1.5\"></a>\n## [3.1.5](https://github.com/ionic-team/ionic-app-scripts/compare/v3.1.4...v3.1.5) (2017-12-07)\n\n\n### Bug Fixes\n\n* **dependencies:** update angular build optimizer for a source map fix ([a5df139](https://github.com/ionic-team/ionic-app-scripts/commit/a5df139))\n\n\n\n<a name=\"3.1.4\"></a>\n## [3.1.4](https://github.com/ionic-team/ionic-app-scripts/compare/v3.1.3...v3.1.4) (2017-11-30)\n\n\n### Bug Fixes\n\n* **aot:** remove template validation until we can properly handle the error message format ([d7c7136](https://github.com/ionic-team/ionic-app-scripts/commit/d7c7136))\n\n\n\n<a name=\"3.1.3\"></a>\n## [3.1.3](https://github.com/ionic-team/ionic-app-scripts/compare/v3.1.2...v3.1.3) (2017-11-29)\n\n\n### Bug Fixes\n\n* **aot:** fix error reporting with ng 5.0.1 or greater ([dece391](https://github.com/ionic-team/ionic-app-scripts/commit/dece391))\n\n\n\n<a name=\"3.1.2\"></a>\n## [3.1.2](https://github.com/ionic-team/ionic-app-scripts/compare/v3.1.1...v3.1.2) (2017-11-13)\n\n\n### Bug Fixes\n\n* **webpack:** revert to 3.6.0 for faster builds ([2553ca6](https://github.com/ionic-team/ionic-app-scripts/commit/2553ca6))\n\n\n\n<a name=\"3.1.1\"></a>\n## [3.1.1](https://github.com/ionic-team/ionic-app-scripts/compare/v3.1.0...v3.1.1) (2017-11-13)\n\n\n### Bug Fixes\n\n* **AoT:** properly check for ngmodule declaration errors from the AoT build ([a47f120](https://github.com/ionic-team/ionic-app-scripts/commit/a47f120))\n* **template:** fix bug with using dollar sign within templates ([de09048](https://github.com/ionic-team/ionic-app-scripts/commit/de09048))\n\n\n\n<a name=\"3.1.0\"></a>\n# [3.1.0](https://github.com/ionic-team/ionic-app-scripts/compare/v3.0.1...v3.1.0) (2017-11-08)\n\n## Features\n\nSupports Angular 5\n\n### Bug Fixes\n\n* **aot:** pass genDir to ng4 ([7506764](https://github.com/ionic-team/ionic-app-scripts/commit/7506764))\n* **config:** only read ionic-angular package json for version info in apps, not in the Ionic repo itself ([700ca04](https://github.com/ionic-team/ionic-app-scripts/commit/700ca04))\n* **deep-linking:** use .ts file extension for lazy loading in dev mode, and .js in AoT mode since the AoT compiler no longer emits an ngfactory.ts file ([dd99f14](https://github.com/ionic-team/ionic-app-scripts/commit/dd99f14))\n* **live-server:** content.toString() crash ([#1288](https://github.com/ionic-team/ionic-app-scripts/issues/1288)) ([07e7e05](https://github.com/ionic-team/ionic-app-scripts/commit/07e7e05))\n* **templates:** escape strings in template ([484d90d](https://github.com/ionic-team/ionic-app-scripts/commit/484d90d))\n\n\n### Performance Improvements\n\n* **uglifyjs:** remove unused `readFileAsync` during uglify ([#1305](https://github.com/ionic-team/ionic-app-scripts/issues/1305)) ([e9217c2](https://github.com/ionic-team/ionic-app-scripts/commit/e9217c2))\n\n\n\n<a name=\"3.0.1\"></a>\n## [3.0.1](https://github.com/ionic-team/ionic-app-scripts/compare/v3.0.0...v3.0.1) (2017-10-20)\n\n\n### Bug Fixes\n\n* **cleancss:** update to latest version of clean-css to mitigate issue with purging some css that should not be purged ([564bd61](https://github.com/ionic-team/ionic-app-scripts/commit/564bd61))\n* **deep-linking:** ensure hasExistingDeepLinkConfig returns true where there is a config referenced by a variable ([2e40340](https://github.com/ionic-team/ionic-app-scripts/commit/2e40340))\n* **deep-linking:** ensure the deepLinkDir ends in path.sep ([496af40](https://github.com/ionic-team/ionic-app-scripts/commit/496af40))\n* set context right immediately ([802b329](https://github.com/ionic-team/ionic-app-scripts/commit/802b329))\n* **dev-server:** fix for --nolivereload flag to stop reloading ([#1200](https://github.com/ionic-team/ionic-app-scripts/issues/1200)) ([d62f5da](https://github.com/ionic-team/ionic-app-scripts/commit/d62f5da))\n* **html:** limit regex to only applicable script tags for replacing content ([93db0ef](https://github.com/ionic-team/ionic-app-scripts/commit/93db0ef))\n* **proxy:** add a cookieRewrite option which is passed to proxy-middleware. ([#1226](https://github.com/ionic-team/ionic-app-scripts/issues/1226))  ([771ee63](https://github.com/ionic-team/ionic-app-scripts/commit/771ee63))\n* **source-maps:** fix race condition between copying and purging source maps ([f5529b5](https://github.com/ionic-team/ionic-app-scripts/commit/f5529b5))\n* **webpack:** always use modules output from webpack to form default basis of where to look for sass files ([c199ea4](https://github.com/ionic-team/ionic-app-scripts/commit/c199ea4))\n\n\n\n<a name=\"3.0.0\"></a>\n# [3.0.0](https://github.com/ionic-team/ionic-app-scripts/compare/v2.1.4...v3.0.0) (2017-09-28)\n\n### Breaking Changes\nThe `webpack` config format changed from being a config that is exported to being a dictionary of configs. Basically, the default config now exports a `dev` and `prod` property with a config assigned to each. See an example of the change [here](https://github.com/ionic-team/ionic-app-scripts/blob/master/config/webpack.config.js#L143-L146). This change is setting the stage for adding multiple \"environment\" support for the next app-scripts release.\n\n### New Features\nThis release adds support for `ngo`, the Angular team's build optimizer tool. `ngo` is enabled by default on `--prod` builds. In the event that `ngo` is not working for your app or something goes wrong, it can be disabled by running the following build command.\n\n```\nionic cordova build ios --aot --minifyjs --minifycss --optimizejs\n```\n\nUsing the `--aot` flag enables the `AoT Compiler`. `--minifyjs` and `--minifycss` minify the outputted code.\n\n### Notes\nVersion `3.0.0` deprecated support for Rollup, Closure Compiler, and Babili. The support for these was poor and they were not used by many developers. `uglifyjs` was replaced with the newer `uglifyes`, which supports ES2015.\n\n### Bug Fixes\n\n* **aot:** normalize paths to fix path issues on windows ([b766037](https://github.com/ionic-team/ionic-app-scripts/commit/b766037))\n* **build:** scan deeplink dir too if different from srcDir ([8929265](https://github.com/ionic-team/ionic-app-scripts/commit/8929265))\n* **deep-linking:** convert deep linking to use TS Transform. DeepLinking now works on TypeScript src instead of on transpiled JS code ([63c4c7f](https://github.com/ionic-team/ionic-app-scripts/commit/63c4c7f))\n* **deep-linking:** remove IonicPage import statement in transform/non-transform approachs to work better with strict TS settings ([84d9ec7](https://github.com/ionic-team/ionic-app-scripts/commit/84d9ec7))\n* **devapp:** do not enable shake ([#1215](https://github.com/ionic-team/ionic-app-scripts/issues/1215)) ([118189c](https://github.com/ionic-team/ionic-app-scripts/commit/118189c))\n* **generators:** correct pipes default folder name ([f0ea0da](https://github.com/ionic-team/ionic-app-scripts/commit/f0ea0da))\n* **ngc:** don't replace deeplink config if an existing one exists ([eeed98b](https://github.com/ionic-team/ionic-app-scripts/commit/eeed98b))\n* **optimization:** removing optimizations in preparation for ngo, updating to latest deps ([90eb8b3](https://github.com/ionic-team/ionic-app-scripts/commit/90eb8b3))\n* **postprocess:** fix and add tests for the logic surrounding purging fonts ([0dd1b22](https://github.com/ionic-team/ionic-app-scripts/commit/0dd1b22))\n* **sass:** include the platforms dir by default ([0da47cb](https://github.com/ionic-team/ionic-app-scripts/commit/0da47cb))\n* **transpile:** check for existing deep link config before using generated one ([c51ac93](https://github.com/ionic-team/ionic-app-scripts/commit/c51ac93))\n* **webpack:** when analyzing stats, factor in new shape of ModuleConcatenation info ([00cf038](https://github.com/ionic-team/ionic-app-scripts/commit/00cf038))\n\n\n\n<a name=\"2.1.4\"></a>\n## [2.1.4](https://github.com/ionic-team/ionic-app-scripts/compare/v2.1.3...v2.1.4) (2017-08-16)\n\n\n### Bug Fixes\n\n* make --lab respect --nobrowser ([8db3be5](https://github.com/ionic-team/ionic-app-scripts/commit/8db3be5))\n* **serve:** allow multiple arguments in console.log ([5c00970](https://github.com/ionic-team/ionic-app-scripts/commit/5c00970))\n* **serve:** fix --consolelogs/--serverlogs usage with Cordova console plugin ([8e64407](https://github.com/ionic-team/ionic-app-scripts/commit/8e64407))\n* **serve:** fix 'launchBrowser' of undefined ([8f71e35](https://github.com/ionic-team/ionic-app-scripts/commit/8f71e35))\n\n\n### Features\n\n* **sourcemaps:** copy for prod and dev ([a1ccc17](https://github.com/ionic-team/ionic-app-scripts/commit/a1ccc17))\n* **sourcemaps:** preserve prod sourcemaps out of code dir ([ee3e41b](https://github.com/ionic-team/ionic-app-scripts/commit/ee3e41b))\n\n\n\n<a name=\"2.1.3\"></a>\n## [2.1.3](https://github.com/ionic-team/ionic-app-scripts/compare/v2.1.2...v2.1.3) (2017-07-27)\n\n\n### Bug Fixes\n\n* **lab:** remove es6 features from lab ([41a1335](https://github.com/ionic-team/ionic-app-scripts/commit/41a1335))\n\n\n\n<a name=\"2.1.2\"></a>\n## [2.1.2](https://github.com/ionic-team/ionic-app-scripts/compare/v2.1.1...v2.1.2) (2017-07-27)\n\n\n### Bug Fixes\n\n* **generators:** handle old cli ([6fd622c](https://github.com/ionic-team/ionic-app-scripts/commit/6fd622c))\n\n\n\n<a name=\"2.1.1\"></a>\n## [2.1.1](https://github.com/ionic-team/ionic-app-scripts/compare/v2.1.0...v2.1.1) (2017-07-27)\n\n\n### Bug Fixes\n\n* **generator:** write file sync ([b0bcb05](https://github.com/ionic-team/ionic-app-scripts/commit/b0bcb05))\n* **generators:** add exception for providers ([db9c793](https://github.com/ionic-team/ionic-app-scripts/commit/db9c793))\n\n\n### Features\n\n* **webpack:** update to latest webpack ([67907b6](https://github.com/ionic-team/ionic-app-scripts/commit/67907b6))\n\n\n\n<a name=\"2.1.0\"></a>\n# [2.1.0](https://github.com/ionic-team/ionic-app-scripts/compare/v2.0.2...v2.1.0) (2017-07-25)\n\n\n### Bug Fixes\n\n* **generators:** handle no ngModule in tabs ([653d9f2](https://github.com/ionic-team/ionic-app-scripts/commit/653d9f2))\n\n\n### Features\n\n* **generators:** refactor generators ([beaf0d3](https://github.com/ionic-team/ionic-app-scripts/commit/beaf0d3))\n\n\n\n<a name=\"2.0.2\"></a>\n## [2.0.2](https://github.com/ionic-team/ionic-app-scripts/compare/v2.0.1...v2.0.2) (2017-07-13)\n\n## Upgrading\nMake sure you follow the instructions below for upgrading from `1.x` to `2.x`. In the `2.0.2` release, we had to make a small change to the `optimization` config. If you override this config, please review the [change](https://github.com/ionic-team/ionic-app-scripts/commit/785e044) and update your config accordingly.\n\n### Bug Fixes\n\n* **sass:** fix potential null pointer, though it really should never happen ([427e556](https://github.com/ionic-team/ionic-app-scripts/commit/427e556))\n* **webpack:** don't output deptree.js, this requires a minor tweak to the optimization config if you have it customized ([785e044](https://github.com/ionic-team/ionic-app-scripts/commit/785e044))\n* **webpack:** upgrade to webpack 3.2.0 to fix some bugs within Webpack surrounding the ModuleConcatenationPlugin ([f85ade0](https://github.com/ionic-team/ionic-app-scripts/commit/f85ade0))\n\n\n\n<a name=\"2.0.1\"></a>\n## [2.0.1](https://github.com/ionic-team/ionic-app-scripts/compare/v2.0.0...v2.0.1) (2017-07-11)\n\n## Upgrading from 1.x\n\nIf you're upgrading directly from `1.3.12` or earlier, make sure you review the changelog for `2.0.0` and follow the [instructions here](https://github.com/ionic-team/ionic-app-scripts/releases/tag/v2.0.0). There were some very minor updates you'll need to make to your app.\n\nIf you're customizing the build process and have a dependency that utilized `webpack@2.x`, it may be best to add an explicit `devDependency` on `webpack@3.1.0` to the project's `package.json` file. There have been a couple reports of non-standard 3rd party dependencies causing trouble with the `webpack` version.\n\n### Bug Fixes\n\n* **generators:** no module by default ([#1096](https://github.com/ionic-team/ionic-app-scripts/issues/1096)) ([dfcaefa](https://github.com/ionic-team/ionic-app-scripts/commit/dfcaefa))\n* **http-server:** revert change for path-based routing since it broke proxies ([065912e](https://github.com/ionic-team/ionic-app-scripts/commit/065912e))\n* **sass:** use webpack/rollup modules for non-optimized build, use optimization data for prod/optimized buids ([0554201](https://github.com/ionic-team/ionic-app-scripts/commit/0554201))\n* **serve:** fix cached file issue by only using the webpack module concat plugin for prod builds, make sure you update custom configs ([feea7fe](https://github.com/ionic-team/ionic-app-scripts/commit/feea7fe))\n* **webpack:** webpack in-memory output file system was breaking some plugins ([574da39](https://github.com/ionic-team/ionic-app-scripts/commit/574da39))\n\n\n\n<a name=\"2.0.0\"></a>\n# [2.0.0](https://github.com/ionic-team/ionic-app-scripts/compare/v1.3.12...v2.0.0) (2017-07-07)\n\n### Breaking Changes\n\nIn order to speed up the bundling process, we have separated `node_modules` code into a new, generated file called `vendor.js`. This means that on every change, `ionic-angular`, `@angular`, etc won't need to be processed by `webpack` :tada:\n\nThis means that `src/index.html` must be modified to include a new vendor script tag `<script src=\"build/vendor.js\"></script>`. This new script tag must be placed above the `main.js` script tag. For example,\n\n```\n...\n<body>\n\n  <!-- Ionic's root component and where the app will load -->\n  <ion-app></ion-app>\n\n  <script src=\"cordova.js\"></script>\n\n  <!-- The polyfills js is generated during the build process -->\n  <script src=\"build/polyfills.js\"></script>\n\n  <!-- all code from node_modules directory is here -->\n  <script src=\"build/vendor.js\"></script>\n\n  <!-- The bundle js is generated during the build process -->\n  <script src=\"build/main.js\"></script>\n\n</body>\n...\n```\n\nAnother side effect of this change is if you are overriding the `webpack` configuration, you will want to update your custom configuration based on the [new default configuration](https://github.com/ionic-team/ionic-app-scripts/blob/master/config/webpack.config.js). The main changes to the config are adding the `ModuleConcatenationPlugin` for scope hoisting for significantly faster apps, and adding the common chunks plugin for the `vendor.js` bundle.\n\nSee commits [e14f819](https://github.com/ionic-team/ionic-app-scripts/commit/e14f819) and [141cb23](https://github.com/ionic-team/ionic-app-scripts/commit/141cb23) for the specifics of the `webpack.config.js` change.\n\n### Bug Fixes\n\n* **config:** updated polyname env variable to match convention and fix typo with it ([d64fcb1](https://github.com/ionic-team/ionic-app-scripts/commit/d64fcb1))\n* **lint:** improve linting performance ([106d82c](https://github.com/ionic-team/ionic-app-scripts/commit/106d82c))\n* **sass:** dont try to process invalid directories ([8af9430](https://github.com/ionic-team/ionic-app-scripts/commit/8af9430))\n* **sass:** fix a bug when calling sass task in stand alone fashion ([54bf3f6](https://github.com/ionic-team/ionic-app-scripts/commit/54bf3f6))\n\n\n### Features\n\n* **dev-server:** add support for path-based routing ([2441591](https://github.com/ionic-team/ionic-app-scripts/commit/2441591))\n* **webpack:** add scope hoisting to webpack, update sass to read scss files from disk ([e14f819](https://github.com/ionic-team/ionic-app-scripts/commit/e14f819))\n* **webpack:** use a vendor bundle to minimize code that needs re-bundling and source map generation ([141cb23](https://github.com/ionic-team/ionic-app-scripts/commit/141cb23))\n* **webpack:** webpack 3.1.0 holy speed upgrade! ([a3bde4a](https://github.com/ionic-team/ionic-app-scripts/commit/a3bde4a))\n\n\n\n<a name=\"1.3.12\"></a>\n## [1.3.12](https://github.com/ionic-team/ionic-app-scripts/compare/v1.3.11...v1.3.12) (2017-06-29)\n\n## Bug Fixes\n\n* **dependencies:** Added `reflect-metadata` to the list of dependencies ([e6f8481](https://github.com/ionic-team/ionic-app-scripts/commit/e6f8481)\n\n\n<a name=\"1.3.11\"></a>\n## [1.3.11](https://github.com/ionic-team/ionic-app-scripts/compare/v1.3.10...v1.3.11) (2017-06-28)\n\n## Bug Fixes\n\n* **dependencies:** Removed `peerDependencies`. ([90cd59d](https://github.com/ionic-team/ionic-app-scripts/commit/90cd59d))\n\n\n<a name=\"1.3.10\"></a>\n## [1.3.10](https://github.com/ionic-team/ionic-app-scripts/compare/v1.3.9...v1.3.10) (2017-06-28)\n\n## Notes\n\nIonic updated to npm 5 across the board, so please update to npm 5 to utilize our lock file when contributing.\n\n### Bug Fixes\n\n* **bonjour:** remove bonjour as its causing trouble for users on Windows without git ([e4b5c59](https://github.com/ionic-team/ionic-app-scripts/commit/e4b5c59))\n\n\n\n<a name=\"1.3.9\"></a>\n## [1.3.9](https://github.com/ionic-team/ionic-app-scripts/compare/v1.3.8...v1.3.9) (2017-06-28)\n\n\n### Features\n\n* **lab:** first iteration of the new Ionic Lab design\n* **scripts:** push npm build to arbitrary tag ([#1060](https://github.com/ionic-team/ionic-app-scripts/issues/1060)) ([4e93f60](https://github.com/ionic-team/ionic-app-scripts/commit/4e93f60))\n\n\n\n<a name=\"1.3.8\"></a>\n## [1.3.8](https://github.com/ionic-team/ionic-app-scripts/compare/v1.3.7...v1.3.8) (2017-06-21)\n\n### Bug Fixes\n\n* **sass:** fixes issue with Node 8 and node-sass\n* **bonjour:** updates dependency + better error handling ([#1040](https://github.com/ionic-team/ionic-app-scripts/issues/1040)) ([e2f73c7](https://github.com/ionic-team/ionic-app-scripts/commit/e2f73c7))\n* **core:** use lower case attrs and not dash case ([0154791](https://github.com/ionic-team/ionic-app-scripts/commit/0154791))\n* **diagnostics:** change direction to always be ltr ([#1004](https://github.com/ionic-team/ionic-app-scripts/issues/1004)) ([6d5ef3c](https://github.com/ionic-team/ionic-app-scripts/commit/6d5ef3c))\n* **lab:** allow params to be passed to iframes ([dabfdd1](https://github.com/ionic-team/ionic-app-scripts/commit/dabfdd1))\n* **sass:** fix .sass files not being watched ([#957](https://github.com/ionic-team/ionic-app-scripts/issues/957)) ([0803eca](https://github.com/ionic-team/ionic-app-scripts/commit/0803eca))\n* **serve:** if a build error occurs, return config if non-fatal ([e5a4134](https://github.com/ionic-team/ionic-app-scripts/commit/e5a4134))\n\n\n\n<a name=\"1.3.7\"></a>\n## [1.3.7](https://github.com/ionic-team/ionic-app-scripts/compare/v1.3.6...v1.3.7) (2017-05-04)\n\n\n### Bug Fixes\n\n* **config:** create new file cache if not defined, even on existing context object ([4359b3d](https://github.com/ionic-team/ionic-app-scripts/commit/4359b3d))\n* **generators:** import paths correct on windows ([d778857](https://github.com/ionic-team/ionic-app-scripts/commit/d778857))\n* **optimizations:** don't ever remove menu-types since it's not a side-effect in menu, it is used just for types ([d7a4d1e](https://github.com/ionic-team/ionic-app-scripts/commit/d7a4d1e))\n* **optimizations:** fix multiple bugs (components not being purged, overlays not working from providers, etc) for manual tree shaking ([4b538c7](https://github.com/ionic-team/ionic-app-scripts/commit/4b538c7))\n* **webpack:** fix issue where bundles output to build dir sub directo… ([#938](https://github.com/ionic-team/ionic-app-scripts/issues/938)) ([aaa9d3c](https://github.com/ionic-team/ionic-app-scripts/commit/aaa9d3c))\n\n\n### Features\n\n* **bonjour:** adds service auto-discovery ([c17e6df](https://github.com/ionic-team/ionic-app-scripts/commit/c17e6df))\n\n\n\n<a name=\"1.3.6\"></a>\n## [1.3.6](https://github.com/ionic-team/ionic-app-scripts/compare/v1.3.5...v1.3.6) (2017-04-27)\n\n\n### Bug Fixes\n\n* **webpack:** fix issue PR introduced with lazy loaded modules and webpack throwing an invalid error ([fb8b69a](https://github.com/ionic-team/ionic-app-scripts/commit/fb8b69a))\n\n\n### Features\n\n* **optimization:** enable manual tree shaking by default ([1c57ee6](https://github.com/ionic-team/ionic-app-scripts/commit/1c57ee6))\n\n\n\n<a name=\"1.3.5\"></a>\n## [1.3.5](https://github.com/ionic-team/ionic-app-scripts/compare/v1.3.4...v1.3.5) (2017-04-26)\n\n\n### Bug Fixes\n\n* **build:** fix `extends` in `ts-config.json` ([#910](https://github.com/ionic-team/ionic-app-scripts/issues/910)) ([0f01603](https://github.com/ionic-team/ionic-app-scripts/commit/0f01603))\n* **deep-linking:** fix issue where deep link config ends up being null when full build is triggered via a change to a template file with the identical content ([68fc463](https://github.com/ionic-team/ionic-app-scripts/commit/68fc463))\n* **serve:** Fix for browser not opening on linux, fixes [#425](https://github.com/ionic-team/ionic-app-scripts/issues/425) ([#909](https://github.com/ionic-team/ionic-app-scripts/issues/909)) ([77edbc6](https://github.com/ionic-team/ionic-app-scripts/commit/77edbc6))\n\n\n### Features\n\n* **sass:** add option to pass addition postcss plugins ([#369](https://github.com/ionic-team/ionic-app-scripts/issues/369)) ([be30a40](https://github.com/ionic-team/ionic-app-scripts/commit/be30a40))\n\n\n\n<a name=\"1.3.4\"></a>\n## [1.3.4](https://github.com/ionic-team/ionic-app-scripts/compare/v1.3.3...v1.3.4) (2017-04-18)\n\n\n### Bug Fixes\n\n* **webpack:** make ionic-angular/util dir dynamic and use the environment variable of ionic angular ([d3346b3](https://github.com/ionic-team/ionic-app-scripts/commit/d3346b3))\n\n\n\n<a name=\"1.3.3\"></a>\n## [1.3.3](https://github.com/ionic-team/ionic-app-scripts/compare/v1.3.2...v1.3.3) (2017-04-14)\n\n\n### Bug Fixes\n\n* **optimizations:** temporarily do not purge ctor params from any of angular ([212146c](https://github.com/ionic-team/ionic-app-scripts/commit/212146c))\n\n\n\n<a name=\"1.3.2\"></a>\n## [1.3.2](https://github.com/ionic-team/ionic-app-scripts/compare/v1.3.1...v1.3.2) (2017-04-12)\n\n\n### Bug Fixes\n\n* **deep-linking:** fix bug with null deep link config when modifying the main NgModule file (app.module.ts) ([759bb4f](https://github.com/ionic-team/ionic-app-scripts/commit/759bb4f))\n* **optimization:** don't purge ctorParams for angular core or angular platform browser ([9562181](https://github.com/ionic-team/ionic-app-scripts/commit/9562181))\n* **uglifyjs:** only minify files processed by webpack or rollup ([30ecdd8](https://github.com/ionic-team/ionic-app-scripts/commit/30ecdd8))\n\n\n\n<a name=\"1.3.1\"></a>\n## [1.3.1](https://github.com/ionic-team/ionic-app-scripts/compare/v1.3.0...v1.3.1) (2017-04-06)\n\n\n### Bug Fixes\n\n* **config:** revert change and once again transpile bundle by default. ([b558584](https://github.com/ionic-team/ionic-app-scripts/commit/b558584))\n* **decorators:** don't remove third party transpiled (not static) decorators ([3a3259a](https://github.com/ionic-team/ionic-app-scripts/commit/3a3259a))\n* **deep-linking:** don't force the main bundle to be re-built unless the deep link config changed ([02b8e97](https://github.com/ionic-team/ionic-app-scripts/commit/02b8e97))\n* **errors:** better error msg reporting from worker threads ([d9d000a](https://github.com/ionic-team/ionic-app-scripts/commit/d9d000a))\n* **uglifyjs:** better error msg reporting ([49c0afb](https://github.com/ionic-team/ionic-app-scripts/commit/49c0afb))\n\n\n\n<a name=\"1.3.0\"></a>\n# [1.3.0](https://github.com/ionic-team/ionic-app-scripts/compare/v1.2.5...v1.3.0) (2017-04-05)\n\n\n### Features\n\n* **optimization:** purge decorators enabled by default ([b626e00](https://github.com/ionic-team/ionic-app-scripts/commit/b626e00))\n* **optimizations:** purge transpiled decorators ([ba5e0cd](https://github.com/ionic-team/ionic-app-scripts/commit/ba5e0cd))\n\n\n\n<a name=\"1.2.5\"></a>\n## [1.2.5](https://github.com/ionic-team/ionic-app-scripts/compare/v1.2.4...v1.2.5) (2017-03-31)\n\n\n### Bug Fixes\n\n* **webpack:** fixes bugs where some third party libs didn't load correctly ([e7559e5](https://github.com/ionic-team/ionic-app-scripts/commit/e7559e5))\n\n\n\n<a name=\"1.2.4\"></a>\n## [1.2.4](https://github.com/ionic-team/ionic-app-scripts/compare/v1.2.3...v1.2.4) (2017-03-30)\n\n### Refactor\n* **deep-linking:** set default segment value to filename without extension([5a97ba5](https://github.com/ionic-team/ionic-app-scripts/commit/5a97ba5))\n\n<a name=\"1.2.3\"></a>\n## [1.2.3](https://github.com/ionic-team/ionic-app-scripts/compare/v1.2.2...v1.2.3) (2017-03-29)\n\n\n### Bug Fixes\n\n* **deep-linking:** Deep linking fixes for Windows and non-unix paths\n\n* **script:** linux only accepts one argument after shebang, so revert giving app-scripts more memory by default ([0999f23](https://github.com/ionic-team/ionic-app-scripts/commit/0999f23)), closes [#838](https://github.com/ionic-team/ionic-app-scripts/issues/838)\n\n\n\n<a name=\"1.2.2\"></a>\n## [1.2.2](https://github.com/ionic-team/ionic-app-scripts/compare/v1.2.1...v1.2.2) (2017-03-27)\n\n\n### Bug Fixes\n\n* **generators:** use correct path and handle providers correctly ([e82d5ff](https://github.com/ionic-team/ionic-app-scripts/commit/e82d5ff))\n* **rollup:** pass all config options to generate ([3502360](https://github.com/ionic-team/ionic-app-scripts/commit/3502360))\n\n\n\n<a name=\"1.2.1\"></a>\n## [1.2.1](https://github.com/ionic-team/ionic-app-scripts/compare/v1.2.0...v1.2.1) (2017-03-26)\n\n\n### Bug Fixes\n\n* **deep-linking:** only attempt to inject deep-link config if there isn't an existing config and the ([507f1a8](https://github.com/ionic-team/ionic-app-scripts/commit/507f1a8))\n* **rollup:** fix bug with not generating source-map correctly ([3b1fd16](https://github.com/ionic-team/ionic-app-scripts/commit/3b1fd16))\n\n\n\n<a name=\"1.2.0\"></a>\n# [1.2.0](https://github.com/ionic-team/ionic-app-scripts/compare/v1.1.4...v1.2.0) (2017-03-24)\n\n\n### Bug Fixes\n\n* **deep-linking:** Fix issue with deep-linking when attempting to update a template and failing, resulting in a full build but not processing deep links ([6b158d3](https://github.com/ionic-team/ionic-app-scripts/commit/6b158d3))\n* **optimization:** fix out of memory errors by providing more memory by default ([b4c287a](https://github.com/ionic-team/ionic-app-scripts/commit/b4c287a))\n* **optimizations:** only store ionic and src files in memory ([f51314f](https://github.com/ionic-team/ionic-app-scripts/commit/f51314f))\n* **uglify:** check for correct file extension ([d17f2e1](https://github.com/ionic-team/ionic-app-scripts/commit/d17f2e1))\n* **uglify:** verify source maps are generated correctly for all bundles, tests ([fc44ca6](https://github.com/ionic-team/ionic-app-scripts/commit/fc44ca6))\n* **utils:** assign correct type ([3c3666c](https://github.com/ionic-team/ionic-app-scripts/commit/3c3666c))\n* **watch:** fixed bug where options.ignore was being ignored if it's an array ([7f1e54c](https://github.com/ionic-team/ionic-app-scripts/commit/7f1e54c))\n* **watch:** queue builds ([06e4971](https://github.com/ionic-team/ionic-app-scripts/commit/06e4971))\n* **watch:** queue buildUpdates events to avoid race conditions when bundling/building ([43caefa](https://github.com/ionic-team/ionic-app-scripts/commit/43caefa))\n* **webpack:** don't overwrite css files when outputting webpack files ([a32649f](https://github.com/ionic-team/ionic-app-scripts/commit/a32649f))\n\n\n### Features\n\n* **serve:** change http-server to use request hostname instead of the configured hostname. ([8e1e81a](https://github.com/ionic-team/ionic-app-scripts/commit/8e1e81a))\n* **deep-linking:** generate default NgModule when missing by default ([90138fa](https://github.com/ionic-team/ionic-app-scripts/commit/90138fa))\n* **deep-linking:** parsing deeplink decorator is now enabled by default, no longer experimental ([e097d4e](https://github.com/ionic-team/ionic-app-scripts/commit/e097d4e))\n* **deep-linking:** upgrade script to generate NgModules for pages with [@DeepLink](https://github.com/DeepLink) decorator ([2943188](https://github.com/ionic-team/ionic-app-scripts/commit/2943188))\n* **generators:** generators for page, component, directive, pipe, provider ([e2a45e4](https://github.com/ionic-team/ionic-app-scripts/commit/e2a45e4))\n* **minification:** code-split bundles will be minified ([#814](https://github.com/ionic-team/ionic-app-scripts/issues/814)) ([d8d9a4e](https://github.com/ionic-team/ionic-app-scripts/commit/d8d9a4e))\n\n\n\n<a name=\"1.1.4\"></a>\n## [1.1.4](https://github.com/ionic-team/ionic-app-scripts/compare/v1.1.3...v1.1.4) (2017-02-23)\n\n\n### Bug Fixes\n\n* **optimizations:** comment out code instead of purge it so source-maps don't error out in some edge ([1dedc53](https://github.com/ionic-team/ionic-app-scripts/commit/1dedc53))\n* **watch:** make default watch fail-to-start timeout configurable so it works more reliably on slow ([2e2a647](https://github.com/ionic-team/ionic-app-scripts/commit/2e2a647)), closes [#772](https://github.com/ionic-team/ionic-app-scripts/issues/772)\n\n\n\n<a name=\"1.1.3\"></a>\n## [1.1.3](https://github.com/ionic-team/ionic-app-scripts/compare/v1.1.2...v1.1.3) (2017-02-17)\n\n\n### Bug Fixes\n\n* **config:** Setting readConfigJson constant wrong ([#761](https://github.com/ionic-team/ionic-app-scripts/issues/761)) ([64bc17f](https://github.com/ionic-team/ionic-app-scripts/commit/64bc17f))\n* **source-maps:** source map must correspond to .js file name with a .map at the end ([debd88b](https://github.com/ionic-team/ionic-app-scripts/commit/debd88b))\n\n\n\n<a name=\"1.1.2\"></a>\n## [1.1.2](https://github.com/ionic-team/ionic-app-scripts/compare/v1.1.1...v1.1.2) (2017-02-16)\n\n\n### Bug Fixes\n\n* **deep-links:** handle configs with internal arrays ([a7df816](https://github.com/ionic-team/ionic-app-scripts/commit/a7df816))\n* **deep-links:** only provide deep links to webpack that contain the import used in code and the abs ([fae4862](https://github.com/ionic-team/ionic-app-scripts/commit/fae4862))\n* **optimizations:** remove the js file created by the optimizations bundling pass ([c0bb3f4](https://github.com/ionic-team/ionic-app-scripts/commit/c0bb3f4))\n\n\n\n<a name=\"1.1.1\"></a>\n## [1.1.1](https://github.com/ionic-team/ionic-app-scripts/compare/v1.1.0...v1.1.1) (2017-02-15)\n\n\n### Bug Fixes\n\n* **config:** node_modules directory should not be configurable (users were finding it confusing) ([1f58aaa](https://github.com/ionic-team/ionic-app-scripts/commit/1f58aaa))\n* **copy:** support overriding config entries with empty objects ([5879a8b](https://github.com/ionic-team/ionic-app-scripts/commit/5879a8b))\n* **deeplinks:** make deep link config parsing support 2.x and 3.x deep link config ([1ac7116](https://github.com/ionic-team/ionic-app-scripts/commit/1ac7116))\n* **deeplinks:** provide deep-links config to webpack as needed vs via the constructor ([a735e96](https://github.com/ionic-team/ionic-app-scripts/commit/a735e96))\n* **http-server:** drive reading ionic.config.json based on config value ([e2d0d83](https://github.com/ionic-team/ionic-app-scripts/commit/e2d0d83))\n* **optimizations:** throw error when ionic-angular index file isn't found ([6437005](https://github.com/ionic-team/ionic-app-scripts/commit/6437005))\n* **transpile:** get tsconfig.json location from config value ([79b0eeb](https://github.com/ionic-team/ionic-app-scripts/commit/79b0eeb))\n\n\n\n<a name=\"1.1.0\"></a>\n# [1.1.0](https://github.com/ionic-team/ionic-app-scripts/compare/v1.0.1...v1.1.0) (2017-02-11)\n\n### Optimizations\nWe are starting to introduce optimizations to improve the size of the `bundle` generated by the build process.\n\nThe first set of optimizations are behind flags:\n\n`ionic_experimental_manual_treeshaking` will remove Ionic components and code that are not being used from the bundle.\n`ionic_experimental_purge_decorators` helps tree shaking by removing unnecessary `decorator` metadata from AoT code.\n\nSince these are experimental, we are looking for feedback on how the work. Please test them out and [let us know](https://github.com/ionic-team/ionic-app-scripts/issues) how it goes. See the instructions [here](https://github.com/ionic-team/ionic-app-scripts#custom-configuration).\n\n\n### Features\n* **fonts:** remove used fonts for cordova builds ([967f784](https://github.com/ionic-team/ionic-app-scripts/commit/967f784))\n\n### Bug Fixes\n\n* **build:** fix test if linting should trigger on file change ([#719](https://github.com/ionic-team/ionic-app-scripts/issues/719)) ([e13b857](https://github.com/ionic-team/ionic-app-scripts/commit/e13b857))\n* **lint:** capture results of all linted files ([eb4314e](https://github.com/ionic-team/ionic-app-scripts/commit/eb4314e)), closes [#725](https://github.com/ionic-team/ionic-app-scripts/issues/725)\n* **optimizations:** make optimizations work on windows and mac ([5fe21f3](https://github.com/ionic-team/ionic-app-scripts/commit/5fe21f3))\n* **serve:** assign all ports dynamically ([#727](https://github.com/ionic-team/ionic-app-scripts/issues/727)) ([6b4115c](https://github.com/ionic-team/ionic-app-scripts/commit/6b4115c))\n* **webpack:** fix bug with using [name] for output file name ([1128c9c](https://github.com/ionic-team/ionic-app-scripts/commit/1128c9c))\n\n\n\n\n\n\n<a name=\"1.0.1\"></a>\n## [1.0.1](https://github.com/ionic-team/ionic-app-scripts/compare/v1.0.0...v1.0.1) (2017-02-07)\n\n### Breaking Changes\n\nThis release was accidentally published with a breaking change for Deep Links. If you're using Deep Links, please don't upgrade to this version. We are in the process of changing the DeepLinks API slightly.\n\n### Bug Fixes\n\n* **angular:** support angular 2.3+ ngc api ([13e930a](https://github.com/ionic-team/ionic-app-scripts/commit/13e930a))\n* **deep-linking:** works when there isn't a valid deep link config ([62f05fc](https://github.com/ionic-team/ionic-app-scripts/commit/62f05fc))\n* **deep-links:** adjust paths for AoT ([4055d73](https://github.com/ionic-team/ionic-app-scripts/commit/4055d73))\n* **sass:** output valid source maps, that chrome can parse ([#306](https://github.com/ionic-team/ionic-app-scripts/issues/306)) ([6589550](https://github.com/ionic-team/ionic-app-scripts/commit/6589550))\n* **source-maps:** always generate source map, then purge them if not needed in postprocess step ([d26b44c](https://github.com/ionic-team/ionic-app-scripts/commit/d26b44c))\n\n\n### Features\n\n* **createWorker:** pass argv and config_argv to spawned processes ([#487](https://github.com/ionic-team/ionic-app-scripts/issues/487)) ([02dfff8](https://github.com/ionic-team/ionic-app-scripts/commit/02dfff8))\n* **lint:** new option to have stand alone lint bail ([b3bb906](https://github.com/ionic-team/ionic-app-scripts/commit/b3bb906))\n\n\n\n<a name=\"1.0.0\"></a>\n# [1.0.0](https://github.com/ionic-team/ionic-app-scripts/compare/v0.0.48...v1.0.0) (2017-01-06)\n\n\n### Upgrade Instructions\nExecute the following command from your ionic project. This installs a new peer dependency called `sw-toolbox` that is used to simplify implementing a service-worker.\n\n```\nnpm install sw-toolbox --save --save-exact\n```\n\n\n### Bug Fixes\n\n* **build:** check to ensure tsconfig contains sourcemaps true. ([e6bcf22](https://github.com/ionic-team/ionic-app-scripts/commit/e6bcf22))\n* **config:** resolve any inputs that could be paths to absolute paths ([50876eb](https://github.com/ionic-team/ionic-app-scripts/commit/50876eb))\n* **copy:** check for null object and src/dest ([eabd125](https://github.com/ionic-team/ionic-app-scripts/commit/eabd125))\n* **ngc:** revert change to purge decorators (Angular CLI did too) ([8aae85c](https://github.com/ionic-team/ionic-app-scripts/commit/8aae85c))\n* **webpack:** update environment plugin for webpack 2 RC3 ([be3aac1](https://github.com/ionic-team/ionic-app-scripts/commit/be3aac1))\n* **websockets:** fix exception when no ws clients connected during rebuild ([#616](https://github.com/ionic-team/ionic-app-scripts/issues/616)) ([8685bf8](https://github.com/ionic-team/ionic-app-scripts/commit/8685bf8))\n\n\n\n<a name=\"0.0.48\"></a>\n## [0.0.48](https://github.com/ionic-team/ionic-app-scripts/compare/v0.0.47...v0.0.48) (2016-12-19)\n\n### Upgrade Instructions\n`@ionic/app-scripts` version `0.0.47` had some breaking changes so please make sure you have performed those upgrade instructions.\n\n### Bug Fixes\n\n* **diagnostics:** fix null pointers ([72adc86](https://github.com/ionic-team/ionic-app-scripts/commit/72adc86))\n* **inline-templates:** check for existence of content ([#557](https://github.com/ionic-team/ionic-app-scripts/issues/557)) ([b68e125](https://github.com/ionic-team/ionic-app-scripts/commit/b68e125))\n* **logging:** don't log msgs about websocket state ([18185fb](https://github.com/ionic-team/ionic-app-scripts/commit/18185fb))\n* **optimization:** stop removing decorators ([45b0255](https://github.com/ionic-team/ionic-app-scripts/commit/45b0255))\n* **serve:** find an open port for the notification server if port is used. ([d6de413](https://github.com/ionic-team/ionic-app-scripts/commit/d6de413))\n* **copy:** generate project context if it doesn't exist ([26f6db8](https://github.com/ionic-team/ionic-app-scripts/commit/26f6db8a7d3398b940cfb4c4b3eb4a6f141e1be7#diff-b477061dcc036b7490cfc73741747819))\n\n\n### Features\n\n* **sass:** enable Sass indented files compilation ([#565](https://github.com/ionic-team/ionic-app-scripts/issues/565)) ([f632298](https://github.com/ionic-team/ionic-app-scripts/commit/f632298))\n\n\n\n<a name=\"0.0.47\"></a>\n## [0.0.47](https://github.com/ionic-team/ionic-app-scripts/compare/v0.0.46...v0.0.47) (2016-12-12)\n\n### Upgrade Instructions\n\n#### Install latest Ionic CLI\nInstall the latest ionic cli. `sudo` may be required depending upon your `npm` set-up.\n\n```\nnpm install -g ionic@latest\n```\n\n#### Entry Point Changes\nDelete `main.dev.ts` and `main.prod.ts` and create a `main.ts` file with the following content:\n\n```\nimport { platformBrowserDynamic } from '@angular/platform-browser-dynamic';\n\nimport { AppModule } from './app.module';\n\nplatformBrowserDynamic().bootstrapModule(AppModule);\n```\n\n#### Dev Builds By Default Changes\nAll builds are now development (non-AoT) builds by default. This allows for a better development experience when testing on a device. To get started, please follow the steps below.\n\nMake sure the `scripts` section of `package.json` looks like this:\n\n```\n  \"scripts\": {\n    \"ionic:build\": \"ionic-app-scripts build\",\n    \"ionic:serve\": \"ionic-app-scripts serve\"\n  }\n```\n\n`ionic run android --prod` will do a production build that utilizes AoT compiling and minifaction.\n`ionic emulate ios --prod` will do a production build that utilizes AoT compiling and minifaction.\n`ionic run android` will do a development build\n`ionic emulate ios` will do a development build\n\nIf you wish to run AoT but disable minifaction, do the following\n`ionic run android --aot`\n`ionic emulate ios --aot`\n\n\n#### Source Map Changes\nChange `ionic_source_map` to `ionic_source_map_type` in package.json if it is overridden.\n\n#### Config Changes\nThere were significant improvements/changes to most configs. Please review the changes and make sure any custom configs are up to date.\n\n#### Validate TSConfig settings\nVerify that `tsconfig.json` is up to date with recommended settings:\n\n```\n{\n  \"compilerOptions\": {\n    \"allowSyntheticDefaultImports\": true,\n    \"declaration\": false,\n    \"emitDecoratorMetadata\": true,\n    \"experimentalDecorators\": true,\n    \"lib\": [\n      \"dom\",\n      \"es2015\"\n    ],\n    \"module\": \"es2015\",\n    \"moduleResolution\": \"node\",\n    \"sourceMap\": true,\n    \"target\": \"es5\"\n  },\n  \"include\": [\n    \"src/**/*.ts\"\n  ],\n  \"exclude\": [\n    \"node_modules\"\n  ],\n  \"compileOnSave\": false,\n  \"atom\": {\n    \"rewriteTsconfig\": false\n  }\n}\n```\n\n\n### Breaking Changes\n1. `main.dev.ts` and `main.prod.ts` have been deprecated in favor of `main.ts` with the content of `main.dev.ts`. The content of `main.ts` will be optimized at build time for production builds.\n2. Builds are now always development (non-AoT) by default. To enable `prod` builds, use the `--prod` option.\n3. `copy.config` and `watch.config` have breaking changes moving to an easier-to-extend configuration style.\n4. `copy.config` uses `node-glob` instead of `fs-extra` to do the copy. Migrate from directory/files to globs in any custom configs.\n5. `ionic_source_map` configuration has been changed to `ionic_source_map_type`.\n6. Source maps now use `source-map` devtool option by default instead of `eval`. Change `ionic_source_map_type` option to return to the faster building `eval`.\n\n### Bug Fixes\n\n* **AoT:** dynamically enable prod mode for AoT builds ([0594803](https://github.com/ionic-team/ionic-app-scripts/commit/0594803))\n* **AoT:** use in-memory data store instead of .tmp directory for AoT codegen ([93106ff](https://github.com/ionic-team/ionic-app-scripts/commit/93106ff))\n* **build:** every build should run clean sync and copy async. ([6d4eb6e](https://github.com/ionic-team/ionic-app-scripts/commit/6d4eb6e))\n* **copy:** Resolve race condition in copy task, move to glob config ([cc99a73](https://github.com/ionic-team/ionic-app-scripts/commit/cc99a73))\n* **lab:** add lab to files ([f42c980](https://github.com/ionic-team/ionic-app-scripts/commit/f42c980))\n* **livereload:** livereload now correctly serves cordova plugins on run and emulate. ([a0c3f5d](https://github.com/ionic-team/ionic-app-scripts/commit/a0c3f5d))\n* **livereload:** on project build all pages connected should reload. ([#513](https://github.com/ionic-team/ionic-app-scripts/issues/513)) ([62d6b23](https://github.com/ionic-team/ionic-app-scripts/commit/62d6b23))\n* **livereload:** use localhost instead of 0.0.0.0 when injecting live reload script ([#450](https://github.com/ionic-team/ionic-app-scripts/issues/450)) ([7f8a0c3](https://github.com/ionic-team/ionic-app-scripts/commit/7f8a0c3))\n* **logging:** remove unnecessary websocket error msg, clean up copy error msg ([1517b06](https://github.com/ionic-team/ionic-app-scripts/commit/1517b06))\n* **ngc:** simpler AoT error reporting ([1b0f163](https://github.com/ionic-team/ionic-app-scripts/commit/1b0f163))\n* **serve:** add flag to indicate to serve for a cordova app ([93782e7](https://github.com/ionic-team/ionic-app-scripts/commit/93782e7))\n* **source-maps:** use detailed source-map as default, fix windows path issue ([19464b3](https://github.com/ionic-team/ionic-app-scripts/commit/19464b3))\n* **workers:** generate context in worker threads ([af036ec](https://github.com/ionic-team/ionic-app-scripts/commit/af036ec))\n\n\n### Features\n\n* **build:** replace --dev flag with --prod and add flags --aot, --minifyJs, --minifyCss, --optimizeJs ([99922ce](https://github.com/ionic-team/ionic-app-scripts/commit/99922ce))\n* **bundle:** pre and post bundle hooks ([4835550](https://github.com/ionic-team/ionic-app-scripts/commit/4835550))\n* **copy:** update copy config to move web workers ([a909fc4](https://github.com/ionic-team/ionic-app-scripts/commit/a909fc4))\n* **lab:** fresh coat of paint ([edb6f09](https://github.com/ionic-team/ionic-app-scripts/commit/edb6f09))\n* **replacePathVars:** support interpolation of objects and arrays ([#449](https://github.com/ionic-team/ionic-app-scripts/issues/449)) ([e039d46](https://github.com/ionic-team/ionic-app-scripts/commit/e039d46))\n* all arguments passed should be compared as case insensitive ([085c897](https://github.com/ionic-team/ionic-app-scripts/commit/085c897))\n\n\n\n<a name=\"0.0.46\"></a>\n## [0.0.46](https://github.com/ionic-team/ionic-app-scripts/compare/v0.0.44...v0.0.46) (2016-11-21)\n\n\n### Bug Fixes\n\n* **build:** better support for saving multiple files at a time ([254bb6c](https://github.com/ionic-team/ionic-app-scripts/commit/254bb6c))\n* **copy:** ionicons copied from ionicons ([69f89a8](https://github.com/ionic-team/ionic-app-scripts/commit/69f89a8))\n* **errors:** skip HTTP errors ([5906167](https://github.com/ionic-team/ionic-app-scripts/commit/5906167))\n* **proxies:** Wrong parameter in Logger.info, in setupProxies function causing proxies not to load ([#395](https://github.com/ionic-team/ionic-app-scripts/issues/395)) ([316b1de](https://github.com/ionic-team/ionic-app-scripts/commit/316b1de))\n* **typescript:** lock typescript version to 2.0.x for now due to build error with 2.1.x ([ef7203b](https://github.com/ionic-team/ionic-app-scripts/commit/ef7203b))\n* **webpack:** fix path resolution ([97c23f9](https://github.com/ionic-team/ionic-app-scripts/commit/97c23f9))\n* **webpack:** reference json-loader to account for webpack breaking change ([d6fe709](https://github.com/ionic-team/ionic-app-scripts/commit/d6fe709))\n* **webpack:** resolve modules to rootDir ([#365](https://github.com/ionic-team/ionic-app-scripts/issues/365)) ([64eb845](https://github.com/ionic-team/ionic-app-scripts/commit/64eb845))\n\n\n### Features\n\n* **options:** allow users to pass their own cleanCss Options ([#377](https://github.com/ionic-team/ionic-app-scripts/issues/377)) ([20df6d4](https://github.com/ionic-team/ionic-app-scripts/commit/20df6d4))\n\n\n<a name=\"0.0.45\"></a>\n## [0.0.45](https://github.com/ionic-team/ionic-app-scripts/compare/v0.0.44...v0.0.45) (2016-11-17)\n\n\n### Bug Fixes\n\n* **errors:** runtime error immediately, selectable stack ([70f68da](https://github.com/ionic-team/ionic-app-scripts/commit/70f68da))\n* **inline-templates:** update bundle and memory file representation on template change ([11a949d](https://github.com/ionic-team/ionic-app-scripts/commit/11a949d))\n* **rollup:** invalidate cache on template change ([80c0eb6](https://github.com/ionic-team/ionic-app-scripts/commit/80c0eb6))\n* **webpack:** invalidate cache by use of timestamps ([4d6bbd5](https://github.com/ionic-team/ionic-app-scripts/commit/4d6bbd5))\n\n\n### Features\n\n* **run-build-update:** handle linked npm modules ([#375](https://github.com/ionic-team/ionic-app-scripts/issues/375)) ([0f113c8](https://github.com/ionic-team/ionic-app-scripts/commit/0f113c8))\n* **serve:** add '/ionic-lab' as an alias for the lab html file path. ([c319404](https://github.com/ionic-team/ionic-app-scripts/commit/c319404))\n\n\n\n<a name=\"0.0.44\"></a>\n## [0.0.44](https://github.com/ionic-team/ionic-app-scripts/compare/v0.0.43...v0.0.44) (2016-11-15)\n\n\n### Bug Fixes\n\n* **debug:** cmd+shift+8 to show debug menu ([a26d729](https://github.com/ionic-team/ionic-app-scripts/commit/a26d729))\n* **error:** (cmd/ctrl)+8 for debug menu ([89550af](https://github.com/ionic-team/ionic-app-scripts/commit/89550af))\n* **error:** add header padding for cordova iOS ([5c4c547](https://github.com/ionic-team/ionic-app-scripts/commit/5c4c547))\n* **error:** apply correct css for runtime error close ([81f1d75](https://github.com/ionic-team/ionic-app-scripts/commit/81f1d75))\n* **error:** fix content scrolling ([3b82465](https://github.com/ionic-team/ionic-app-scripts/commit/3b82465))\n* **error:** reload immediately after js/html update ([07f918e](https://github.com/ionic-team/ionic-app-scripts/commit/07f918e))\n* **error:** safari css fixes ([7c2fb59](https://github.com/ionic-team/ionic-app-scripts/commit/7c2fb59))\n* **serve:** correct paths so that --lab works ([1d99a98](https://github.com/ionic-team/ionic-app-scripts/commit/1d99a98))\n* **serve:** open browser to localhost ([14275c7](https://github.com/ionic-team/ionic-app-scripts/commit/14275c7))\n* **transpile:** normalize and resolve paths always for OS independence ([ca6c889](https://github.com/ionic-team/ionic-app-scripts/commit/ca6c889))\n* **watch:** fallback for when chokidar watch ready/error don't fire (happens on windows when file is ([519cd7f](https://github.com/ionic-team/ionic-app-scripts/commit/519cd7f)), closes [#282](https://github.com/ionic-team/ionic-app-scripts/issues/282)\n* **watch:** watch now ignores Mac OS meta data files ([02d0b8d](https://github.com/ionic-team/ionic-app-scripts/commit/02d0b8d)), closes [#331](https://github.com/ionic-team/ionic-app-scripts/issues/331)\n* **webpack:** source maps link to original src for ide debugging ([39edd2e](https://github.com/ionic-team/ionic-app-scripts/commit/39edd2e))\n\n\n### Features\n\n* **debug:** debug menu options ([53d6e30](https://github.com/ionic-team/ionic-app-scripts/commit/53d6e30))\n* **debug:** shake device to show debug menu ([770f4e3](https://github.com/ionic-team/ionic-app-scripts/commit/770f4e3))\n* **error:** client runtime error reporting ([fc40b92](https://github.com/ionic-team/ionic-app-scripts/commit/fc40b92))\n* **error:** syntax and error highlighting ([8836310](https://github.com/ionic-team/ionic-app-scripts/commit/8836310))\n\n\n\n<a name=\"0.0.43\"></a>\n## [0.0.43](https://github.com/ionic-team/ionic-app-scripts/compare/v0.0.42...v0.0.43) (2016-11-10)\n\n\n### Bug Fixes\n\n* **rollup:** removing rollup metadata prefix for paths ([350a288](https://github.com/ionic-team/ionic-app-scripts/commit/350a288))\n* **watch:** remove shorthand arg for watch ([0685c0b](https://github.com/ionic-team/ionic-app-scripts/commit/0685c0b)), closes [#290](https://github.com/ionic-team/ionic-app-scripts/issues/290)\n* **webpack:** typo in import, close [#326](https://github.com/ionic-team/ionic-app-scripts/issues/326) ([#341](https://github.com/ionic-team/ionic-app-scripts/issues/341)) ([6b89fa2](https://github.com/ionic-team/ionic-app-scripts/commit/6b89fa2))\n\n\n\n<a name=\"0.0.42\"></a>\n## [0.0.42](https://github.com/ionic-team/ionic-app-scripts/compare/v0.0.41...v0.0.42) (2016-11-09)\n\n## Upgrade Steps\nTo use this version of `@ionic/app-scripts`, follow these steps to upgrade:\n\n1. Install the latest version of the ionic cli\n\n  ```\n    npm install ionic@latest -g\n  ```\n\n  Note: sudo may be required depending on your workstation set-up\n\n2. Update the project's `package.json` file's `script` section to look like this:\n\n  ```\n  ...\n  \"scripts\" : {\n    \"ionic:build\": \"ionic-app-scripts build\",\n    \"ionic:serve\": \"ionic-app-scripts serve\"\n  }\n  ...\n  ```\n\n  Note: This is removing several deprecated Ionic scripts. If you have any of your own custom scripts, don't remove them.\n\n\n3. Install the latest version of `@ionic/app-scripts`\n\n  ```\n  npm install @ionic/app-scripts@latest --save-dev\n  ```\n\n### Bug Fixes\n\n* **bundling:** execute bundle updates if full bundle has completed at least once ([fbe56dc](https://github.com/ionic-team/ionic-app-scripts/commit/fbe56dc))\n* **sass:** remove broken sass caching ([91faf0b](https://github.com/ionic-team/ionic-app-scripts/commit/91faf0b))\n\n\n### Features\n\n* **error:** use datauri for favicon build status ([892cf4a](https://github.com/ionic-team/ionic-app-scripts/commit/892cf4a))\n* **errors:** overlay build errors during development ([87f7648](https://github.com/ionic-team/ionic-app-scripts/commit/87f7648))\n\n\n\n<a name=\"0.0.41\"></a>\n## [0.0.41](https://github.com/ionic-team/ionic-app-scripts/compare/v0.0.40...v0.0.41) (2016-11-07)\n\n\n### Bug Fixes\n\n* **webpack:** use source-maps instead of eval for prod builds ([fdd86be](https://github.com/ionic-team/ionic-app-scripts/commit/fdd86be))\n\n\n\n<a name=\"0.0.40\"></a>\n## 0.0.40 (2016-11-07)\n\n### Breaking Changes\n\n`ionic_source_map` variable is now used to drive the `devtool` (sourcemap) value for webpack. It now defaults to `eval` for faster builds. Set it to `source-map` for `typescript` sourcemaps.\n\n### Bug Fixes\n* **sourcemaps:** fix source maps for all files ([066de6d](https://github.com/ionic-team/ionic-app-scripts/commit/066de6d))\n* **sourcemaps:** webpack .ts sourcemaps ([bfca1be](https://github.com/ionic-team/ionic-app-scripts/commit/bfca1be))\n* **webpack:** modify config to use IONIC_APP_SCRIPTS_DIR variable ([2b7c606](https://github.com/ionic-team/ionic-app-scripts/commit/2b7c606))\n\n\n### Features\n* **events:** emit bundler events ([8d73da9](https://github.com/ionic-team/ionic-app-scripts/commit/8d73da9))\n* **exports:** add templateUpdate and fullBuildUpdate ([a31897d](https://github.com/ionic-team/ionic-app-scripts/commit/a31897d))\n* **webpack source maps:** make it easy to configure source map type ([03565b7](https://github.com/ionic-team/ionic-app-scripts/commit/03565b7))\n\n\n### Performance Improvements\n\n* **webpack:** speed up webpack build by not using file-system and watches ([23ad195](https://github.com/ionic-team/ionic-app-scripts/commit/23ad195))\n\n\n# 0.0.39 (2016-10-31)\n* Switch default bundler to Webpack\n\n# 0.0.36 (2016-10-15)\n\n* Fix handling multiple async template updates\n\n\n# 0.0.35 (2016-10-15)\n\n* Fix resolving index files correctly\n* Fix template rebuilds for multiple templates in one file\n* Fix ability to watchers to ignore paths\n\n\n# 0.0.34 (2016-10-15)\n\n* Fix silently failed bundles\n* Fix template path resolving issues\n\n\n# 0.0.33 (2016-10-14)\n\n* Improve build times for template changes\n* Fix bundle updates on template changes\n\n\n# 0.0.32 (2016-10-14)\n\n* Fix Windows entry path normalization\n\n\n# 0.0.31 (2016-10-13)\n\n* Add ability use multiple processor cores for various subtasks\n* Use typescript `createProgram` to transpile entire app\n* Add syntax highlighting and colors to typescript, sass and tslint errors\n* Improved error messages for typescript errors\n* `clean` task only cleans out the `www/build/` directory rather than all of `www/`\n* Add task to copy `src/service-worker.js` to `www/service-worker.js`\n* Add task to copy `src/manifest.json` to `www/manifest.json`\n\n\n# 0.0.30 (2016-10-06)\n\n* Fix JS source maps\n* Fix template inlining\n\n\n# 0.0.29 (2016-10-05)\n\n* Addressed memory usage error\n* Dev builds no longer use the `.tmp` directory\n* Dev build entry files should be the source `main.dev.ts` file\n* Custom rollup configs should remove the `ngTemplate()` plugin\n"
  },
  {
    "path": "LICENSE",
    "content": "MIT License\n\nCopyright (c) 2016 Drifty Co\n\nPermission is hereby granted, free of charge, to any person obtaining a copy\nof this software and associated documentation files (the \"Software\"), to deal\nin the Software without restriction, including without limitation the rights\nto use, copy, modify, merge, publish, distribute, sublicense, and/or sell\ncopies of the Software, and to permit persons to whom the Software is\nfurnished to do so, subject to the following conditions:\n\nThe above copyright notice and this permission notice shall be included in all\ncopies or substantial portions of the Software.\n\nTHE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR\nIMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,\nFITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE\nAUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER\nLIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,\nOUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE\nSOFTWARE.\n"
  },
  {
    "path": "README.md",
    "content": "[![npm version](https://img.shields.io/npm/v/@ionic/app-scripts.svg)](https://www.npmjs.com/package/@ionic/app-scripts)\n[![Circle CI](https://circleci.com/gh/ionic-team/ionic-app-scripts.svg?style=shield&circle-token=:circle-token)](https://circleci.com/gh/ionic-team/ionic-app-scripts)\n\n# DISCLAIMER: NO LONGER MAINTAINED\nOh, [hello there](https://media.giphy.com/media/8JTFsZmnTR1Rs1JFVP/giphy.gif)! Ionic App Scripts is a tool tied specifically to version 3.x of Ionic Framework. Since the release of version 4.0 of Ionic Framework, we are no longer using Ionic App Scripts for building in Ionic Framework. Version 3 of Ionic Framework is no longer actively maintained by us. For more information on which versions are active, see our [support policy](https://ionicframework.com/docs/reference/support#framework-maintenance-and-support-status).\nDue to this, we are no longer maintaining Ionic App Scripts and we recommend developers update their apps to the latest framework release. This provides several new features, bug fixes, performance improvements, as well as up to date tooling for Ionic apps. For more details on how to upgrade, check out our [migration guide](https://ionicframework.com/docs/reference/migration).\n\n# Ionic App Scripts\n\nHelper scripts to get [Ionic apps](https://ionicframework.com/) up and running quickly (minus the config overload).\n\nTo get the latest `@ionic/app-scripts`, please run:\n\n```\nnpm install @ionic/app-scripts@latest --save-dev\n```\n\n### Config Defaults\n\nOut of the box, Ionic starters have been preconfigured with great defaults for building fast apps, including:\n\n- Multi-core processing tasks in parallel for faster builds\n- In-memory file transpiling and bundling\n- Transpiling source code to ES5 JavaScript\n- Ahead of Time (AoT) template compiling\n- Just in Time (JiT) template compiling\n- Template inlining for JiT builds\n- Bundling modules for faster runtime execution\n- Treeshaking unused components and dead-code removal\n- Generating CSS from bundled component Sass files\n- Autoprefixing vendor CSS prefixes\n- Minifying JavaScript files\n- Compressing CSS files\n- Copying `src` static assets to `www`\n- Linting source files\n- Watching source files for live-reloading\n\nJust the bullet list above is a little overwhelming, and each task requires quite a bit of development time just to get started. Ionic App Script's intention is to make it easier to complete common tasks so developers can focus on building their app, rather than building build scripts.\n\nNote that the [Ionic Framework's](https://github.com/ionic-team/ionic) source is made up of modules and can be packaged by any bundler or build process. However, this project's goal is provide simple scripts to make building Ionic apps easier, while also allowing developers to further configure their build process.\n\n\n### npm Scripts\n\nInstead of depending on external task runners, Ionic App Scripts now prefers being executed from [npm scripts](https://docs.npmjs.com/misc/scripts). Ionic's npm scripts come preconfigured in the project's `package.json` file. For example, this is the default setup for npm scripts in each starter:\n\n```\n  \"scripts\": {\n    \"ionic:build\": \"ionic-app-scripts build\",\n    \"ionic:serve\": \"ionic-app-scripts serve\"\n  },\n```\n\nTo run the `build` script found in the `package.json` `scripts` property, execute:\n\n```\nnpm run build\n```\n\n## Environments\n\nYou can use Node style `process.env.MY_VAR` syntax directly in your typescript\nand when the application is bundled it'll be replaced with the following order of precedence:\n* If the variable exists in the process environment it will be replaced with that value.\n* If the variable is not defined in the process environment it will be read from a `.env.dev`\nfile for dev builds or `.env.prod` file for prod builds which are located in the root of the app\n* If the variable is not defined in either place it will be `undefined`\n\nIn order to take advantage of this apps will need a `src/declarations.d.ts` file with the following declaration:\n```typescript\ndeclare var process: { env: { [key: string]: string | undefined; } };\n```\n\n*Note*: This declaration may conflict if `@types/node` is installed in your project. See [#3541](https://github.com/ionic-team/ionic-cli/issues/3541).\n\n## Custom Configuration\n\nIn many cases, the defaults which Ionic provides cover most of the scenarios required by developers; however, Ionic App Scripts does provide multiple ways to configure and override the defaults for each of the various tasks. Note that Ionic will always apply its defaults for any property that was not provided by custom configuration.\n\n[Default Config Files](https://github.com/ionic-team/ionic-app-scripts/tree/master/config)\n\n### package.json Config\n\nIonic projects use the `package.json` file for configuration. There's a handy [config](https://docs.npmjs.com/misc/config#per-package-config-settings) property which can be used. Below is an example of setting a custom config file using the `config` property in a project's `package.json`.\n\n```\n  \"config\": {\n    \"ionic_cleancss\": \"./config/cleancss.config.js\"\n  },\n```\n\n### Command-line Flags\n\nRemember how we're actually running `ionic-app-scripts` from the `scripts` property of a project's `package.json` file? Well we can also add command-line flags to each script, or make new scripts with these custom flags. For example:\n\n```\n  \"scripts\": {\n    \"build\": \"ionic-app-scripts build --webpack ./config/webpack.dev.config.js\",\n    \"minify\": \"ionic-app-scripts minify --cleancss ./config/cleancss.config.js\",\n  },\n```\n\nThe same command-line flags can be also applied to `npm run` commands too, such as:\n\n```\nnpm run build --webpack ./config/webpack.dev.config.js\n```\n\n\n### Overriding Config Files\n\n| Config File | package.json Config | Cmd-line Flag         |\n|-------------|---------------------|-----------------------|\n| CleanCss    | `ionic_cleancss`    | `--cleancss` or `-e`  |\n| Copy        | `ionic_copy`        | `--copy` or `-y`      |\n| Generator   | `ionic_generator`   | `--generator` or `-g` |\n| NGC         | `ionic_ngc`         | `--ngc` or `-n`       |\n| Sass        | `ionic_sass`        | `--sass` or `-s`      |\n| TSLint      | `ionic_tslint`      | `--tslint` or `-i`    |\n| UglifyJS    | `ionic_uglifyjs`    | `--uglifyjs` or `-u`  |\n| Watch       | `ionic_watch`       | `--watch`             |\n| Webpack     | `ionic_webpack`     | `--webpack` or `-w`   |\n\n\n### Overriding Config Values\n\n| Config Values   | package.json Config | Cmd-line Flag | Defaults        | Details        |\n|-----------------|---------------------|---------------|-----------------|----------------|\n| root directory  | `ionic_root_dir`    | `--rootDir`   | `process.cwd()` | The directory path of the Ionic app |\n| src directory   | `ionic_src_dir`     | `--srcDir`    | `src`           | The directory holding the Ionic src code |\n| www directory   | `ionic_www_dir`     | `--wwwDir`    | `www`           | The deployable directory containing everything needed to run the app |\n| build directory | `ionic_build_dir`   | `--buildDir`  | `build`         | The build process uses this directory to store generated files, etc |\n| temp directory   | `ionic_tmp_dir`     | `--tmpDir`    | `.tmp`           | Temporary directory for writing files for debugging and various build tasks |\n| ionic-angular directory | `ionic_angular_dir` | `--ionicAngularDir` | `ionic-angular` | ionic-angular directory |\n| ionic-angular entry point | `ionic_angular_entry_point` | `--ionicAngularEntryPoint` | `index.js` | entry point file for ionic-angular |\n| source map type | `ionic_source_map_type`  | `--sourceMapType` | `source-map` | Chooses the webpack `devtool` option. `eval` and `source-map` are supported |\n| generate source map | `ionic_generate_source_map`  | `--generateSourceMap` | `true` | Determines whether to generate a source map or not |\n| tsconfig path | `ionic_ts_config`  | `--tsconfig` | `{{rootDir}}/tsconfig.json` | absolute path to tsconfig.json |\n| app entry point | `ionic_app_entry_point`  | `--appEntryPoint` | `{{srcDir}}/app/main.ts` | absolute path to app's entrypoint bootstrap file |\n| app ng module path | `ionic_app_ng_module_path`  | `--appNgModulePath` | `{{srcDir}}/app/app.module.ts` | absolute path to app's primary `NgModule` |\n| app ng module class | `ionic_app_ng_module_class`  | `--appNgModuleClass` | `AppModule` | Exported class name for app's primary `NgModule` |\n| clean before copy | `ionic_clean_before_copy`  | `--cleanBeforeCopy` | `false` | clean out existing files before copy task runs |\n| output js file | `ionic_output_js_file_name`  | `--outputJsFileName` | `main.js` | name of js file generated in `buildDir` |\n| output css file | `ionic_output_css_file_name`  | `--outputCssFileName` | `main.css` | name of css file generated in `buildDir` |\n| bail on lint error | `ionic_bail_on_lint_error`  | `--bailOnLintError` | `null` | Set to `true` to make stand-alone lint commands fail with non-zero status code |\n| enable type checking during lint | `ionic_type_check_on_lint`  | `--typeCheckOnLint` | `null` | Set to `true` to enable [type checking](https://palantir.github.io/tslint/usage/type-checking) during lint |\n| write AoT files to disk | `ionic_aot_write_to_disk` | `--aotWriteToDisk` | `null` | Set to `true` to write files to disk for debugging |\n| print webpack dependency tree | `ionic_print_webpack_dependency_tree` | `--printWebpackDependencyTree` | `null` | Set to `true` to print out a dependency tree after running Webpack |\n| parse deeplink config | `ionic_parse_deeplinks` | `--parseDeepLinks` | `true` | Parses and extracts data from the `@IonicPage` decorator |\n| convert bundle to ES5 | `ionic_build_to_es5` | `--buildToEs5` | `true` | Convert bundle to ES5 for production deployments |\n| default watch timeout | `ionic_start_watch_timeout` | `--startWatchTimeout` | `3000` | Milliseconds controlling the default watch timeout |\n| choose the polyfill  | `ionic_polyfill_name` | `--polyfillName` | `polyfills` | Change with polyfills.modern or polyfills.ng (all options)[https://github.com/driftyco/ionic/tree/master/scripts/polyfill] |\n| enable linting | `ionic_enable_lint` | `--enableLint` | `true` | Set to `false` for skipping the linting after the build |\n\n\n\n\n\n### Ionic Environment Variables\n\nThese environment variables are automatically set to [Node's `process.env`](https://nodejs.org/api/process.html#process_process_env) property. These variables can be useful from within custom configuration files, such as custom `webpack.config.js` file.\n\n| Environment Variable       | Description                                                          |\n|----------------------------|----------------------------------------------------------------------|\n| `IONIC_ENV`                | Value can be either `prod` or `dev`.                                 |\n| `IONIC_ROOT_DIR`           | The absolute path to the project's root directory.                   |\n| `IONIC_SRC_DIR`            | The absolute path to the app's source directory.                     |\n| `IONIC_WWW_DIR`            | The absolute path to the app's public distribution directory.        |\n| `IONIC_BUILD_DIR`          | The absolute path to the app's bundled js and css files.             |\n| `IONIC_TMP_DIR`            | Temp directory for debugging generated/optimized code and various build tasks |\n| `IONIC_NODE_MODULES_DIR`   | The absolute path to the `node_modules` directory.                   |\n| `IONIC_ANGULAR_DIR`        | The absolute path to the `ionic-angular` node_module directory.      |\n| `IONIC_APP_SCRIPTS_DIR`    | The absolute path to the `@ionic/app-scripts` node_module directory. |\n| `IONIC_SOURCE_MAP_TYPE`    | The Webpack `devtool` setting. `eval` and `source-map` are supported.|\n| `IONIC_GENERATE_SOURCE_MAP`| Determines whether to generate a sourcemap or not.                   |\n| `IONIC_TS_CONFIG`          | The absolute path to the project's `tsconfig.json` file              |\n| `IONIC_APP_ENTRY_POINT`    | The absolute path to the project's `main.ts` entry point file        |\n| `IONIC_APP_NG_MODULE_PATH` | The absolute path to app's primary `NgModule`                        |\n| `IONIC_APP_NG_MODULE_CLASS`    | The exported class name for app's primary `NgModule`             |\n| `IONIC_GLOB_UTIL`          | The path to Ionic's `glob-util` script. Used within configs.         |\n| `IONIC_CLEAN_BEFORE_COPY`  | Attempt to clean existing directories before copying files.          |\n| `IONIC_CLOSURE_JAR`        | The absolute path ot the closure compiler jar file                   |\n| `IONIC_OUTPUT_JS_FILE_NAME` | The file name of the generated javascript file                      |\n| `IONIC_OUTPUT_CSS_FILE_NAME` | The file name of the generated css file                            |\n| `IONIC_WEBPACK_FACTORY`    | The absolute path to Ionic's `webpack-factory` script                |\n| `IONIC_WEBPACK_LOADER`     | The absolute path to Ionic's custom webpack loader                   |\n| `IONIC_BAIL_ON_LINT_ERROR`     | Boolean determining whether to exit with a non-zero status code on error |\n| `IONIC_TYPE_CHECK_ON_LINT` | Boolean determining whether to type check code during lint or not |\n| `IONIC_AOT_WRITE_TO_DISK` | `--aotWriteToDisk` | `null` | Set to `true` to write files to disk for debugging |\n| `IONIC_PRINT_WEBPACK_DEPENDENCY_TREE` | boolean to print out a dependency tree after running Webpack |\n| `IONIC_PARSE_DEEPLINKS` | boolean to enable parsing the Ionic 3.x deep links API for lazy loading |\n| `IONIC_BUILD_TO_ES5` | boolean to enable converting bundle to ES5 for production deployments |\n| `IONIC_START_WATCH_TIMEOUT` | Milliseconds controlling the default watch timeout |\n\n\nThe `process.env.IONIC_ENV` environment variable can be used to test whether it is a `prod` or `dev` build, which automatically gets set by any command. By default the `build` and `serve` tasks produce `dev` builds (a build that does not include Ahead of Time (AoT) compilation or minification). To force a `prod` build you should use the `--prod` command line flag.\n\n`process.env.IONIC_ENV` environment variable is set to `prod` for `--prod` builds, otherwise `dev` for all other builds.\n\n\n## All Available Tasks\n\nThese tasks are available within `ionic-app-scripts` and can be added to npm scripts or any Node command.\n\n| Task       | Description                                                                                         |\n|------------|-----------------------------------------------------------------------------------------------------|\n| `build`    | A complete build of the application. It uses `development` settings by default. Use `--prod` to create an optimized build |\n| `clean`    | Empty the `www/build` directory.                                                                          |\n| `cleancss` | Compress the output CSS with [CleanCss](https://github.com/jakubpawlowicz/clean-css)                |\n| `copy`     | Run the copy tasks, which by defaults copies the `src/assets/` and `src/index.html` files to `www`. |\n| `lint`     | Run the linter against the source `.ts` files, using the `tslint.json` config file at the root.     |\n| `minify`   | Minifies the output JS bundle and compresses the compiled CSS.                                      |\n| `sass`     | Sass compilation of used modules. Bundling must have at least ran once before Sass compilation.     |\n| `watch`    | Runs watch for dev builds.                                                                          |\n\nExample NPM Script:\n\n```\n  \"scripts\": {\n    \"minify\": \"ionic-app-scripts minify\"\n  },\n```\n\n## Tips\n1. The Webpack `devtool` setting is driven by the `ionic_source_map_type` variable. It defaults to `source-map` for the best quality source map. Developers can enable significantly faster builds by setting `ionic_source_map_type` to `eval`.\n2. By default, the `lint` command does not exit with a non-zero status code on error. To enable this, pass `--bailOnLintError true` to the command.\n\n```\n\"scripts\" : {\n  ...\n  \"lint\": \"ionic-app-scripts lint\"\n  ...\n}\n```\n\n```\nnpm run lint --bailOnLintError true\n```\n\n## The Stack\n\n- [Ionic Framework](https://ionicframework.com/)\n- [TypeScript Compiler](https://www.typescriptlang.org/)\n- [Angular Compiler (NGC)](https://github.com/angular/angular/tree/master/modules/%40angular/compiler-cli)\n- [Webpack Module Bundler](https://webpack.js.org/)\n- Ionic Component Sass\n- [Node Sass](https://www.npmjs.com/package/node-sass)\n- [Autoprefixer](https://github.com/postcss/autoprefixer)\n- [UglifyJS](https://lisperator.net/uglifyjs/)\n- [CleanCss](https://github.com/jakubpawlowicz/clean-css)\n- [TSLint](https://palantir.github.io/tslint/)\n\n## Contributing\n\nWe welcome any PRs, issues, and feedback! Please be respectful and follow the [Code of Conduct](https://github.com/ionic-team/ionic/blob/master/CODE_OF_CONDUCT.md).\n\nWe use Node 6, and NPM 5 for contributing.\n\n### Publish a Nightly Build\n\n1. Run `npm run build` to generate the `dist` directory\n2. Run `npm run test` to validate the `dist` works\n3. Tick the `package.json` version\n4. Run `npm run nightly` to generate a nightly build on npm\n\n\n\n### Publish a release\n\nExecute the following steps to publish a release:\n\n1. Ensure your branch has been merged into `master`\n2. Run `npm run build` to generate the `dist` directory\n3. Run `npm run test` to validate the `dist` works\n4. Temporarily tick the `package.json` version\n5. Run `npm run changelog` to append the latest additions to the changelog\n6. Manually verify and commit the changelog changes. Often times you'll want to manually add content/instructions\n7. Revert the `package.json` version to the original version\n8. Run `npm version patch` to tick the version and generate a git tag\n9. Run `npm run github-release` to create the github release entry\n10. Run `npm publish` to publish the package to npm\n11. `git push origin master` - push changes to master"
  },
  {
    "path": "bin/ion-dev.js",
    "content": "window.IonicDevServerConfig = window.IonicDevServerConfig || {};\n\nwindow.IonicDevServer = {\n  start: function() {\n    this.msgQueue = [];\n\n    this.consoleLog = console.log;\n    this.consoleError = console.error;\n    this.consoleWarn = console.warn;\n\n    IonicDevServerConfig.systemInfo.push('Navigator Platform: ' + window.navigator.platform);\n    IonicDevServerConfig.systemInfo.push('User Agent: ' + window.navigator.userAgent);\n\n    if (IonicDevServerConfig.sendConsoleLogs) {\n      this.patchConsole();\n    }\n\n    this.openConnection();\n    this.bindEvents();\n\n    var self = this;\n    document.addEventListener(\"DOMContentLoaded\", function() {\n      var diagnosticsEle = document.getElementById('ion-diagnostics');\n      if (diagnosticsEle) {\n        self.buildStatus('error');\n      } else {\n        self.buildStatus('success');\n      }\n    });\n\n    if (window.cordova && document.documentElement) {\n      document.documentElement.classList.add('ion-diagnostics-cordova');\n\n      var ua = window.navigator.userAgent.toLowerCase();\n      if ((ua.indexOf('ipad') > -1 || ua.indexOf('iphone') > -1 || ua.indexOf('ipod') > -1) && ua.indexOf('windows phone') === -1) {\n        document.documentElement.classList.add('ion-diagnostics-cordova-ios');\n      }\n    }\n\n    window.onerror = function(msg, url, lineNo, columnNo, error) {\n      self.handleError(error);\n    };\n  },\n\n  handleError: function(err) {\n    if (!err) return;\n\n    // Ignore HTTP errors\n    if(err.url || err.headers) return;\n\n    // Socket is ready so send this error to the server for prettifying\n    if (this.socketReady) {\n      var msg = {\n        category: 'runtimeError',\n        type: 'runtimeError',\n        data: {\n          message: err.message ? err.message.toString() : null,\n          stack: err.stack ? err.stack.toString() : null\n        }\n      };\n      this.queueMessageSend(msg);\n\n    } else {\n      var c = [];\n\n      c.push('<div class=\"ion-diagnostics-header\">');\n      c.push(' <div class=\"ion-diagnostics-header-content\">');\n      c.push('  <div class=\"ion-diagnostics-header-inner\">Error</div>');\n      c.push('  <div class=\"ion-diagnostics-buttons\">');\n      c.push('   <button id=\"ion-diagnostic-close\">Close</button>');\n      c.push('  </div>');\n      c.push(' </div>');\n      c.push('</div>');\n\n      c.push('<div class=\"ion-diagnostics-content\">');\n      c.push(' <div class=\"ion-diagnostic\">');\n      c.push('  <div class=\"ion-diagnostic-masthead\">');\n      c.push('   <div class=\"ion-diagnostic-header\">Runtime Error</div>');\n      c.push('   <div class=\"ion-diagnostic-message\">' + this.escapeHtml(err.message) + '</div>');\n      c.push('  </div>');\n      c.push('  <div class=\"ion-diagnostic-stack-header\">Stack</div>');\n      c.push('  <div class=\"ion-diagnostic-stack\">' + this.escapeHtml(err.stack) + '</div>');\n      c.push(' </div>');\n      c.push('</div>');\n\n      this.buildUpdate({\n        type: 'clientError',\n        data: {\n          diagnosticsHtml: c.join('')\n        }\n      });\n    }\n  },\n\n  reloadApp: function() {\n    window.location.reload(true);\n  },\n\n  openConnection: function() {\n    var self = this;\n    var socketProtocol = window.location.protocol === 'https:' ? 'wss:' : 'ws:';\n    this.socket = new WebSocket(socketProtocol + '//' + window.location.hostname + ':' + IonicDevServerConfig.wsPort);\n\n    this.socket.onopen = function(ev) {\n      self.socketReady = true;\n\n      self.socket.onmessage = function(ev) {\n        try {\n          var msg = JSON.parse(ev.data);\n          switch (msg.category) {\n            case 'buildUpdate':\n              self.buildUpdate(msg);\n              break;\n          }\n        } catch (e) {\n          self.consoleError('error receiving ws message', e);\n        }\n      };\n\n      self.socket.onclose = function() {\n        self.consoleLog('Dev server logger closed');\n        self.socketReady = false;\n      };\n\n      self.drainMessageQueue();\n    };\n  },\n\n  queueMessageSend: function(msg) {\n    this.msgQueue.push(msg);\n    this.drainMessageQueue();\n  },\n\n  drainMessageQueue: function() {\n    if (this.socketReady) {\n      var msg;\n      while (msg = this.msgQueue.shift()) {\n        try {\n          this.socket.send(JSON.stringify(msg));\n        } catch(e) {\n          if (e instanceof TypeError) {\n\n          } else {\n            this.consoleError('ws error: ' + e);\n          }\n        }\n      }\n    }\n  },\n\n  patchConsole: function() {\n    var self = this;\n    function patchConsole(consoleType) {\n      console[consoleType] = (function() {\n        var orgConsole = console[consoleType];\n        return function() {\n          orgConsole.apply(console, arguments);\n          var msg = {\n            category: 'console',\n            type: consoleType,\n            data: []\n          };\n          for (var i = 0; i < arguments.length; i++) {\n            msg.data.push(arguments[i]);\n          }\n          if (msg.data.length) {\n            self.queueMessageSend(msg);\n          }\n        };\n      })();\n    }\n\n    // https://cordova.apache.org/docs/en/latest/reference/cordova-plugin-console/#supported-methods\n    var consoleFns = ['log', 'error', 'exception', 'warn', 'info', 'debug', 'assert', 'dir', 'dirxml', 'time', 'timeEnd', 'table'];\n    for (var i in consoleFns) {\n      patchConsole(consoleFns[i]);\n    }\n  },\n\n  /**\n   * Process a build update message and display something to the friendly user.\n   */\n  buildUpdate: function(msg) {\n    var status = 'success';\n\n    if (msg.type === 'started') {\n      status = 'active';\n      this.buildingNotification(true);\n\n    } else {\n\n      if (msg.data.reloadApp) {\n        this.reloadApp();\n        return;\n      }\n\n      status = msg.data.diagnosticsHtml ? 'error' : 'success';\n\n      this.buildingNotification(false);\n\n      var diagnosticsEle = document.getElementById('ion-diagnostics');\n\n      // If we have an element but no html created yet\n      if (diagnosticsEle && !msg.data.diagnosticsHtml) {\n        diagnosticsEle.classList.add('ion-diagnostics-fade-out');\n\n        this.diagnosticsTimerId = setTimeout(function() {\n          var diagnosticsEle = document.getElementById('ion-diagnostics');\n          if (diagnosticsEle) {\n            diagnosticsEle.parentElement.removeChild(diagnosticsEle);\n          }\n        }, 100);\n\n      } else if (msg.data.diagnosticsHtml) {\n\n        // We don't have an element but we have diagnostics HTML, so create the error\n\n        if (!diagnosticsEle) {\n          diagnosticsEle = document.createElement('div');\n          diagnosticsEle.id = 'ion-diagnostics';\n          diagnosticsEle.className = 'ion-diagnostics-fade-out';\n          document.body.insertBefore(diagnosticsEle, document.body.firstChild);\n        }\n\n        // Show the last error\n        clearTimeout(this.diagnosticsTimerId);\n        this.diagnosticsTimerId = setTimeout(function() {\n          var diagnosticsEle = document.getElementById('ion-diagnostics');\n          if (diagnosticsEle) {\n            diagnosticsEle.classList.remove('ion-diagnostics-fade-out');\n          }\n        }, 24);\n\n        diagnosticsEle.innerHTML = msg.data.diagnosticsHtml\n      }\n    }\n\n    this.buildStatus(status);\n  },\n\n  buildStatus: function (status) {\n    var iconLinks = document.querySelectorAll('link[rel=\"icon\"]');\n    for (var i = 0; i < iconLinks.length; i++) {\n      iconLinks[i].parentElement.removeChild(iconLinks[i]);\n    }\n\n    var iconLink = document.createElement('link');\n    iconLink.rel = 'icon';\n    iconLink.type = 'image/png';\n    iconLink.href = this[status + 'Icon'];\n    document.head.appendChild(iconLink);\n\n    if (status === 'error') {\n      var diagnosticsEle = document.getElementById('ion-diagnostics');\n      if (diagnosticsEle) {\n        var systemInfoEle = diagnosticsEle.querySelector('#ion-diagnostics-system-info');\n        if (!systemInfoEle) {\n          systemInfoEle = document.createElement('div');\n          systemInfoEle.id = 'ion-diagnostics-system-info';\n          systemInfoEle.innerHTML = IonicDevServerConfig.systemInfo.join('\\n');\n          diagnosticsEle.querySelector('.ion-diagnostics-content').appendChild(systemInfoEle);\n        }\n      }\n    }\n  },\n\n  buildingNotification: function(showToast) {\n    clearTimeout(this.toastTimerId);\n\n    var toastEle = document.getElementById('ion-diagnostics-toast');\n\n    if (showToast) {\n      if (!toastEle) {\n        toastEle = document.createElement('div');\n        toastEle.id = 'ion-diagnostics-toast';\n        var c = []\n        c.push('<div class=\"ion-diagnostics-toast-content\">');\n        c.push(' <div class=\"ion-diagnostics-toast-message\">Building...</div>');\n        c.push(' <div class=\"ion-diagnostics-toast-spinner\">');\n        c.push('  <svg viewBox=\"0 0 64 64\"><circle transform=\"translate(32,32)\" r=\"26\"></circle></svg>');\n        c.push(' </div>');\n        c.push('</div>');\n        toastEle.innerHTML = c.join('');\n        document.body.insertBefore(toastEle, document.body.firstChild);\n      }\n\n      this.toastTimerId = setTimeout(function() {\n        var toastEle = document.getElementById('ion-diagnostics-toast');\n        if (toastEle) {\n          toastEle.classList.add('ion-diagnostics-toast-active');\n        }\n      }, 16);\n\n    } else if (!showToast && toastEle) {\n      toastEle.classList.remove('ion-diagnostics-toast-active');\n    }\n  },\n\n  toggleOptionsMenu: function() {\n    var optsEle = document.getElementById('ion-diagnostics-options');\n    this.optionsMenu(!optsEle);\n  },\n\n  optionsMenu: function(showMenu) {\n    clearTimeout(this.optionsMenuTimerId);\n\n    var optsEle = document.getElementById('ion-diagnostics-options');\n    if (showMenu) {\n\n      if (!optsEle) {\n        var c = [];\n\n        c.push('<div id=\"ion-diagnostics-backdrop\"></div>');\n        c.push('<div class=\"ion-diagnostics-sheet-wrapper\">');\n        c.push(  '<div class=\"ion-diagnostics-sheet-container\">');\n        c.push(    '<div class=\"ion-diagnostics-sheet-group\">');\n        c.push(      '<div class=\"ion-diagnostics-sheet-title\">Ionic App Debugger</div>');\n        c.push(      '<button id=\"ion-diagnostics-options-reload-app\" class=\"ion-diagnostics-sheet-button\">Reload App</button>');\n        c.push(    '</div>');\n        c.push(    '<div class=\"ion-diagnostics-sheet-group\">');\n        c.push(      '<button id=\"ion-diagnostics-options-close\" class=\"ion-diagnostics-sheet-button\">Close Menu</button>');\n        c.push(    '</div>');\n        c.push(  '</div>');\n        c.push('</div>');\n\n        optsEle = document.createElement('div');\n        optsEle.id = 'ion-diagnostics-options';\n        optsEle.innerHTML = c.join('\\n');\n        document.body.insertBefore(optsEle, document.body.firstChild);\n      }\n      this.optionsMenuTimerId = setTimeout(function() {\n        var optsEle = document.getElementById('ion-diagnostics-options');\n        optsEle.classList.add('ion-diagnostics-options-show');\n      }, 16);\n\n    } else if (!showMenu && optsEle) {\n      optsEle.classList.remove('ion-diagnostics-options-show');\n\n      this.optionsMenuTimerId = setTimeout(function() {\n        optsEle.parentElement.removeChild(optsEle);\n      }, 300);\n\n    }\n  },\n\n  bindEvents: function() {\n    var self = this;\n\n    document.addEventListener('keyup', function(ev) {\n      var key = ev.keyCode || ev.charCode;\n\n      if (key == 27) {\n        // escape key\n        self.optionsMenu(false);\n      }\n    });\n\n    document.addEventListener('keydown', function(ev) {\n      var key = ev.keyCode || ev.charCode;\n\n      if ((ev.metaKey || ev.ctrlKey) && ev.shiftKey && key == 56) {\n        // mac: command + shift + 8\n        // win: ctrl + shift + 8\n        self.toggleOptionsMenu();\n      }\n    });\n\n    document.addEventListener('click', function(ev) {\n      if (!ev.target) return;\n\n      switch (ev.target.id) {\n        case 'ion-diagnostic-close':\n          self.buildUpdate({\n            type: 'closeDiagnostics',\n            data: {\n              diagnosticsHtml: null\n            }\n          });\n          break;\n\n        case 'ion-diagnostics-options-reload-app':\n          self.reloadApp();\n          break;\n\n        case 'ion-diagnostics-backdrop':\n          self.optionsMenu(false);\n          break;\n\n        case 'ion-diagnostics-options-close':\n          self.optionsMenu(false);\n          break;\n      }\n    });\n\n    if (location.href.indexOf('devapp=true') < 0) {\n      this.enableShake();\n    }\n  },\n\n  enableShake: function() {\n    /*\n    * Author: Alex Gibson\n    * https://github.com/alexgibson/shake.js\n    * License: MIT license\n    */\n    var self = this;\n    var threshold = 15;\n    var timeout = 1000;\n\n    self.shakeTime = new Date();\n    self.shakeX = null;\n    self.shakeY = null;\n    self.shakeZ = null;\n\n    window.addEventListener('devicemotion', function(ev) {\n      var current = ev.accelerationIncludingGravity;\n      var currentTime;\n      var timeDifference;\n      var deltaX = 0;\n      var deltaY = 0;\n      var deltaZ = 0;\n\n      if (self.shakeX === null) {\n        self.shakeX = current.x;\n        self.shakeY = current.y;\n        self.shakeZ = current.z;\n        return;\n      }\n\n      deltaX = Math.abs(self.shakeX - current.x);\n      deltaY = Math.abs(self.shakeY - current.y);\n      deltaZ = Math.abs(self.shakeZ - current.z);\n\n      if (((deltaX > threshold) && (deltaY > threshold)) || ((deltaX > threshold) && (deltaZ > threshold)) || ((deltaY > threshold) && (deltaZ > threshold))) {\n        currentTime = new Date();\n        timeDifference = currentTime.getTime() - self.shakeTime.getTime();\n\n        if (timeDifference > timeout) {\n          self.optionsMenu(true);\n          self.shakeTime = new Date();\n        }\n      }\n\n      self.shakeX = current.x;\n      self.shakeY = current.y;\n      self.shakeZ = current.z;\n    });\n\n  },\n\n  escapeHtml: function (unsafe) {\n    return unsafe\n          .replace(/&/g, '&amp;')\n          .replace(/</g, '&lt;')\n          .replace(/>/g, '&gt;')\n          .replace(/\"/g, '&quot;')\n          .replace(/'/g, '&#039;');\n  },\n\n  activeIcon: 'data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAMAAAADACAMAAABlApw1AAACQFBMVEUAAAD/xET/zDX/xz7/xUL/xUL/vlD/vk//zDX/zDb/xkD/zDX/xUP/xUL/zDT/vlD/vlD/zDX/zDb/zDb/vlD/zDT/vk//v0//v07/yzf/vVH/vU//vlD/vlD/zDX/v0//vk//zDX/vVD/zDb/yzX/zDT/wE3/vlD/wE3/zDX/wkj/zDT/vVH/v07/vVH/yzb/v0z/wE3/vlD/////yzb/vVD/zDT/vk//vk3/wkn/wEv/yDr/yjr/xET/xkH/xz7/yDz/wUj/w0b/xzz/yjf/wkb/xkD/wE3/yzj/x0D/wUz/w0f/xkP//v3//vr/+/D/+u7/yj3/9d3/+Ob/+/L/9+T/8tn/xkb//Pb/+ev//vz//fj/6Lj/x17/xEr/7cj/7Lr/5bL/36H/+/T/+en/9uH/9d//7sv/wU//zUH/89z/89X/787/7sD/3Jr/2Xv/x1n/3ZL/3IT/0nv/wlL//fn/9Nn/8NP/4Kb/35z/2pX/2I7/1IT/ymD/y1r/xFn/yUL/8dD/6sD/6L7/46P/0nf/13H/0W3/02b/1GH/x1T/8cv/6bL/6q7/14H/zmf/ymf/xlH/zUX/+Oj/4qz/5Kj/5J3/4JT/4In/13b/123/w1X/z1P/zUz/xkz/8Mn/78X/7MP/67X/5q//5qX/5qD/45f/1on/3oH/znT/zG3/1mX/w07/zkn/ykn/ykb/6Kn/2nb/0V3/yk//xkn//vn/347/3Yn/2Yn/04D/0HL/z1j/5bb/01n/zDn/zD0Eb5b9AAAAM3RSTlMAC+EVHwjhSOvzT0cmApta3NnRuXd2buuSkvTx18fHvbinnGxY9vTo0VwTvainmHD57Z6HmSHnAAANwklEQVR42t2dZ1tTSRTHQwfdqmvb3nufQFzWXbe4iy2VJFKSQEIJoSUQkB6QDiJNei8K0gUEBHT9ahsQdSa5c5PMTNjc/T8PvIT55d4zc1rOiDwo5OhHZ44fi4y6cG5PP+//OtBvTv35p/PHqfMH+vvvS5f+durX57r6ywv9caC//rryl1M/OfX773s/zt97in2qy3u/vv/htc/fO30y4ksRuYKOnDl24cLFixcvOHXYALFxezp7VvL56YggktW/fPSDKLFY/J8D7OnTbyJe9vXD/y7YufpAAXAq/KQvjyHsRKRYHFgAZyWn3gjzdv0fOz/9wAOQSMJf9Wr5ocfF4sAEkEjeD/W8/i+ixIELIHnL00MIe0UsDmSA6OgfeS0h5F1xoANI3gnhef1fFwc+gOQ1rCG8FCwWAoDks5dw648RBoAknJMg5PUYoQBIXuOwg7B3Y4QDEP2O+170SoyQAKI/dDu/YoQFEO1yooVGCQ3gLXQzPR4jNIDo9xH/M0Z4APBLFBQsRIDwFyHOiRghAkS/8fwBRAoT4NSzR/BRjDABok8e5B+ChQoQ/jRXcTRGqADREfsAHwgX4Ot9E/5EuABv7pnxkRjGAFevrTdbN+vbanO1mkSFXBGffWNS11vQ3uQYM1xhCxB9ZM8NZQhwaWW9eat3PB1wSn7DWLDT2nWFIcC3ToDXmQGMrm1NXwcepNEVNLX+xArgNWcgFsMGYLS5IE8OvFKxuWzHxgYgOkR0lAXA+bXCVAXwQckp5R0GFgARohP0ADXN9VrgszKMDV30AG+IXqEFqGlsiwdEUtS1d9ECfCj6ihKgcSoRECtt8qGNDuA90etUAC1t6YBK8Z10AG+LIikAVvoyAJ0y7Yt0AJ+JosgBrDkyQCdtFa0NvCmKIQVYuiUHlMqoerELXe7cLs8vr5yP8w0gmhRA2XhdCvgkVzk9oOne+t7eaV1trioxiWMXanq+jVaZtOqE4uLiZHVmz/xhAChnkgFGUpkmpXeraf3Btee68mCsq+Nh/WSmDIGueHaQ2UrlL/ikaUa93wFGUjEf/4ZivK9xGedOjzWVTSQWgwPVGp4CGHa0rq+WPc6/AAvcLptMkVLg8BQPOAomE/dtP6HqwJXYdj9IEmbj/AigtHIeXeqc+hbvApoqU4kcgN4DX6jhNpe/V+k/AOWAjMtmJwprfIjI2lM0VU+90Sott5vR6S8AywzH+tMm2n0NKTsM+wCLPYBbKUX+AbD0ceyHNwsvkcbEOwpc0FDpFwAlx/o1/aPEQb3BBHAy+wVgZgO4KrWZIitRlY0FSOz0A8BAAnBR+tYKTVqlgcfVrmAPsKoGLhpvpssLVQC8epgDtLgZ3K1RusSWIZ8HYJI1wKjr+Zvcv/InJYCJB8DMGECZ6np2FSp/owUo4wFIYQwwI3Ux30ElfW60gAfgEVuAoWSX9Q+xSO5u86SO8pkCLKlc188kO92hwCdd7CwBzt2Sou9/I5v0epcOC5A9zBKgMRn11++xqg9UJuEATCxdiaUcNGrsZ1bgsJlxAb+eJUCfDD2/lOwqNA3cViCtYBkPPE5HveduliUm7qPAyDIi+6cNseD4FqY1slYjcFfdMEuAITSDNeB9ke+XB+vrY9c8FPlaTVK3z7+aZVbiyRTq/1u8Ahhpr9ellGhVKm2OWdf70MFTpVysRKOC2xXDTPNC6APIWPCizLpWNq5RQ4afpL6d29uBL7Pqy1Uvlm/qfJaZq67KYgDw5BaA1a/0BPCrNUXOEfjL1DcejuHqxJerG/J1WpW2rsyuz3qeWuxJ0TMAWIhHdqAlD3XiGmvJBsBIll1hwNaJL2cVFRXtL/4A4Gx5kqySHuDcXfQI5i90/9qcwp/zLWkweFnoHt6LFXTD1ACPkTAmT8kLsNyfBjxIVt/qFYD+0b5BzNECXLyHpA+tvJX63VTghXI7PAMM27UHHpGEEqAbWdMEb6W+6bqXdbFtDwDVczrps1dOTwnQokAsgA9g0Otqq3yWDyCusi7hxQa8TQfwzyaAlGPhARj0oV6pruAByNIiyZUiKoDuPAAdR3d5eiWafap2y9t5XqF8GRzX6KkAHqvhZN8aHmAk28f6cAcewAZ/FlI7DcA/91ATxgLU1AIfldOKN2IdEpllUQBYYDdCtonvVukHPmv6ChagshhOD1VTACzBG6NiBAvQkuY7gMyOBSjSwEneTgqAFimcyMX2C50fBwRSdWEPsjoAqVJCCoCagLQPC2CVAhLlYwEq4H2olBzgyR34kQ/hAM7nEHaqYBueOtVwlrqIGMAyAf87Cw7AukEGIMvH+kLwWaapJgZYSkc2URzAOCBUtgEH8AhAmicGaAGQ7uAAduWkAAl2HEA+YsXEAKtIMgIH0EfeNaTDAdilsLETAwzAm9ACDuAmIFa2AQOgV8P+HDEAHE3KlzAAoxpyAEUTBqAoE85yEQPAjoTKggGwqskBistwALlwsYwYAI7GbigxAHdl5ABSHQYgCz6LtcQA8NudhwOYAhQy4wDghGkmMQDsyk3hACZoALSL3ABxpfARSgwAd4XexwAob1D1jbZiAExwIEUMkAifYxiAlet0jZcYAPgkUxADwBmJuxiAZRVd5zEGoBzewYkB5EIH8P8rpPHvK6TxwohrcqiM2IYBKIONmMk22obbRscDeBuFD7JUHEBqAB9keXBlAwdwJ4kCwM+uRBvszOEACtMAsWQmnDNnhvPxTNzpxG4MwEg8OYC8AQeQzcSd3oQzuy24gIbCl1AtYgCqFXBAwyakvIcBoDACaR0upJwDTELKBQDpLg6gRU0c1LfjAMrZBPVLCjggwAGcJ36HMhZxAEY2aRXLTfi/YRNbhYQxWVIpNrFVAnceVJOnFu/DW94CDqCG0J9LtOEA9HKkEZ88uTsAf14z2OTuFllytxSb3J1llNwVL0Ark+ZhAEgduvhWHACaWZylAFiCg8r0UWyBYyiZYA+dxRc44HcymabAYWmDjWAAX2Kq9x2gDl9ishfDjgRFiQk1Amkevsi37PNWqnHgi3xGKWwCRTRl1pYE+B0awZdZ13z0iBQN+DJrdSaAtE1X6IbDFdkMT6F70Lfv1FfwFLorkpH+USqAczMA0g2+sQyFap9qMzwASMuRcZgKQLwAL0u9igdwEsi9X7+Bp1diLhF1hKgA0GYJkMfbbmO9DbxSWgXfYAzUD8rupG14GkAO/0behqdmr/aizAbehqf5DOS0zqJuOUMcnVT+2Sqj96Wew2AHf8tZDxKyzdE3/d1H/uAqf9Pf1cHr/AgaZ+slL8AcUvCpraZvu0T7XvMsHtoul/s1Mqz/fLu09Qr/eJ7Lj1B3g0HfqGUKWcOAx/lC6/05aVy+T5rK5PA4IKkS2Yxz9Sxaj1cTAKTrj71oPR5s0yYi/l2xQpXaPuZ5wpMtF9luy5n0TltS3b/94Ln5u6nfmJKjytBoMjK1Zl1ZwwNvRlRlmdAuWT2b9vtV5I3YsHrdfn9t3dHR4Wh94PWMLfTrHMXlcWwAnqCPQLXrryFh1SVoh2w1I4CLCwqA7kT+AYg1uo42YAVwweV8uusfgHL0v9RmsQMYVaHOcKE/AOxyNGKYZ/lFuHsbLl/kYw8wj7qCstI4lgBKl1kk8c2sAfTZAFHJMNuhAK5J9Mw1tgC2EheXe471YAyrSw5atcYMgGP90jL2o0nuABeCFnYAVXvrR3cg9gDdN10za41KRgDz2a4xj94f43l2XTMn8ns1LAAM9njgagD+GZA0pHYNzmdW6AEWC1yd74QKf014KnQb7DG1Swugn3YvXcb5bUSVe4el1qqkAmjIBa4yZsX6DUB53z3HdmeXHMBmcs8l1Q77c8qZ5RZwU0lhDSFAu5ljHIbNv3PmujkIklMbSQCaHqk51q/396C87vvAXeltTb4CdPRwNczW6mP9DOAk6JNy1YumBn0B2DFy9vsabbH+Bzin3OIsKClubq57B9D1MCWRs+hkGo49DICflavp3En/jCnriicAg31alYDJ+e7vn4cyLHIN07EuTc7UbTnwALaHxuwETO5R5fQfDgnAqe5bMoBhSFLntBU0juzPitxf9tNpka1NBdO5Chm2ZFCrh8qshzGuszCdt4YqV5lTp+vL+gsKCsrqe3TmbAV/ySA/9idCgE9IB6Y+zksGjFScu+NcPhkAzczdTQ1gotumRYqRtcEUQ4N32xIBtdR181RDg49RjW0ezEsAVJLlztKNbaYdnF1TOLEByJVb3kU5OJt+dPloIelTSDKX26hHl7MYHr88OJVO0HJZO+tgMDyezfj+leYyH7s9tKU7rUzG9zO7QGGksT7H65716e0qA6MLFBheYXF1ZGhmUu6x1dJcb3eMMbvCgvElIjXLDmtfbQbOv4ifLG3v6DIwvUSE/TUuNdeWmwc37+icJT5NukIhV6RrMnPMqXv3uHSNPWB9jcv/4SIdwV9lJPzLpAR/nZfgL1QT/pV2gr9UUPjXOgr+Yk3hX20q/MtlBX+9r/AvWBb+FdeCv2Rc+Ne8C+eifXj9qEKPCQHg7VARVl++G/gA73wp4lHYmUAHOB0m4teRyEAGeOuIyKNCjwcuwPuhIm/0cXBgAoS/KvJSYSciAw/g1BthIu8V9F1wYAGEnwwS+aaXj34QFSgAn34T8bKIQEFHzhz7zwEkn5+OCBKRK+ToR2eOH4uMOnyAT0+9/d7pkxEhIn79CxIosts8XZ0fAAAAAElFTkSuQmCC',\n\n  errorIcon: 'data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAMAAAADACAMAAABlApw1AAABa1BMVEUAAAD/VEP/VEP/VEP/VEL/VU//VVD/Uzb/UzX/VU//VEL/UzX/VEP/VU//UzT/VU//UzX/VU//UzX/VEP/VVD/VU//Uzb/UzT/VD3/VVD/UzT/VU//Uzb/VVD/UzX/VU//UzX/////VU//UzX/Uzv/VEv/VEb/VEP/Uz3/Uzj/VED/U0j/VDj//f3/4d7/5uT/+vn/9fT/8fD/7Or/2dX/Wkv/x7//vLT/0s7/Vz7/6ef/8vH/zcj/gXD/m5H/XUX/+Pf/tqz/oZv/kof/X1L/WU//7u3/XFH/3tr/z8r/p6H/Z17/YVj/wr7/wLn/rKT/sar/eW7/eWb/i37/WUL/dWL/3Nj/uLL/hXv/Wkf/yMP/bGL/ZFr/1dH/sKX/lI7/jIT/koH/gXj/cFz/9/f/oJX/h3b/fHT/cWj/aVr/YEr/pZj/nJb/lYn/Y03/raD/iXr/dGv/qZ3/mYn/bFf/Z1L/WkD/tK//iIL0KD5WAAAAIXRSTlMACBRaH/Pi8+KZT+sM69y7ukdHJtzV1JuRd3ZubsfHqKfKjLozAAAMxklEQVR42u2d53/TRhjHnUAISdgFGkaHJAtSGslTlvfee8dJnL13Agnw59fQAHeyTpbvTqnV9veGF+Rj31e68Sw/Zxmg8SevZx89n5n8429ZVTQH6d0PvVXqTb/+/JPn/+yJV4q9//TZw6lXj8ct+Bqbnn3+xx/z8/O9kd84AM9+Ue+fZ7PTY1jDf/L7JMdx/zxATxO/PR529PdeP+iNflQAerrz6t4wc+flDMeNFgDL3n+heybdustxowfQewu3dA3/9iOOG00Aln14W8fjn+RGF4CdGPQSxn7muFEGYNkpzZUw/is36gDsL+Ma0/8BN/oA7FPkQvjpLmcGAPbOT8jxmwMAQTD+gDMLAPt0XGX/+ZUzDwD7S/9e9DNnJgB2qu/84swFwN5SbKCTZgOYgDfTR5zZANiH8AQyHwA4icbumhHgzo+d6CVnRgD2xXf/ccacAPe/eZmvOXMCsK+uAR6YFeDpdfyEMysA+3e05XfzAvz2dQ+dNC/AxJeddJqjDPB2MVk/D1dkj7dtSzsDab+4siRXdxq1A0miDMBOQ2YoOcC7xdP6STXoYlQVWFkI7xUzNAGmgD2IHOB06ySWZQbIVg7XijwRALwPjXN0AA7rYU+A0SV7Lh8q0gFgxy1PqABsJUpOZghFl473JRoAjy0vyQGa9dU2M7RsC5sZcoAXlp9JAXwh2c9gKV1qZEgBpiyPCAFC5TSDLcfSSZcM4KHlORFAS3YxRPLvkwE8s8wQAETObAyZxE6GDOC+ZRIf4NwtMGRyL5OugQkLhwtwGAswhLItA7vQ/ufwcXi9xg4HwOICzIWy2o/fKa545Fi1slqNyR6v6BJU/qT2fRtdzrcd23a7PeoQqxc3AeA7ijIICXZbsHpSSy4uLr5588UC7f0rScmLRGVJtEMYa98OsmIl8OM/BMdCwXCAq7KAsBKcufchCWVOS7X4ksvOXMvDXwPU2sqp1WGNBahn1UefDoYPBvkDB+FrBsfytSnxuX8n3k4YCTB3nlY9lNyVlj6HZjnvDjBM9doW2nSpPYt14wB8H+xqtn4w0RzCI1sP9nagrwDLbfU9YN8ogI9HgppJ0BjWpbz42zuTqoy6giljAD6+Vxm/d8OH6xPX0iin4bMhAD6V8dt2TrGdeinPoJQzAsB61D//y3WCqEQB7YK69g0A+BDt+5qTCElYZU/D1F6jD7Dr6HvPdbK40BqDVpU6QKvP7Y2dkgW2+LgGwBJtgFPlfI3uRAgjc1JeAyBHGcDnUZ41Gz6r1cA3EKQLYFUeYK7dOSsxQFgDQKYLEIoqxh+iEdxtaISO4lQBIiJi/GQA+2l00GWTJoA1JsC2W4hOeD1TRgJkixQBlBPIsUErP7COdEzzNE2JiBv2GneoJTiKXpTDX6AJoDDhYnP0MjSbTnXPeo2mP7Dlgq3nCM0Uk/pRsEDVI5OhF+BvUc2RJT8x/SoVaQKE4Lf8waofQMokpQEAfDIv9D3/Ik2n3ifD9n9TF8BVoyIH3W1RzLpz5WriAA3Ap9ZFOOC7luRpAsAvwFbXkWbdep+zOQRgSTr83uoFOs1aCP9A8OeXv0XmisspCgC+GANqZ25gmrURdNpV9pXAygkyzcoXN+Nlt9guxTuFFP8NoBosUACo+yEb99CqDdBsuO3I7F52TULniVOZTCYFRqePBeEzOcD8GWRhbWgnut/Wg4J2QH1T0pknzsS/LLgkMcAlFHjyzGkCHO44mAESKkldAIWFrzZjjRRgfgOygRqamfpLD6ND3v3BAMlOG7CISAAi0JiCmpn6WlZnXqwxAKC49z36vVIgBGilwaDxhhbAru5sayChCbBe2v4x4zpkAPNhaAU2NQB2XUOkV9c0AFKQ6VvNkADAM0g406iVqA+VsHQ2NN5AHNyHswUigC1wV0lvoQGuskPmhy/QAF3wWQibJADwHhREF3s0PUNnWJPoRVyGPLMUAcDHGHiOhtEAO8zQ+oQGWLeDj61IABDJgjPoCgnQcgwPYO8gATI2cMHvEwC0BNAMQtYLvcthlRlkkAdZiQG0jg8wnwBX03skwDleyUEcCbAmgIsAH8C3Cr7yEArgnRuzUiWDAlgOgPZXBhvgYxD0ZJoogHM7HoAQR9pCbvCLi9gAhy4wXI8sOcsxmMpKKADIi73ABmgxgFZRAJcBXIDtDgIADrt/xgbYZQCdoADe41cNlVEAm+BnHmMDfAAnbB0F4GWwleURAF3wrVaxAUBv0nmIADi14QOkawiAjAhGubABQENC/IgAaDjwAexxBEAKfK05bAAP6Aj6EABnAj6AIKMASqDdhw0A7o8eFIDMECiHAvgEvnxsADAgIaMAgiQAbkkdgK2AJxk2ALg8YwgAn5sEQCwiAMCDwIUN4ALPsTl1gEiWBMBW0AHgxAYAo7pnCIBDkazyGAFw/D/ADU2hZT1T6L+7iLOGb6PZlI5t1I8N4NVzkJVJALzEBxm5KbEqEACUjTUlZPAxoAA2SIy5vB5jzkvFnHZFEABXfnwA56ax5nQYNBxbKIdmhWATSiEAik4qDs05A2gDAKC0CIQSyqXcg+JH2AB1BtAZCqDlwHbq11EAx3Sc+lPwRXqQYRXsOWSTUAALDKAafmArB37bRxRAAjewVUEGtsBn4i/ihxah6HodBdDEtOdcXRRAAXz1wQx+cBeKqxwhg7sneMu4ggzuJgTwzwjC63UBAFhCAjTdWKZ0EgRAL4EEAcChDXzjp8gERyiKsQISyARHCpyT0X2SFJMMLoIP6BRTZXiAEjrF1AF3BS9JigleBB40QMQ79BZ6gAZYgJZAhgCAgw4p1yU6zbplG9YKQqdZiyJ0jJEluoPwPoROdHeG+039mkaiey0KOZ1kpQZQ/nTFigaYSziGy82gAaCSo4UMEQBXB4fl2NUAeJcI6I/p8hoAe2k4R0kGEFmC6p00y23O/XpLPdCNMZSHQHaZDADeh5h0SLPgqa7LrBM3NQueLqDtoJIiLTnbEiEfVhNg7jQmDHaDD7RLzqrQZrXHkgJYY1Cp0rl20V+zk9VGsCUGFP3t2eASZPKyS7ju1dMcUHYp7diQCIILqPhDAMiwucGTA0TK0Ed+GNhf6HDH7VAbvUPMdwf2F1qHtjJvgUbp8S5kqbW3dJQeh+R2Ogr/VF0sNaTBHZ66Xoj5mKcBABcuMjGfnuLvt7UdOegWbT2J7ZycD0l6WlSl8gyolQKd8nv4J5T2hu7y+zfJg1brILmou8fWZhr6pmOWDoDiN3zipVFNwoqwY+TtUgKYrzsZeCcyCGBB0dqA3o+AFLGrM2MAwgL8nFL0AE5F2BhOGAHQgY3B9AXNH8Jt2GF3JEQf4MIFHxt5liaAT9GLxN+iDVBQBJdWknSbAiiD6OIWXYCuW2Fy12g3xmgICAJyAJXxC3GeNoB1VWnWt+gBFJSRMU+Kfm+VSE4ZWQvNUQK4yCofTsGI7jaXSo8xsNGkAtBRfrBjz5gGSSGH0jk/WiQHyISVH7u9ZlSHp8R2n394SQpQuPb4oBPAKACVCsv2uY8IYLM/JrmQMq5JmC/WH2NbvcQH6Ob7Y0mepJFdziL9BIw70cQEWFcJCQe7xvaZUyOIlkI4ADXZoTL+Am8sgCoB45JrwwLsV9Xi2Z4CbzSANXIkqOWL5N1hAPYWVMPxC13eeACr7ySqGvH3hk/1AWROltKqYaN8kr8JACvqR3tRm9xYHATAdz6JDkTMN8XfCEBPW4iMkhAV5ZMDNEA3IYvbAiLmu8fzNwbQW8p2BsEgBNxyOHS1uPh28du4v3aLrIVj3rSArvso8DcBANQXuDRTMAHRW/pUOdoJh8PxfLWcE9PaKYM4z98oQE9bnihDSXbvHo8LQNJzN2xjqMiVlwha1t7FB7BeymmGWIFSjaRpMGHb5l3SeSR4E2Rtm0kbZ/s2gnaSutHjJGHjbPLW5Ycbnm3Mp58Ld4lbl9NoHn+4K7tw5n7igELzeDrt+yP1oyEL59qVvSSV9v20LlB4dxVa1V32JH5qFHhKFyhQvMKieRU6WgoMLLXMVToHErUrLChfItKUDs7fe0TUovUvVdaXkxLVS0ToX+PSXJRanfDq1xSfy+kMOF020Z0rVY8btaTEG3CNyz2zX6Rj+quMzH+ZlOWuWQHu/FsuVDP9lXamv1TQ/Nc6mv5iTfNfbWr+y2VNf72v+S9YNv8V16a/ZNz817yb/6L93mb63AwAz25bkBr/dfQBfhm3aGhsdtQBpsYs2pqeGWWAiWnLQN1+NLoAD29b9OjW3dEEuHPLolNjL2dGD+D+izGLft17/WC0AO68umcZUk9+nxwVgInfHltwNDY9+/yfB3g2Oz1mwdf4k9ezj57PTN48wMT9Zw+nXj0et2jrL8ZcYnwUxGUrAAAAAElFTkSuQmCC',\n\n  successIcon: 'data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAMAAAADACAMAAABlApw1AAACWFBMVEUAAABPpf9Ck/80f/9Qpv9Bkf9Qpf81gP81f/9Qpf81gP81f/9Ppf9Qp/80f/81gP9DlP9Qpv80f/82gf81f/9Qpv81gP81f/9Rp/9Nov83g/9Ppv82gv9Nov9Qpv81f/82gP9Ppf9Ppf81gP9Rp/9Qpv9Qpv9Ppf9Qpv81f/82gf9Oov83gv9Nof9Rp/9Nof80f/9Oov83gv9PpP9Nov83g/82gP82gP////9Qpv83g/9PpP86hv9Oo/81f/84hf82gf81gP88if9JnP9Nof9Imv89i/9GmP9Knf9Dlf80fv86iP9Ckf9MoP9Lnv9Ln/9Flv9Rp/9Hmf8+jP8+jf8/jv9BkP9Aj/9Ck/9Bkf/8/v/+/v/u9v/5/P/1+v/o8v/r9P/w9v/m8f/3+//y+P/c6/8+iP9HlP/7/P/f7f/j7//a6f9Rnv+/3P+31f+nzv+izf91tP9nrP9Ci//h7v/U5//H3//A2f9Hjv/K3//D3v+Vv/9dqf9spv9bpv9XpP9Tov9koP9Lmv9Kl/+62f+81v+eyv+lyP+Uwv+Etf96r//Y6v/X6P+00/+uzf+ayf99uP+It/9Pof9Kkf9Dj//Q5f/J4v+y1/+Kv/+Evv9xqv9Yp/9UmP/N5P/M4f/G3f+oy/+XyP+gxf+Cuv95tv+Bsv9gq/9hp/9mpf9cm//T5f+w1P+iyv+Txf+dw/+Nwv+Pvf9xsf9qsP91rv92q/9hnv9NlP/P4/+r0/9urv9aof9YnP9QmP/5+//e6/+22P+u0f+x0P+Zw/+HvP+Luf99s/9+u/9Wof9vgxRDAAAAOHRSTlMASh/dCwni6+MUFPLdm5taB+t2bkfYTwz09PTx1tHHx7y4qKd3b1xXJibRlJTrv7u4kJB2bEW4tzpe9IwAAA2/SURBVHja5Z33Q1NXFMeDVK3VDrW11Wp3a/cej4ZSB1pqJdXSVptFJishhABhJATCDCB77733VJYiiP5bDYjlvuTdl5d776N57fdnCO/Du+Pc7zk5V+RDz3xz/OM3Th889eO2Qp/oF7cubOvytqLc+t2tP9y6cePGn2799dfVbd28efPnx7q2pZ8e6ze3Lm3r4rYitiRx61e3It06+cmRM5999HXQIRG69p/48sPvt+R+9D0HiAx/rB/OfBH0lAhFL759yv3s/z6AWyffChL5qZePB7sfPFAA3Dryrj+vYf+xg+fOBRbAD+KjX+0XcdTzT587F3gAYvGBsyIu2vfduXOBCSAWv7mPw7//8PnABRC/+p6v0f/c+fOBDCAWP/sC67b1+vlABwh57RDL8A8+H/gAIUegE+GDp88LASDkwPuw5w8TBgCE4JngMKEAhBxhmAcvvB4mHICQ17zXoufChAQQ8qzX/hUmLIAQjx1t32GhAbxKX0w/DxMaQMib9AEkPICQs0AE9LQQAQ7sHnGOhQkRIOSdf86PB4UJcPTJKzh+RZgAIe/uAAQLFeDIjn9yRagA1x+7LW8LF+Ct7TX0sHABXtmaxieukAW4cTV2ZWhzaqw8scOQoJKr5Rp9fZYpydW7uJSX9xNZgO0x9BxBgD9iV7IfdFlVFKPU9aOu6UdGkgBbQWkwMYCF+eo1A+VDCQWuxUeXSAG85D6IXSEDsDA0la6mOEnRUDT9iAzA9UOiF4kADFcXyik/pLWOW/JIAASJjuED1A0V36L8VuposhEf4B3Rc7gAUS3lGgpJ8sReIy7As6I3MAFaTCoKWcqsphw8gE9Fp7EAnOXRFJaiLXgAL4kOYgBk9KRSeIrvM+IBHBUdRgcoHYmj8FQ2iDsHXhFdQQWorVBTmEod3F2FJJbe8e7x5ulw/wCuowJcaDFIKRbJ5HfdEdBaUldX0pop8bZexfDT8sV/ltEBc5lOqVAotLrUOzORewBw2aalIJIqNNauB9krsTu6Fhv7U36+0bIxlpWqkIKMTU82shyzWrr7+8rGAd4BFkwyilGr8sye2XxYOJ23WNmpUlA7Sry4AzBd5hkvPQznF8DJvPEqVFbbsq/zwJIr6zGDcnAnlKhSeUcak3wCXChl3Lp0aWNObgeawaI0NUUl7cRCfUyfpmjmDyDmgYIp1u8sqfPjRNZrTRh8HI0OljGHGTN8AaTYGBZ/Zeemv0dKS942gOQOxawGMT8AKRMM62FmyQ3UM/E0LApXNPMCEMPw/Am2BeRDfYSZgqmBFwDbKuUp0xCGK+HQQwFUMzwAPFB6RZPVGTi2SrIMHmpPkgeY03mN/iE8X6iJJRi5Qxxg3mvCVSzgGVsXKym4skgD1Bo890tbHaYzFzHGAtBAGCCm0HOvKYm6gAtQtHcAoTaZx/SdI+CNulgA7pEFyNZ6PH82CXO3isU6qiQKkGHwfH4i7rRFDjddqkgChFbQB5C6hYy9brwHBdC3kgRooQ8gZT+p/MCGFLYNmEmGEhkj9A+3EUtw5LTDXGw7SYCJOPr+RTBD0ydnfgGTJM8Dw3T7rSODIMBF5q2gkeiJ7D5tBmucRHNkuaOUtxJbSQJk099yNfck3838lZX8WB9JvlyzzOv/30ryUB9TTo//UzgBtG2OmawjBr3ekNZQkLSxxJKljOyNp+8x47kkfCHIC0h1ckizLk1kanTAxJfqom8nWeBpVvv4LkK02fLEmWsdEBMAiKmgQNl85on/3LTKGYyLOF19Ux4sTxyRk1xZUBZfllhUZa/5xxu90zBAAMCpoZ1gan0A1G2OrEKze/qpPGieWFJTk5tbE7nrTv/QLY1rxgf4sYe+BbMnuv8csrJ7viPJeRzzxDlFMooqyMEGaKPZiOkxrAC1LiXlQ3FJRk4AA9tRkioZG6CfZh+WsmbqlwspDrpt8Q2QU7VjV5hxAVJoz9TJmqnPNnDMi1X5AGhNLngyENMGMAHmVbQZwAYwyznbqm5iA4hsTtTuLsAPMQEeUIBGUlgAZv3IV+qaWABqaHbvnVwcAPoIkvaEwgGGEig/pO5lGUKV4C6iH8ACGNaBZt8wHKDN4G9+GA7gAMeitAoLoJ82heHFHnXplJ+6ZYRP4gIwujOL0QHoYYRiCg5go/zWKBygWQHaQ60YADQvQtUGBXAq/QdQVEEBcsH5pJzBAJiXgmEQtF4oKhOpzMAI3choY6gZAwCcAtIJKECplEJRJRRgPA6cBCHIAKHF4CvPhgFEpSFWqhhhABYd6FJfRwaI6QR9jhQYQOkqGkBcJTQWAvcyjR0ZICOatojCADIpROkjYACgYSebRgaYpwAVwwCW1agAyj4YAC3x0YwMMEcBKoEBTCBXDckKYAB94DLUjQxQDS5CTggAMIIQxhAEwK4D4zlkgAnQ7c6AACwkoAPIFyEAufGgy4UMAAYS+hgIwKYOHUBRBAEQ11O7akcGKAQNURhAD0bhnOweBKAmEaysQwYAR3c6DKCcwlADDKARjDmQAUBD4j4MoBOrbFHCDBCeBO5kyABgVWgXBCBqBKtuNAcCAJruKmQAFbiPhTID1BlwABLsEABwJ5MjA4Cubg8EoFaPA6CxQAC6wQP0/xdgD4bQIK9DKEHok5jTMmrFATBwWUajiWxkhTAAEw5AO2wjGwWTQsgAoNmTCQMolmIAFPAbStwHXzYMoETJRzDXTiSY6wFnEiycXtGgA6iT+Q2nQWtaOg870NzGWIQkEIBWOXigIXOk7IcAYEwCWSLsSEkrx6wkc6jvgQHMI59otL0wgG4yh/pa8EWmQwAwxlCqBAbQSAFKRje2MsG/FgMDqFZQSJImQY2tNHAfs6Nbi13gkueEAdTdRQNQ5cAA7KDVZEW3Fum+ig1q7lbLkACSoOZuUxz4YyHoAE4psGSkQwHq0pC83VwowD3wP9KEk+CIB/9gLTTBka1FmAEb0ASHGPyz2hmcFBMYTCiq4SmmYv8BEuEppocKMJBoxUnylVDgGIID1Pq9lCY44ACNUnAK5GKlWZXgGGqDp1nn/TQY5cnwNGtOKgXoIV6iG3R94mwsie5S/75TP8mS6J7Ugm8KL9H9o41WZ8ICEFWi8yM3032JBcAKrkGNOXi1Ek7wsXRzcAA3gZrzMaA7j6VWIllOD4TwADJoKfhC1nKb0miOiZlJ1sYYjeAL0FvwAOibMaVqYS14GqrndAhI/o2tXmgmgbZbi3EBhmmBjom9t8pChdT3MXiJveRsFHwB6j7cii16QEep59iL/q6WGmTsy/+Gj6K/ZNoJNasVHYC57jU9xUfZZawrIQ4aPUSbjT7a80Teo/3CZDg+QIqJ9pHVPgtf8123dAyvQabUjz3y2SCpWUezjQZIlB7PKWl1PsscSo9nyw0qLW3hlOsLevN9d3hytNO3C8zaaabCRariMqfi7+z1cuutu6kaTWq8ocNUNJvPpUVVuJkClTZApvye/hXK1VLO5fexK0uDg0vGfM49tvrk9P0unAyAx3f49G18NQlrpR+M6h14ALtyyin6SsQPgKSRHvA1kfsSkId31cMPwDh97coSkwOopfsO2n4+AKrowaCc6De6+1fpH95CHmAmmh5xm8NJAsQU0l+vxkkawB5P0ZTWSrYpwIKHiX53mCyAw8OZUSaTbozhWZmoHyYJkOPx/NKiSNIAoZ7OyV0nOQC7pzOWVUO+t0pKpmeSuuUyIYAZvadtbeeju02bxjNB1J9CAiCiKprynAD8NEjK9vQdFOu1+ABGl9LLc+Grw5N3QtK0jAvg2MkI03YAvgBCvSssb5VGYQH0efsAjTX8NQmL6fL22IqX0QEcRd5eUlYOn23aUiooL42U1CEC9LYzVNE5+O0zl8FAoC1sQQFYNDGYkVY7343yMrqYki33s/0FsNxhyvBn2X/lGyA0ZZ3JvNKUz/oDMD3KaMc3OiT8A4RertYyOv4dUyvcAIxNzH2RpeZcCf8Abl2YY3Zxtanlm7G+AC71rcUrIZ5vjYR3gB0Nd0CsN2286cEyHGBpo1yvlMI8X4lkzwDcixEsOy+T6tLKbbNtsVu6ufX0sdeubXWLXHSN1svjZLCUR6IdSLPuRbvOEtZkgEyt7yhcG1t3TU251sfWCjr0clbTV1kpiUAECENtmDqcrqUISVE/HRGBCIDTc3cqgSKiaLMEo2VtMEbT4Lb7KgpbusRFrKbBH2K1bZ5NV1JYimtvwmvbjNs4+4+SzlUKXfXjuZiNs/Fbl9eWoL4Face4A7t1OYnm8QuzKP2/1YkbDgLN48m0788YWvez2qMsaTqXSPt+YhcotLVw7+Efv1bluEjoAgWCV1hcbWtZz1L7TNN3jPU58ohdYUH4EpG6/OXSifR4GSS+0GSZeweNeUQvESF/jUtdbP5Q6VSxyZqm16jkcrVcpYm/5W7y5KqyGPPyebjG5WWhX6Qj+KuMhH+ZlHCv8zrwX7lQTfBX2gn+UkHhX+sozIs13/svXW0q/MtlBX+9r/AvWBb+FdeCv2Rc+Ne8C/+iffdieloIAC/tE0H1zOuBD/DaIRGLXvg40AE+ekHErhOHAxng1bMin9r3beACvLlPxEXPPx2YAAfOijhq/7GDgQdw9Kv9Iu56+XhwYAEcefcpkZ968e1TgQJw8q0gEYr2n/jyw38f4MwXQU+J0PXMN8c/fuP0wVN7D3DykzOfffR10CERu/4GltYY/Gjw6lkAAAAASUVORK5CYII='\n\n};\n\nIonicDevServer.start();\n"
  },
  {
    "path": "bin/ionic-app-scripts.js",
    "content": "#!/usr/bin/env node\n\nif (process.argv.length > 2) {\n\n  if (process.env.npm_config_argv && process.env.npm_config_argv.length > 0 && process.env.npm_config_argv !== 'undefined') {\n    try {\n      var npmRunArgs = JSON.parse(process.env.npm_config_argv);\n      if (npmRunArgs && npmRunArgs.original && npmRunArgs.original.length > 2) {\n        // add flags from original \"npm run\" command\n        for (var i = 2; i < npmRunArgs.original.length; i++) {\n          process.argv.push(npmRunArgs.original[i]);\n        }\n      }\n    } catch (e) {\n      console.log(e)\n    }\n  }\n\n  require('../dist/index').run(process.argv[2]);\n\n} else {\n  console.error('Missing ionic app script task name');\n}\n"
  },
  {
    "path": "circle.yml",
    "content": "machine:\n  node:\n    version: 6.9.5\n  post:\n    - npm install -g npm@3.x.x\ntest:\n  override:\n    - nvm use 6 && npm test\n"
  },
  {
    "path": "config/cleancss.config.js",
    "content": "\n// https://www.npmjs.com/package/clean-css\n\nmodule.exports = {\n  /**\n   * sourceFileName: the file name of the src css file\n   */\n  sourceFileName: process.env.IONIC_OUTPUT_CSS_FILE_NAME,\n\n  /**\n   * destFileName: the file name for the generated minified file\n   */\n  destFileName: process.env.IONIC_OUTPUT_CSS_FILE_NAME\n\n};\n"
  },
  {
    "path": "config/copy.config.js",
    "content": "// this is a custom dictionary to make it easy to extend/override\n// provide a name for an entry, it can be anything such as 'copyAssets' or 'copyFonts'\n// then provide an object with a `src` array of globs and a `dest` string\nmodule.exports = {\n  copyAssets: {\n    src: ['{{SRC}}/assets/**/*'],\n    dest: '{{WWW}}/assets'\n  },\n  copyIndexContent: {\n    src: ['{{SRC}}/index.html', '{{SRC}}/manifest.json', '{{SRC}}/service-worker.js'],\n    dest: '{{WWW}}'\n  },\n  copyFonts: {\n    src: ['{{ROOT}}/node_modules/ionicons/dist/fonts/**/*', '{{ROOT}}/node_modules/ionic-angular/fonts/**/*'],\n    dest: '{{WWW}}/assets/fonts'\n  },\n  copyPolyfills: {\n    src: [`{{ROOT}}/node_modules/ionic-angular/polyfills/${process.env.IONIC_POLYFILL_FILE_NAME}`],\n    dest: '{{BUILD}}'\n  },\n  copySwToolbox: {\n    src: ['{{ROOT}}/node_modules/sw-toolbox/sw-toolbox.js'],\n    dest: '{{BUILD}}'\n  }\n}\n"
  },
  {
    "path": "config/sass.config.js",
    "content": "\n// https://www.npmjs.com/package/node-sass\n\nmodule.exports = {\n\n  /**\n   * outputFilename: The filename of the saved CSS file\n   * from the sass build. The directory which it is saved in\n   * is set within the \"buildDir\" config options.\n   */\n  outputFilename: process.env.IONIC_OUTPUT_CSS_FILE_NAME,\n\n  /**\n   * sourceMap: If source map should be built or not.\n   */\n  sourceMap: false,\n\n  /**\n   * outputStyle: How node-sass should output the css file.\n   */\n  outputStyle: 'expanded',\n\n  /**\n   * autoprefixer: The config options for autoprefixer.\n   * Excluding this config will skip applying autoprefixer.\n   * https://www.npmjs.com/package/autoprefixer\n   */\n  autoprefixer: {\n    browsers: [\n      'last 2 versions',\n      'iOS >= 8',\n      'Android >= 4.4',\n      'Explorer >= 11',\n      'ExplorerMobile >= 11'\n    ],\n    cascade: false\n  },\n\n  /**\n   * includePaths: Used by node-sass for additional\n   * paths to search for sass imports by just name.\n   */\n  includePaths: [\n    'node_modules/ionic-angular/themes',\n    'node_modules/ionicons/dist/scss',\n    'node_modules/ionic-angular/fonts'\n  ],\n\n  /**\n   * includeFiles: An array of regex patterns to search for\n   * sass files in the same directory as the component module.\n   * If a file matches both include and exclude patterns, then\n   * the file will be excluded.\n   */\n  includeFiles: [\n    /\\.(s(c|a)ss)$/i\n  ],\n\n  /**\n   * excludeFiles: An array of regex patterns for files which\n   * should be excluded. If a file matches both include and exclude\n   * patterns, then the file will be excluded.\n   */\n  excludeFiles: [\n    /*  /\\.(wp).(scss)$/i  */\n  ],\n\n  /**\n   * variableSassFiles: Lists out the files which include\n   * only sass variables. These variables are the first sass files\n   * to be imported so their values override default variables.\n   */\n  variableSassFiles: [\n    '{{SRC}}/theme/variables.scss'\n  ],\n\n  /**\n   * directoryMaps: Compiled JS modules may be within a different\n   * directory than its source file and sibling component sass files.\n   * For example, NGC places it's files within the .tmp directory\n   * but doesn't copy over its sass files. This is useful so sass\n   * also checks the JavaScript's source directory for sass files.\n   */\n  directoryMaps: {\n    '{{TMP}}': '{{SRC}}'\n  },\n\n  /**\n   * excludeModules: Used just as a way to skip over\n   * modules which we know wouldn't have any sass to be\n   * bundled. \"excludeModules\" isn't necessary, but is a\n   * good way to speed up build times by skipping modules.\n   */\n  excludeModules: [\n    '@angular',\n    'commonjs-proxy',\n    'core-js',\n    'ionic-native',\n    'rxjs',\n    'zone.js'\n  ]\n\n};\n"
  },
  {
    "path": "config/uglifyjs.config.js",
    "content": "\n// https://www.npmjs.com/package/uglify-es\n\nmodule.exports = {\n\n  /**\n   * mangle: uglify 2's mangle option\n   */\n  mangle: true,\n\n  /**\n   * compress: uglify 2's compress option\n   */\n  compress: {\n    toplevel: true,\n    pure_getters: true\n  }\n};"
  },
  {
    "path": "config/watch.config.js",
    "content": "var watch = require('../dist/watch');\nvar copy = require('../dist/copy');\nvar copyConfig = require('./copy.config');\n\n// this is a custom dictionary to make it easy to extend/override\n// provide a name for an entry, it can be anything such as 'srcFiles' or 'copyConfig'\n// then provide an object with the paths, options, and callback fields populated per the Chokidar docs\n// https://www.npmjs.com/package/chokidar\n\nmodule.exports = {\n  srcFiles: {\n    paths: ['{{SRC}}/**/*.(ts|html|s(c|a)ss)'],\n    options: { ignored: ['{{SRC}}/**/*.spec.ts', '{{SRC}}/**/*.e2e.ts', '**/*.DS_Store', '{{SRC}}/index.html'] },\n    callback: watch.buildUpdate\n  },\n  copyConfig: copy.copyConfigToWatchConfig()\n};\n"
  },
  {
    "path": "config/webpack.config.js",
    "content": "/*\n * The webpack config exports an object that has a valid webpack configuration\n * For each environment name. By default, there are two Ionic environments:\n * \"dev\" and \"prod\". As such, the webpack.config.js exports a dictionary object\n * with \"keys\" for \"dev\" and \"prod\", where the value is a valid webpack configuration\n * For details on configuring webpack, see their documentation here\n * https://webpack.js.org/configuration/\n */\n\nvar path = require('path');\nvar webpack = require('webpack');\nvar ionicWebpackFactory = require(process.env.IONIC_WEBPACK_FACTORY);\nconst Dotenv = require('dotenv-webpack');\n\nvar ModuleConcatPlugin = require('webpack/lib/optimize/ModuleConcatenationPlugin');\nvar PurifyPlugin = require('@angular-devkit/build-optimizer').PurifyPlugin;\n\nvar optimizedProdLoaders = [\n  {\n    test: /\\.json$/,\n    loader: 'json-loader'\n  },\n  {\n    test: /\\.js$/,\n    loader: [\n      {\n        loader: process.env.IONIC_CACHE_LOADER\n      },\n\n      {\n        loader: '@angular-devkit/build-optimizer/webpack-loader',\n        options: {\n          sourceMap: true\n        }\n      },\n    ]\n  },\n  {\n    test: /\\.ts$/,\n    loader: [\n      {\n        loader: process.env.IONIC_CACHE_LOADER\n      },\n\n      {\n        loader: '@angular-devkit/build-optimizer/webpack-loader',\n        options: {\n          sourceMap: true\n        }\n      },\n\n      {\n        loader: process.env.IONIC_WEBPACK_LOADER\n      }\n    ]\n  }\n];\n\nfunction getProdLoaders() {\n  if (process.env.IONIC_OPTIMIZE_JS === 'true') {\n    return optimizedProdLoaders;\n  }\n  return devConfig.module.loaders;\n}\n\nvar devConfig = {\n  entry: process.env.IONIC_APP_ENTRY_POINT,\n  output: {\n    path: '{{BUILD}}',\n    publicPath: 'build/',\n    filename: '[name].js',\n    devtoolModuleFilenameTemplate: ionicWebpackFactory.getSourceMapperFunction(),\n  },\n  devtool: process.env.IONIC_SOURCE_MAP_TYPE,\n\n  resolve: {\n    extensions: ['.ts', '.js', '.json'],\n    modules: [path.resolve('node_modules')]\n  },\n\n  module: {\n    loaders: [\n      {\n        test: /\\.json$/,\n        loader: 'json-loader'\n      },\n      {\n        test: /\\.ts$/,\n        loader: process.env.IONIC_WEBPACK_LOADER\n      }\n    ]\n  },\n\n  plugins: [\n    new Dotenv({\n      path: '.env.dev', // load this now instead of the ones in '.env'\n      systemvars: true, // load all the predefined 'process.env' variables which will trump anything local per dotenv specs.\n      silent: true // hide any errors\n    }),\n    ionicWebpackFactory.getIonicEnvironmentPlugin(),\n    ionicWebpackFactory.getCommonChunksPlugin()\n  ],\n\n  // Some libraries import Node modules but don't use them in the browser.\n  // Tell Webpack to provide empty mocks for them so importing them works.\n  node: {\n    fs: 'empty',\n    net: 'empty',\n    tls: 'empty'\n  }\n};\n\nvar prodConfig = {\n  entry: process.env.IONIC_APP_ENTRY_POINT,\n  output: {\n    path: '{{BUILD}}',\n    publicPath: 'build/',\n    filename: '[name].js',\n    devtoolModuleFilenameTemplate: ionicWebpackFactory.getSourceMapperFunction(),\n  },\n  devtool: process.env.IONIC_SOURCE_MAP_TYPE,\n\n  resolve: {\n    extensions: ['.ts', '.js', '.json'],\n    modules: [path.resolve('node_modules')]\n  },\n\n  module: {\n    loaders: getProdLoaders()\n  },\n\n  plugins: [\n    new Dotenv({\n      path: '.env.prod', // load this now instead of the ones in '.env'\n      systemvars: true, // load all the predefined 'process.env' variables which will trump anything local per dotenv specs.\n      silent: true // hide any errors\n    }),\n    ionicWebpackFactory.getIonicEnvironmentPlugin(),\n    ionicWebpackFactory.getCommonChunksPlugin(),\n    new ModuleConcatPlugin(),\n    new PurifyPlugin()\n  ],\n\n  // Some libraries import Node modules but don't use them in the browser.\n  // Tell Webpack to provide empty mocks for them so importing them works.\n  node: {\n    fs: 'empty',\n    net: 'empty',\n    tls: 'empty'\n  }\n};\n\n\nmodule.exports = {\n  dev: devConfig,\n  prod: prodConfig\n}\n\n"
  },
  {
    "path": "lab/index.html",
    "content": "<!doctype html>\n<html>\n  <head>\n    <title>Ionic Lab</title>\n    <meta charset=\"utf-8\">\n    <link rel=\"icon\" type=\"image/png\" href=\"/ionic-lab/static/img/favicon.png\" />\n    <link rel=\"stylesheet\" href=\"/ionic-lab/static/css/style.css\" />\n    <link rel=\"stylesheet\" href=\"http://code.ionicframework.com/ionicons/2.0.1/css/ionicons.min.css\" />\n  </head>\n  <body>\n    <div id=\"app\">\n      <div id=\"header\">\n        <div id=\"header-left\">\n          <a href=\"#\" id=\"menu-toggle\">\n            <i class=\"icon ion-navicon-round\"></i>\n          </a>\n          <div id=\"logo\">\n          </div>\n        </div>\n\n        <div id=\"header-right\">\n          <a href=\"/\" target=\"_blank\" id=\"open-fullscreen\">Open fullscreen <i class=\"icon ion-share\"></i></a>\n          <div class=\"dropdown\">\n            <button class=\"dropdown-toggle\" type=\"button\">Platforms <span class=\"dropdown-caret\"></span></button>\n            <ul class=\"dropdown-menu\">\n              <li><input type=\"checkbox\" id=\"device-iphone\" name=\"iphone\"><label for=\"device-iphone\">iPhone</label></li>\n              <li><input type=\"checkbox\" id=\"device-android\" name=\"android\"><label for=\"device-android\">Android</label></li>\n              <li><input type=\"checkbox\" id=\"device-windows\" name=\"windows\"><label for=\"device-windows\">Windows</label></li>\n            </ul>\n          </div>\n        </div>\n      </div>\n      <div id=\"main\">\n        <div id=\"sidebar\" class=\"hidden\">\n          <div class=\"title\">Quick reference</div>\n          <div class=\"close\"><i class=\"icon ion-close-circled\"></i></div>\n          <ul id=\"menu\" class=\"menu\">\n            <li><a>Components</a>\n              <ul id=\"components-menu\">\n              </ul>\n            </li>\n            <li><a target=\"_blank\" href=\"https://ionicframework.com/docs/api\">API Reference</a></li>\n            <li><a target=\"_blank\" href=\"https://ionicframework.com/docs/native\">Ionic Native</a></li>\n            <li><a target=\"_blank\" href=\"https://ionicframework.com/docs/\">All Documentation</a></li>\n          </ul>\n          <div id=\"view-ad\" class=\"ad\">\n            <img class=\"logo\" src=\"/ionic-lab/static/img/view-logo.jpg\" style=\"height: 64px\"/>\n            <div class=\"ad-content\">\n              Test and share your app live on iOS and Android\n              with the Ionic View app!\n              <br>\n              <a href=\"http://view.ionic.io\">Download View</a>\n            </div>\n          </div>\n        </div>\n        <preview>\n        </preview>\n      </div>\n      <div id=\"footer\">\n        <div id=\"footer-left\">\n          <span id=\"app-info\"></span>\n        </div>\n        <div id=\"footer-right\">\n          <a target=\"_blank\" href=\"http://twitter.com/ionicframework\">Twitter</a>\n          <a target=\"_blank\" href=\"http://ionicframework.com/docs\">Documentation</a>\n          <a target=\"_blank\" href=\"https://forum.ionicframework.com/\">Forum</a>\n          <a target=\"_blank\" href=\"https://github.com/ionic-team/ionic\">GitHub</a>\n          <a target=\"_blank\" class=\"view-link\" href=\"http://view.ionic.io/\">Ionic View</a>\n        </div>\n      </div>\n\n      <div id=\"view-popup\">\n        <div class=\"view-popup-wrapper\">\n          <div class=\"close\"></div>\n          <div class=\"content\">\n            <h2>Psssst...</h2>\n            <p>\n              You can test your app live on iOS and Android\n              with the <a href=\"http://view.ionic.io\" target=\"_blank\">Ionic View</a> app!\n            </p>\n          </div>\n          <div class=\"ionitron\"></div>\n        </div>\n      </div>\n    </div>\n    <template id=\"iphone-frame-template\">\n      <div class=\"phone\" id=\"iphone\">\n        <div class=\"phone-frame\">\n          <h2><div class=\"phone-icon\"></div> <a href=\"/?ionicplatform=ios\" target=\"_blank\">iOS</a></h2>\n          <div id=\"iphone-frame\" class=\"phone-frame-wrap\">\n            <div class=\"statusbar\"></div>\n            <iframe src=\"/?ionicplatform=ios&amp;ionicstatusbarpadding=true\" class=\"frame\"></iframe>\n          </div>\n        </div>\n      </div>\n    </template>\n    <template id=\"android-frame-template\">\n      <div class=\"phone\" id=\"android\">\n        <div class=\"phone-frame\">\n          <h2><div class=\"phone-icon\"></div> <a href=\"/?ionicplatform=android\" target=\"_blank\">Android</a></h2>\n          <div id=\"android-frame\" class=\"phone-frame-wrap\">\n            <div class=\"statusbar\"></div>\n            <iframe src=\"/?ionicplatform=android\" class=\"frame\"></iframe>\n          </div>\n        </div>\n      </div>\n    </template>\n    <template id=\"windows-frame-template\">\n      <div class=\"phone\" id=\"windows\">\n        <div class=\"phone-frame\">\n          <h2><div class=\"phone-icon\"></div> <a href=\"/?ionicplatform=windows\" target=\"_blank\">Windows</a></h2>\n          <div id=\"windows-frame\" class=\"phone-frame-wrap\">\n            <div class=\"statusbar\"></div>\n            <iframe src=\"/?ionicplatform=windows\" class=\"frame\"></iframe>\n          </div>\n        </div>\n      </div>\n    </template>\n    <script src=\"https://cdnjs.cloudflare.com/ajax/libs/fetch/2.0.3/fetch.min.js\"></script>\n    <script src=\"/ionic-lab/static/js/lab.js\"></script>\n    <script>\n      (function(i,s,o,g,r,a,m){i['GoogleAnalyticsObject']=r;i[r]=i[r]||function(){\n      (i[r].q=i[r].q||[]).push(arguments)},i[r].l=1*new Date();a=s.createElement(o),\n      m=s.getElementsByTagName(o)[0];a.async=1;a.src=g;m.parentNode.insertBefore(a,m)\n      })(window,document,'script','//www.google-analytics.com/analytics.js','ga');\n\n      ga('create', 'UA-44023830-9', {\n        'cookieDomain': 'none'\n      })\n      ga('send', 'pageview');\n    </script>\n  </body>\n</html>\n"
  },
  {
    "path": "lab/static/css/style.css",
    "content": "@font-face {font-family: 'AvenirNextLTPro-Regular';src: url('http://code.ionicframework.com/assets/fonts/28882F_0_0.eot');src: url('http://code.ionicframework.com/assets/fonts/28882F_0_0.eot?#iefix') format('embedded-opentype'),url('http://code.ionicframework.com/assets/fonts/28882F_0_0.woff') format('woff'),url('http://code.ionicframework.com/assets/fonts/28882F_0_0.ttf') format('truetype');}\n\n\n@font-face {font-family: 'AvenirNextLTPro-Medium';src: url('http://code.ionicframework.com/assets/fonts/28882F_1_0.eot');src: url('http://code.ionicframework.com/assets/fonts/28882F_1_0.eot?#iefix') format('embedded-opentype'),url('http://code.ionicframework.com/assets/fonts/28882F_1_0.woff') format('woff'),url('http://code.ionicframework.com/assets/fonts/28882F_1_0.ttf') format('truetype');}\n\n\n@font-face {font-family: 'AvenirNextLTPro-UltLt';src: url('http://code.ionicframework.com/assets/fonts/29CC36_0_0.eot');src: url('http://code.ionicframework.com/assets/fonts/29CC36_0_0.eot?#iefix') format('embedded-opentype'),url('http://code.ionicframework.com/assets/fonts/29CC36_0_0.woff') format('woff'),url('http://code.ionicframework.com/assets/fonts/29CC36_0_0.ttf') format('truetype');}\n\nhtml, body {\n  height: 100%;\n}\n\nbody {\n  background-color: #242A31;\n  font-family: 'AvenirNextLTPro-Regular', 'Helvetica Neue', 'Helvetica', Arial, sans-serif;\n  padding: 0;\n  margin: 0;\n  -webkit-font-smoothing: antialiased;\n}\nh2 {\n  color: #fff;\n  font-family: 'AvenirNextLTPro-Regular', 'Helvetica Neue', 'Helvetica', Arial, sans-serif;\n}\n\nh2 a {\n  font-size: 14px;\n  color: #727a87;\n  text-decoration: none;\n}\n\n.dropdown {\n  height: 100%;\n  position: relative;\n}\n.dropdown-toggle {\n  color: #858D9B;\n  display: block;\n  height: 100%;\n  background: none;\n  border: none;\n  font-size: 13px;\n  cursor: pointer;\n  padding-left: 15px;\n  font-weight: bold;\n  outline: none;\n  font-family: 'AvenirNextLTPro-Medium', 'Helvetica Neue', 'Helvetica', Arial, sans-serif;\n}\n.dropdown-menu {\n  display: none;\n  position: absolute;\n  z-index: 999;\n  width: 150px;\n  right: -10px;\n  background-color: white;\n  box-shadow: 0 6px 10px rgba(0,0,0,.08), 0 0px 6px rgba(0,0,0,.05);\n}\n.dropdown-menu > li {\n  padding: 6px 5px;\n}\n.dropdown:hover .dropdown-toggle {\n  /* increase the hit box when hovering */\n  /*padding-left: 100px;*/\n}\n.dropdown:hover .dropdown-menu {\n  display: block;\n}\n.dropdown li {\n  list-style: none;\n}\n.dropdown ul {\n  margin: 0;\n  padding: 0;\n}\n.dropdown-caret {\n  width: 0;\n  height: 0;\n  border-left: 5px solid transparent;\n  border-right: 5px solid transparent;\n\n  border-top: 5px solid #858D9B;\n  display: inline-block;\n}\n.dropdown-menu input {\n  cursor: pointer;\n  background-color: #4D82E9;\n}\n.dropdown-menu label {\n  font-size: 13px;\n  margin-left: 5px;\n  color: #858D9B;\n  cursor: pointer;\n  user-select: none;\n  -webkit-user-select: none;\n}\n\n#app {\n  display: flex;\n  flex-direction: column;\n  height: 100%;\n}\n\n#header {\n  width: 100%;\n  height: 50px;\n  background-color: #151A21;\n  box-shadow: 0px 1px 3px rgba(0,0,0, 0.15);\n}\n#header a {\n  color: #858D9B;\n  font-family: 'AvenirNextLTPro-Medium', 'Helvetica Neue', 'Helvetica', Arial, sans-serif;\n  font-size: 13px;\n  text-decoration: none;\n  display: inline-block;\n  font-weight: bold;\n}\n#header .icon {\n  display: inline-block;\n  font-size: 22px;\n  vertical-align: middle;\n  margin-bottom: 3px;\n  margin-left: 3px;\n}\n#header .dropdown {\n  display: inline-block;\n}\n\n#header-left {\n  float: left;\n  line-height: 50px;\n}\n#header-left #menu-toggle {\n  margin-left: 13px;\n}\n#header a:hover {\n  opacity: 1;\n}\n#header-right {\n  float: right;\n  margin-right: 15px;\n  height: 100%;\n}\n#footer {\n  width: 100%;\n  border-top: 1px solid rgba(0,0,0,0.06);\n  background-color: #151A21;\n}\n#footer-left {\n  float: left;\n  padding: 14px 0 14px 15px;\n  font-size: 13px;\n}\n#app-info {\n  color: #828080;\n}\n#footer-right {\n  float: right;\n  padding: 14px 15px 14px 5px;\n  font-size: 13px;\n}\n#footer-right a {\n  margin-left: 10px;\n  color: #a2a9b4;\n  text-decoration: none;\n  font-family: 'AvenirNextLTPro-Medium', 'Helvetica Neue', 'Helvetica', Arial, sans-serif;\n}\n#logo {\n  display: inline-block;\n  vertical-align: middle;\n\n  width: 64px;\n  height: 28px;\n  margin-left: 15px;\n  background-size: 100%;\n  background-repeat: no-repeat;\n  background-image: url(data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAIAAAAA4CAMAAAACeQDhAAACEFBMVEVHiv////9Hiv9Hiv9Hiv9Hiv9Hiv9Hiv9Hiv9Hiv9Hiv9Hiv9Hiv9Hiv9Hiv9Hiv9Hiv9Hiv9Hiv9Hiv9Hiv9Hiv9Hiv9Hiv9Hiv9Hiv9Hiv9Hiv9Hiv9Hiv9Hiv9Hiv9Hiv9Hiv9Hiv9Hiv9Hiv9Hiv9Hiv9Hiv9Hiv9Hiv9Hiv9Hiv9Hiv9Hiv9Hiv9Hiv9Hiv9Hiv9Hiv9Hiv9Hiv9Hiv9Hiv9Hiv9Hiv9Hiv9Hiv9Hiv9Hiv9Hiv9Hiv9Hiv9Hiv9Hiv9Hiv9Hiv9Hiv9Hiv9Hiv9Hiv9Hiv9Hiv9Hiv9Hiv9Hiv9Hiv9Hiv9Hiv9Hiv9Hiv9Hiv9Hiv9Hiv9Hiv9Hiv9Hiv9Hiv9Hiv9Hiv9Hiv9Hiv9Hiv9Hiv9Hiv9Hiv9Hiv9Hiv9Hiv9Hiv9Hiv9Hiv9Hiv9Hiv9Hiv9Hiv9Hiv9Hiv9Hiv9Hiv9Hiv9Hiv9Hiv9Hiv9Hiv9Hiv9Hiv9Hiv9Hiv9Hiv9Hiv9Hiv9Hiv9Hiv9Hiv9Hiv9Hiv9Hiv9Hiv9Hiv9Hiv9Hiv9Hiv9Hiv9Hiv9Hiv9Hiv9Hiv9Hiv9Hiv9Hiv9Hiv9Hiv9Hiv9Hiv9Hiv9Hiv9Hiv9Hiv9Hiv9Hiv9Hiv9Hiv9Hiv9Hiv9Hiv9Hiv9Hiv9Hiv9Hiv9Hiv9Hiv9Hiv9Hiv9Hiv9Hiv9Hiv9Hiv9Hiv9Hiv9Hiv9Hiv9Hiv9Hiv9Hiv/arDJrAAAAr3RSTlMAAAEDBAUGBwgJCgsMDQ4PEBEUFRYXGBkbHB0eHyAhIiMkJSYnKTAxMjU2Nzk6Oz0+P0BFRkdKS1BXWFlaW1xdYGFiY2RlZ2hpa2xub3BxcnN0dXd4eXp8fX5/gIGChYeIiY2Oj5CRl5iZm5ydnp+go6Slrq+wtLW2t7u/wMHCw8bHyMnLzM3Oz9DS09TV2Nna297f4OLj5OXm5+jp6+zu7/Dx8vP09fb3+Pn7/P3+Rx34RQAABFxJREFUeAHF2ft701Qcx/HvCgyQzUWHsuE62HRBBAWDQhXYIooiGEXEi9bZqfXilKBSFa0MlcnKNp0S5qZclg0YFzc+/6LnnFyaNEkxPL28fqPlefbuSU5yckINdUZBic7dmWO/Ts6YM5Onc+/3rU9QUBUD1r3842V4XTn5alftAh4enEXQ7JEtiZoEPPDJNYT7blUtAtQpuK4aYyNjZ6/A8kML0Zred9/YsaKKAc2DsF06fljpbm1a0dTatfXQtxeB6XaiZ6fBDD9atYD2E7CM7O8kr+S+0ZeInofF3FilgK4xCOO7G6lUYyN1zMF2cmlVAtaOg/s300KhDsFxa3M1ApqGwP21nSLocPVVI+BzcKM9FOVIuQBwVF7U/7EC+qzD30GRDsKxsKnyAW1/g5l6iKIlTdhOLKt8wGdgbjxJ5eyF5eIGqniAfB1MhrxWbt2f+XjggLKKHH0GmF82UeUDvgDz291U1Pb2xAK4xT/715Lt3p39bz7RSPTYwO0DFEWODpAV2ReQnAPzNBX1TqJoag/5bP9nsbtsgJQugMupzt/1BEia9WVBk9yAV8QHjeRIZOD3wRJybfhyAXi9XEDahMOQSwIUrfilqTkBQ2D2kSuDUh+S60VxIiQiA6Q8vFR/QAFeuhWwbp7f/9rJsQtBz5BjzQUA13siAwoIcAMCdBGwE8z35LhvGkHni33fiKCogCxs+bwRFWDk3VHSeMAAmNfI8RbCvEeOA2CyEQFJCIYq8X+kwwJ0mRjVyjOTLOAYGMWd/xMIc66JbJvFgEUE6OAKEllkszTATJFFylk5LOA0gPkusm1ZRJhb28iWnOEXjeWhARI4QyKHUhqQIodUEEEsgM95Y3XxLA93kGz38CG60BwakAKnUpHuD8gTlcSlGoj/ovHmqDkYmInL+YjNrQ4NEAfdIA/FH5AiDwNMuoFmARRWku0jhPuUbMuGAVxuCw3Iu7Pb5Q8glzNjcvFHYCR6BERA+v8GiPHKxz8H/hDnQMUC4s8CM3oW6IFDIPkDZPIQEzF7Z9eB4xR9EprkofoDNPIwxScVvRIqgWlY8AcYgTb5zu4FakiA+6NMmRwa/AGe4yOZoij23fD+snfDLHwFGkoD3IJkAZwWez2wB8ypiPWAu3bWUxJJah7BABhakkjOmlaqFHdFtOQUmMMUEUAa4qwHkIq9JkyBmX8wMoB0+Jj+ABM+epxVsXCXOHJfU3SAv8CQ/QGyryAb67lAeAfMzUfKBVDKgM1MS+QPoGS+GKfEejISlGtgjlJpAPmpegEwcqpEIZQsazDz2VS8Z0Nh/Tkwlzqq8Hh+26djkXkG3F6qw/4A1zUC7iuiau6Q7KAo2wxwP0tE9dgjau6/CW6ik6gOu2RLnzoD4fceojrsE74wDMtPSaKa7pSuau1+XOyUWo621HqvePTsVbjOP1fX3fIbg511fV+gb6znG5OhGr4xERKdu6x3RiZ7ZzTQW+13Rg11VveA/wDc7Jb5d/SIFAAAAABJRU5ErkJggg==);\n}\n\n#logo img {\n  max-width: 100%;\n}\n\npreview {\n  flex: 1;\n  width: 100%;\n  display: flex;\n  vertical-align: middle;\n  overflow: auto;\n  text-align: center;\n  justify-content: center;\n  align-items: center;\n  flex-wrap: wrap;\n  flex-direction: row;\n}\n\n.phone {\n  display: inline-block;\n  margin: 0px 20px 0 20px;\n  vertical-align: middle;\n}\n.phone h2 {\n  text-align: left;\n  vertical-align: middle;\n  margin-top: 0;\n}\n.phone-icon {\n  display: inline-block;\n  vertical-align: middle;\n  background-repeat: no-repeat;\n  background-image: url('data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAACAAAABgCAYAAAB8InCYAAAAGXRFWHRTb2Z0d2FyZQBBZG9iZSBJbWFnZVJlYWR5ccllPAAABFlJREFUeNrsmmdoFEEUxzeagIXYomKNhUv8YMPoBzGRKBpEgxoFBSGRiKDYsWFMAuqHqCAW0OgHlSgaRcSICoqFKMEPSuyIiAVLbIgFUcESPf+PvNNh2Nub3Z05EtwHP27uZnbnvzuzb96bvYRwOGxFbHnJRsuHJYAZYD74CsZFa7i5rOhvuZmlxzqB86ASZLo5b6KGzltx54OF326rHqzjDqyROic7Ei8BHcBC6bczoDZeAnJ4CCL2Fsx1cwK/AgYI5ZsgC9S5OYHKJOwGJoCe4Bu4Bi6Bn+Axz/zj4ARIApPBQL4zz8Fp/nQtoD3YBApBc6nuI6gCp/BM58N/TEF5H8gDraW25GgOgmXgnaqAEDgL+jqIm00oOC9yUAUgG4wBj2LNgRRwzqFzr5bK520RS8B20MfSb3T7Z/I8ijoE/dmf67YfPJFrYz2G0y0zti2ac5IFjDYkYIeqI0o30PlDJ+ckC+hsQMArN644wYCAJDcCvhkQ0M9pzZEr6gwIIMc2QlXAfUNPQbGqgKuGBIwHc1QEnLPM2S6wVJ7osoDr4IUhAdTXFnDZScBvsMcya7dirYa7DT2OZL/A1lgCyHOVGxKwUyUgISsDHwzEA2tVo2KK+RZrFjDf7qKcwvJKRofRxD7qJSyfzStkjk0dheU3wBsOUodJSUrEToIFXhOT7yAXlILX/NsDsJLzheEIy/M44u0C5oE73O4loDx8Kodk9iEz7Q8ohNaFvE5cUbzllCH15nzActonUE3PKekgle3oqkEG6C4kLPWc/Vzj2C+b75q2/YH+vKSucmgzFEzh8nlObh7pSE5z+apGusyaaYKO8iuAruIQaMmPEY1rjUP7Gk5qjoFk/uzpRwCtXm2EYOUZZ8TRjOqegnvCBsYmrwLo6icK30v5EZzlcMwsblMqJTupXgTk2UTMaQrjnyYFHVSe5ChA3LcTbLDGdWCI42MIR9SLA0dar8sg6CXn9AU6VyP0Q76jhP3HeppTET9Qxc6FbBAaZvHOhk6joaDtu0z+TmvH0MgcyBAaZhoMx8RzZ+jYJdMSqQYCAgGBgEYhoEL4rdxgf+K5K8SQjMLvw7wWXDQoYBG7fVoLLogCwhzHmTbqp7rJTMJqjf1UexGwxGp4C+LXTvDWjHpewBHSXTEsQ4wQltokSMFG1HrKvOicdhlY3OZAtPSvSXnCL0K53kO9bwH7hfIBD/W+klNbL+ay3j5SXVa8wfQw09PQgzMtIl0ohxLj0YklvarzOgRiJ5R654OxKp24mQOqV0Kp1jTeCfFliZw+h/xeiR8BK4KQLBAQCAgE/NcCyBOu83AcvaTYZzX8n8i3gKcejvvBIrTcgQoPx9FqWKRjNQwmYSAgEBAIaBRrwWqr4T1PiD+7xluAvHOQLCUm6UK5owkBsn22Gv6ie9Omri0LecIpeJ0gsp0uAU72yfr3x8S9TMRSeAjT+VMkWZcAJ3vP2L3i7yLMM3FYQ38EGAB86eMs4sfk/QAAAABJRU5ErkJggg==');\n  width: 16px;\n  height: 16px;\n  background-size: 100%;\n}\n#iphone .phone-icon {\n  background-position: 0px 0px;\n}\n#android .phone-icon {\n  background-position: 0px -16px;\n}\n#windows .phone-icon {\n  background-position: 0px -32px;\n}\n.frame {\n  border: none;\n}\n.phone-frame-wrap {\n  position: relative;\n  width: 375px;\n  height: 667px;\n  border-radius: 3px;\n  box-shadow: 0 8px 24px rgba(0,0,0,.08), 0 0px 6px rgba(0,0,0,.1);\n  overflow: hidden;\n}\n.phone-frame-wrap iframe {\n  width: 100%;\n  height: 100%;\n}\n.statusbar {\n  position: absolute;\n  top: 0;\n  width: 375px;\n  height: 10px;\n  padding: 5px 0;\n  background-size: 100%;\n  background-repeat: no-repeat;\n  background-color: transparent;\n  background-position: center;\n  border-radius: 3px 3px 0 0;\n}\n#iphone-frame .statusbar {\n  background-image: url(../img/ios-statusbar.png);\n}\n#android-frame .statusbar {\n  background-image: url(../img/android-statusbar.png);\n}\n#windows-frame .statusbar {\n  background-image: url(../img/wp-statusbar.png)\n}\n\n/* Android and windows don't make space for the statusbar like iOS does */\n#android-frame iframe, #windows-frame iframe {\n  height: 647px;\n  margin-top: 20px;\n}\n\n#view-popup {\n  display: none;\n  opacity: 0;\n  transition: 400ms linear opacity;\n  position: fixed;\n  bottom: 15px;\n  left: 15px;\n}\n#view-popup .view-popup-wrapper {\n  position: relative;\n}\n#view-popup .ionitron {\n  background: url('../img/popup-ionitron.png') no-repeat transparent;\n  width: 90px;\n  height: 90px;\n  background-size: 100%;\n}\n#view-popup .content {\n  box-sizing: border-box;\n  background: url('../img/popup-view-bubble.png') no-repeat transparent;\n  width: 350px;\n  height: 350px;\n  background-size: 100%;\n  margin-bottom: -50px;\n  padding-top: 25px;\n  text-align: center;\n  color: white;\n}\n#view-popup .content a {\n  color: #fff;\n  font-weight: bold;\n}\n#view-popup .content h2 {\n  margin-bottom: 5px;\n}\n#view-popup .content p {\n  font-size: 13px;\n  max-width: 220px;\n  margin: auto;\n}\n\n#view-popup .close {\n  cursor: pointer;\n  background: url('../img/popup-close.png') no-repeat transparent;\n  position: absolute;\n  right: 35px;\n  top: 20px;\n  width: 60px;\n  height: 60px;\n  background-size: 100%;\n  position: absolute;\n}\n\n#main {\n  display: flex;\n  flex-direction: row;\n  flex: 1;\n}\n\n#sidebar {\n  position: relative;\n  z-index: 1;\n  background-color: #141A21;\n  height: 100%;\n  width: 300px;\n  box-sizing: border-box;\n  padding: 15px;\n  overflow: auto;\n}\n\n#sidebar.hidden {\n  display: none;\n}\n\n#sidebar .close {\n  cursor: pointer;\n  position: absolute;\n  top: 15px;\n  right: 15px;\n  color: #a2a9b4;\n  font-size: 24px;\n}\n\n#sidebar .title {\n  font-size: 20px;\n  margin-top: 5px;\n  color: white;\n}\n\n.menu {\n  padding: 0;\n  margin: 0;\n  -webkit-touch-callout: none; /* iOS Safari */\n  -webkit-user-select: none; /* Safari */\n   -khtml-user-select: none; /* Konqueror HTML */\n     -moz-user-select: none; /* Firefox */\n      -ms-user-select: none; /* Internet Explorer/Edge */\n          user-select: none; /* Non-prefixed version, currently\n                                supported by Chrome and Opera */\n}\n.menu li {\n  list-style: none;\n  margin: 15px 0;\n}\n.menu li a {\n  color: #a2a9b4;\n  text-decoration: none;\n  cursor: pointer;\n}\n.menu > li > ul {\n  display: none;\n  padding: 0;\n  margin: 0;\n}\n.menu > li > ul li {\n  padding-left: 15px;\n}\n.menu > li.expanded > ul {\n  display: block;\n}\n.menu hr {\n  height: 1px;\n  border: 0;\n  background-color: #2b3642;\n}\n.menu .version {\n  color: #a2a9b4;\n  opacity: 0.5;\n  font-size: 12px;\n}\n\n#menu {\n}\n\n.ad {\n  background-color: #232A31;\n  cursor: pointer;\n  border-radius: 2px;\n  border: 1px solid #3f4650;\n  font-size: 13px;\n  color: white;\n  padding: 10px;\n  display: flex;\n  margin-top: 55px;\n}\n.ad .logo {\n  display: block;\n  margin-right: 15px;\n}\n.ad .content {\n  flex: 1;\n}\n.ad a {\n  color: #308EFD;\n}\n\n@media screen and (max-height: 800px) {\n  #header {\n    height: 40px;\n  }\n  #header-left {\n    line-height: 40px;\n  }\n  #logo {\n    width: 50px;\n    height: 22px;\n  }\n  #footer-left {\n    float: left;\n    padding: 7px 0 7px 15px;\n    font-size: 12px;\n  }\n  #app-info {\n    color: #828080;\n  }\n  #footer-right {\n    float: right;\n    padding: 7px 15px 7px 5px;\n    font-size: 13px;\n  }\n  #footer-right .view-link {\n    color: #4D82E9;\n  }\n  .phone-frame h2 {\n    display: none;\n  }\n\n  @media screen and (max-width: 500px) {\n    #footer {\n      display: none;\n    }\n  }\n}\n\n@media screen and (max-height: 780px) {\n  .statusbar {\n    width: 340px;\n  }\n  .phone-frame-wrap {\n    width: 340px;\n    height: 605px;\n  }\n  #android-frame iframe, #windows-frame iframe {\n    height: 585px;\n    margin-top: 20px;\n  }\n}\n@media screen and (max-height: 680px) {\n  .statusbar {\n    width: 325px;\n  }\n  .phone-frame-wrap {\n    width: 325px;\n    height: 578px;\n  }\n  #android-frame iframe, #windows-frame iframe {\n    height: 558px;\n    margin-top: 20px;\n  }\n}\n"
  },
  {
    "path": "lab/static/js/lab.js",
    "content": "var $ = document.querySelector.bind(document);\n\nvar API_ROOT = '/ionic-lab/api/v1';\n\nvar APP_CONFIG = {};\n\nfunction loadAppConfig() {\n  var req = new XMLHttpRequest();\n  req.addEventListener('load', function(e) {\n    setAppConfig(JSON.parse(req.response));\n  });\n  req.open('GET', API_ROOT + '/app-config', true);\n  req.send(null);\n}\n\nfunction setAppConfig(data) {\n  APP_CONFIG = data;\n}\n\nfunction buildMenu() {\n  buildComponentsMenu();\n  var sidebar = $('#sidebar');\n  var topLevels = sidebar.querySelectorAll('#menu > li > a');\n\n  var lastMenuConfig = window.localStorage.getItem('ionic_labmenu');\n  if (lastMenuConfig === 'true' || lastMenuConfig === null) {\n    sidebar.classList.remove('hidden');\n  }\n\n  Array.prototype.map.call(topLevels, function(a) {\n    if (!a.href) {\n      a.addEventListener('click', function(e) {\n        if (a.parentNode.classList.contains('expanded')) {\n          a.parentNode.classList.remove('expanded');\n        } else {\n          a.parentNode.classList.add('expanded');\n        }\n        e.preventDefault();\n      });\n    }\n  });\n\n  $('#view-ad').addEventListener('click', function(e) {\n    var win = window.open('http://view.ionic.io/', '_blank');\n    win.focus();\n  });\n\n  var toggleMenu = function(e) {\n    if (sidebar.classList.contains('hidden')) {\n      sidebar.classList.remove('hidden');\n      window.localStorage.setItem('ionic_labmenu', 'true');\n    } else {\n      sidebar.classList.add('hidden');\n      window.localStorage.setItem('ionic_labmenu', 'false');\n    }\n  };\n\n  $('#menu-toggle').addEventListener('click', toggleMenu);\n  $('#sidebar .close').addEventListener('click', toggleMenu);\n}\n\nfunction buildComponentsMenu() {\n  var items = [{\"href\":\"http://ionicframework.com/docs/components/#overview\",\"title\":\"Overview\"},{\"href\":\"http://ionicframework.com/docs/components/#action-sheets\",\"title\":\"Action Sheets\"},{\"href\":\"http://ionicframework.com/docs/components/#alert\",\"title\":\"Alerts\"},{\"href\":\"http://ionicframework.com/docs/components/#badges\",\"title\":\"Badges\"},{\"href\":\"http://ionicframework.com/docs/components/#buttons\",\"title\":\"Buttons\"},{\"href\":\"http://ionicframework.com/docs/components/#cards\",\"title\":\"Cards\"},{\"href\":\"http://ionicframework.com/docs/components/#checkbox\",\"title\":\"Checkbox\"},{\"href\":\"http://ionicframework.com/docs/components/#datetime\",\"title\":\"DateTime\"},{\"href\":\"http://ionicframework.com/docs/components/#fabs\",\"title\":\"FABs\"},{\"href\":\"http://ionicframework.com/docs/components/#gestures\",\"title\":\"Gestures\"},{\"href\":\"http://ionicframework.com/docs/components/#grid\",\"title\":\"Grid\"},{\"href\":\"http://ionicframework.com/docs/components/#icons\",\"title\":\"Icons\"},{\"href\":\"http://ionicframework.com/docs/components/#inputs\",\"title\":\"Inputs\"},{\"href\":\"http://ionicframework.com/docs/components/#lists\",\"title\":\"Lists\"},{\"href\":\"http://ionicframework.com/docs/components/#loading\",\"title\":\"Loading\"},{\"href\":\"http://ionicframework.com/docs/components/#menus\",\"title\":\"Menus\"},{\"href\":\"http://ionicframework.com/docs/components/#modals\",\"title\":\"Modals\"},{\"href\":\"http://ionicframework.com/docs/components/#navigation\",\"title\":\"Navigation\"},{\"href\":\"http://ionicframework.com/docs/components/#popovers\",\"title\":\"Popover\"},{\"href\":\"http://ionicframework.com/docs/components/#radio\",\"title\":\"Radio\"},{\"href\":\"http://ionicframework.com/docs/components/#range\",\"title\":\"Range\"},{\"href\":\"http://ionicframework.com/docs/components/#searchbar\",\"title\":\"Searchbar\"},{\"href\":\"http://ionicframework.com/docs/components/#segment\",\"title\":\"Segment\"},{\"href\":\"http://ionicframework.com/docs/components/#select\",\"title\":\"Select\"},{\"href\":\"http://ionicframework.com/docs/components/#slides\",\"title\":\"Slides\"},{\"href\":\"http://ionicframework.com/docs/components/#tabs\",\"title\":\"Tabs\"},{\"href\":\"http://ionicframework.com/docs/components/#toast\",\"title\":\"Toast\"},{\"href\":\"http://ionicframework.com/docs/components/#toggle\",\"title\":\"Toggle\"},{\"href\":\"http://ionicframework.com/docs/components/#toolbar\",\"title\":\"Toolbar\"}];\n\n  var componentsMenu = $('#components-menu');\n  items.map(function (i) {\n    var l = document.createElement('li');\n    var a = document.createElement('a');\n    a.href = i.href;\n    a.target = \"_blank\";\n    a.innerText = i.title;\n    l.appendChild(a);\n    componentsMenu.appendChild(l);\n  });\n}\n\nfunction tryShowViewPopup() {\n  var view = window.localStorage.getItem('ionic_viewpop');\n\n  if (!view) {\n    $('#view-popup').style.display = 'block';\n    $('#view-popup .close').addEventListener('click', function(e) {\n      window.localStorage.setItem('ionic_viewpop', true);\n      $('#view-popup').style.opacity = 0;\n      setTimeout(function() {\n        $('#view-popup').style.display = 'none';\n      }, 200);\n    });\n    window.requestAnimationFrame(function() {\n      $('#view-popup').style.opacity = 1;\n    });\n  }\n}\n\n// Bind the dropdown platform toggles\nfunction bindToggles() {\n  // Watch for changes on the checkboxes in the device dropdown\n  var iphone = $('#device-iphone');\n  var android = $('#device-android');\n  var windows = $('#device-windows');\n\n  var devices = [iphone, android, windows];\n  for(var i in devices) {\n    devices[i].addEventListener('change', function(e) {\n      var device = this.name;\n      console.log('Device changed', device, this.checked);\n\n      showDevice(device, this.checked);\n      saveLastDevices(device, this.checked);\n    });\n  }\n}\n\n// Show one of the devices\nfunction showDevice(device, isShowing) {\n  $('#device-' + device).checked = isShowing;\n\n  var rendered = $('#' + device);\n  if(!rendered) {\n    var template = $('#' + device + '-frame-template');\n    var clone = document.importNode(template, true);\n    $('preview').appendChild(clone.content);\n    //check for extra params in location.url to pass on to iframes\n    var params = document.location.href.split('?');\n    if (params) {\n      var newparams = params[params.length - 1];\n      var oldsrc = $('preview .frame').getAttribute('src');\n      $('preview .frame').setAttribute('src', oldsrc + '&' + newparams);\n    }\n  } else {\n    rendered.style.display = isShowing ? '' : 'none';\n  }\n}\n\nfunction saveLastDevices(newDevice, didAdd) {\n  var last = window.localStorage.getItem('ionic_lastdevices');\n  if(!last && didAdd) {\n    window.localStorage.setItem('ionic_lastdevices', newDevice);\n    return;\n  }\n  var devices = last.split(',');\n  var di = devices.indexOf(newDevice);\n  if(di == -1 && didAdd) {\n    window.localStorage.setItem('ionic_lastdevices', devices.join(',') + ',' + newDevice);\n  } else if(di >= 0) {\n    devices.splice(di, 1);\n    window.localStorage.setItem('ionic_lastdevices', devices.join(','));\n  }\n}\n\nfunction showLastDevices() {\n  var last = window.localStorage.getItem('ionic_lastdevices');\n  if(!last) {\n    showDevice('iphone', true);\n    return;\n  }\n\n  var devices = last.split(',');\n  for(var i = 0; i < devices.length; i++) {\n    showDevice(devices[i], true);\n  }\n}\n\nfunction setCordovaInfo(data) {\n  var el = $('#app-info');\n  el.innerHTML = data.name + ' - v' + data.version;\n  if(data.name) {\n    document.title = data.name + ' - Ionic Lab';\n  }\n}\n\nfunction loadCordova() {\n  var req = new XMLHttpRequest();\n  req.addEventListener('load', function(e) {\n    setCordovaInfo(JSON.parse(req.response));\n  });\n  req.open('GET', API_ROOT + '/cordova', true);\n  req.send(null);\n}\n\n//loadSearchIndex();\nloadAppConfig();\nbuildMenu();\nshowLastDevices();\nloadCordova();\nbindToggles();\n//tryShowViewPopup();\n"
  },
  {
    "path": "package.json",
    "content": "{\n  \"name\": \"@ionic/app-scripts\",\n  \"version\": \"3.2.4\",\n  \"description\": \"Scripts for Ionic Projects\",\n  \"homepage\": \"https://ionicframework.com/\",\n  \"author\": \"Ionic Team <hi@ionic.io> (https://ionic.io)\",\n  \"license\": \"MIT\",\n  \"files\": [\n    \"bin/\",\n    \"config/\",\n    \"dist/\",\n    \"lab\",\n    \"LICENSE\",\n    \"README.md\"\n  ],\n  \"bin\": {\n    \"ionic-app-scripts\": \"./bin/ionic-app-scripts.js\"\n  },\n  \"scripts\": {\n    \"build\": \"npm run clean && tsc && npm run sass\",\n    \"build-and-test\": \"jest\",\n    \"changelog\": \"./node_modules/.bin/conventional-changelog -p angular -i CHANGELOG.md -s\",\n    \"clean\": \"rimraf ./dist\",\n    \"github-release\": \"node ./scripts/create-github-release.js\",\n    \"lint\": \"tslint -c ./tslint.json --project ./tsconfig.json --type-check -t stylish\",\n    \"nightly\": \"npm run build && node ./scripts/publish-nightly.js\",\n    \"sass\": \"node-sass ./src/dev-client/sass/ion-dev.scss --output ./bin/ --output-style compressed\",\n    \"sass-watch\": \"npm run sass && node-sass ./src/dev-client/sass/ion-dev.scss --watch --output ./bin/ --output-style compressed\",\n    \"test\": \"jest\",\n    \"watch\": \"npm run clean && tsc --watch & npm run sass-watch\"\n  },\n  \"main\": \"dist/index.js\",\n  \"dependencies\": {\n    \"@angular-devkit/build-optimizer\": \"0.0.35\",\n    \"autoprefixer\": \"^7.2.6\",\n    \"chalk\": \"^2.4.0\",\n    \"chokidar\": \"^2.0.4\",\n    \"clean-css\": \"^4.1.11\",\n    \"cross-spawn\": \"^5.1.0\",\n    \"dotenv-webpack\": \"^1.5.7\",\n    \"express\": \"^4.16.3\",\n    \"fs-extra\": \"^4.0.2\",\n    \"glob\": \"^7.1.2\",\n    \"json-loader\": \"^0.5.7\",\n    \"node-sass\": \"^4.10.0\",\n    \"os-name\": \"^2.0.1\",\n    \"postcss\": \"^6.0.21\",\n    \"proxy-middleware\": \"^0.15.0\",\n    \"reflect-metadata\": \"^0.1.10\",\n    \"rollup\": \"0.50.0\",\n    \"rollup-plugin-commonjs\": \"8.2.6\",\n    \"rollup-plugin-node-resolve\": \"3.0.0\",\n    \"source-map\": \"^0.6.1\",\n    \"tiny-lr\": \"^1.1.1\",\n    \"tslint\": \"^5.8.0\",\n    \"tslint-eslint-rules\": \"^4.1.1\",\n    \"uglify-es\": \"3.2.2\",\n    \"webpack\": \"3.12.0\",\n    \"ws\": \"3.3.2\",\n    \"xml2js\": \"^0.4.19\"\n  },\n  \"devDependencies\": {\n    \"@angular/animations\": \"5.0.3\",\n    \"@angular/common\": \"5.0.3\",\n    \"@angular/compiler\": \"5.0.3\",\n    \"@angular/compiler-cli\": \"5.0.3\",\n    \"@angular/core\": \"5.0.3\",\n    \"@angular/forms\": \"5.0.3\",\n    \"@angular/http\": \"5.0.3\",\n    \"@angular/platform-browser\": \"5.0.3\",\n    \"@angular/platform-browser-dynamic\": \"5.0.3\",\n    \"@angular/platform-server\": \"5.0.3\",\n    \"@types/chokidar\": \"^1.7.5\",\n    \"@types/clean-css\": \"^3.4.29\",\n    \"@types/express\": \"^4.11.1\",\n    \"@types/fs-extra\": \"^4.0.8\",\n    \"@types/glob\": \"^5.0.35\",\n    \"@types/jest\": \"^21.1.5\",\n    \"@types/mock-fs\": \"^3.6.30\",\n    \"@types/node\": \"^8.10.9\",\n    \"@types/node-sass\": \"^3.10.32\",\n    \"@types/rewire\": \"^2.5.27\",\n    \"@types/webpack\": \"^3.8.11\",\n    \"@types/ws\": \"^3.2.0\",\n    \"conventional-changelog-cli\": \"^1.3.22\",\n    \"github\": \"0.2.4\",\n    \"ionic-cz-conventional-changelog\": \"^1.0.0\",\n    \"jest\": \"^21.2.1\",\n    \"mock-fs\": \"^4.4.2\",\n    \"rewire\": \"^2.5.2\",\n    \"rimraf\": \"^2.6.1\",\n    \"rxjs\": \"^5.5.10\",\n    \"sw-toolbox\": \"^3.6.0\",\n    \"tslint-ionic-rules\": \"0.0.8\",\n    \"typescript\": \"~2.4.2\",\n    \"zone.js\": \"^0.8.26\"\n  },\n  \"repository\": {\n    \"type\": \"git\",\n    \"url\": \"git+https://github.com/ionic-team/ionic-app-scripts.git\"\n  },\n  \"bugs\": {\n    \"url\": \"https://github.com/ionic-team/ionic-app-scripts/issues\"\n  },\n  \"config\": {\n    \"commitizen\": {\n      \"path\": \"node_modules/ionic-cz-conventional-changelog\"\n    }\n  },\n  \"typings\": \"dist/index.d.ts\",\n  \"jest\": {\n    \"testEnvironment\": \"node\",\n    \"moduleFileExtensions\": [\n      \"ts\",\n      \"js\"\n    ],\n    \"transform\": {\n      \"^.+\\\\.(ts)$\": \"<rootDir>/preprocessor.js\"\n    },\n    \"testRegex\": \"/src/.*\\\\.spec\\\\.(ts|js)$\",\n    \"coverageDirectory\": \"coverage\"\n  }\n}\n"
  },
  {
    "path": "preprocessor.js",
    "content": "const tsc = require('typescript');\nconst tsConfig = require('./tsconfig.json');\n\nmodule.exports = {\n  process(src, path) {\n    if (path.endsWith('.ts')) {\n      return tsc.transpile(\n        src,\n        tsConfig.compilerOptions,\n        path,\n        []\n      );\n    }\n    return src;\n  },\n};\n"
  },
  {
    "path": "scripts/commit-changelog.js",
    "content": "var execSync = require('child_process').execSync;\n\nfunction main() {\n  try {\n    execSync('git add ./CHANGELOG.md');\n    execSync('git commit -m \"chore(changelog): update changelog for release\"');\n  } catch (ex) {\n    console.log('Failed to complete commiting changelog - ', ex.message);\n    process.exit(1);\n  }\n}"
  },
  {
    "path": "scripts/create-github-release.js",
    "content": "var path = require('path');\nvar execSync = require('child_process').execSync;\n\nvar GithubApi = require('github');\n\nvar changelogCommand = './node_modules/.bin/conventional-changelog -p angular';\n\nvar packageJsonPath = path.join(__dirname, '..', 'package.json');\nvar packageJson = require(packageJsonPath);\n\nvar github = new GithubApi({ version: '3.0.0'});\n\ngithub.authenticate({ type: 'oauth', token: process.env.GH_TOKEN });\n\nvar changelogContent = execSync(changelogCommand).toString();\n\ngithub.releases.createRelease({\n  owner: 'ionic-team',\n  repo: 'ionic-app-scripts',\n  target_commitish: 'master',\n  tag_name: 'v' + packageJson.version,\n  name: packageJson.version,\n  body: changelogContent,\n  prerelease: false\n}, function(err, result) {\n  if (err) {\n    console.log('[create-github-release] An error occurred: ' + err.message);\n    process.exit(1);\n  }\n  else {\n    console.log('[create-github-release]: Process succeeded');\n  }\n});"
  },
  {
    "path": "scripts/publish-nightly.js",
    "content": "var execSync = require('child_process').execSync;\nvar fs = require('fs');\nvar path = require('path');\n\nvar packageJsonPath = path.join(__dirname, '..', 'package.json');\nvar tempPackageJsonPath = path.join(__dirname, '..', 'package-orig.json');\nvar originalPackageJson = require(packageJsonPath);\n\n/*\n * This script assumes the `build` step was run prior to it\n */\n\nfunction backupOriginalPackageJson() {\n  var originalContent = JSON.stringify(originalPackageJson, null, 2);\n  fs.writeFileSync(tempPackageJsonPath, originalContent);\n}\n\nfunction createNightlyVersionInPackageJson() {\n  var originalVersion = originalPackageJson.version;\n  originalPackageJson.version = originalVersion + '-' + createTimestamp();\n  fs.writeFileSync(packageJsonPath, JSON.stringify(originalPackageJson, null, 2));\n}\n\nfunction revertPackageJson() {\n  var fileContent = fs.readFileSync(tempPackageJsonPath);\n  fileContent = fileContent + '\\n';\n  fs.writeFileSync(packageJsonPath, fileContent);\n  fs.unlinkSync(tempPackageJsonPath);\n}\n\nfunction createTimestamp() {\n  // YYYYMMDDHHMM\n  var d = new Date();\n  return d.getUTCFullYear() + // YYYY\n          ('0' + (d.getUTCMonth() + 1)).slice(-2) + // MM\n          ('0' + (d.getUTCDate())).slice(-2) + // DD\n          ('0' + (d.getUTCHours())).slice(-2) + // HH\n          ('0' + (d.getUTCMinutes())).slice(-2); // MM\n}\n\nfunction publishToNpm(tagName) {\n  var command = `npm publish --tag=${tagName} ${process.cwd()}`;\n  execSync(command);\n}\n\n\nfunction mainFunction() {\n  try {\n    let tagName = 'nightly';\n    if (process.argv.length >= 3) {\n      tagName = process.argv[2];\n    }\n    console.log(`Building ${tagName} ... BEGIN`);\n    console.log('Backing up the original package.json');\n    backupOriginalPackageJson();\n    console.log('Creating the nightly version of package.json');\n    createNightlyVersionInPackageJson();\n    console.log('Publishing to npm');\n    publishToNpm(tagName);\n    console.log('Restoring original package.json');\n    revertPackageJson();\n    console.log(`Building ${tagName}... DONE`);\n  }\n  catch (ex) {\n    console.log(`Something went wrong with publishing the nightly. This process modifies the package.json, so restore it before committing code! - ${ex.message}`);\n    process.exit(1);\n  }\n}\n\nmainFunction();"
  },
  {
    "path": "src/aot/aot-compiler.ts",
    "content": "import { readFileSync } from 'fs-extra';\nimport { extname, normalize, resolve } from 'path';\n\nimport 'reflect-metadata';\n\nimport {\n  CompilerHost,\n  CompilerOptions,\n  DiagnosticCategory,\n  ParsedCommandLine,\n  Program,\n  transpileModule,\n  TranspileOptions,\n  TranspileOutput,\n  createProgram\n} from 'typescript';\n\nimport { HybridFileSystem } from '../util/hybrid-file-system';\nimport { getInstance as getHybridFileSystem } from '../util/hybrid-file-system-factory';\nimport { getFileSystemCompilerHostInstance } from './compiler-host-factory';\nimport { FileSystemCompilerHost } from './compiler-host';\nimport { getFallbackMainContent, replaceBootstrapImpl } from './utils';\nimport { Logger } from '../logger/logger';\nimport { printDiagnostics, clearDiagnostics, DiagnosticsType } from '../logger/logger-diagnostics';\nimport { runTypeScriptDiagnostics } from '../logger/logger-typescript';\nimport { getTsConfig, TsConfig } from '../transpile';\nimport { BuildError } from '../util/errors';\nimport { changeExtension, readFileAsync } from '../util/helpers';\nimport { BuildContext, CodegenOptions, File, SemverVersion } from '../util/interfaces';\n\nexport async function runAot(context: BuildContext, options: AotOptions) {\n  const tsConfig = getTsConfig(context);\n\n  const angularCompilerOptions = Object.assign({}, {\n    basePath: options.rootDir,\n    genDir: options.rootDir,\n    entryPoint: options.entryPoint\n  });\n\n  const aggregateCompilerOption = Object.assign(tsConfig.options, angularCompilerOptions);\n\n  const fileSystem = getHybridFileSystem(false);\n  const compilerHost = getFileSystemCompilerHostInstance(tsConfig.options);\n  // todo, consider refactoring at some point\n  const tsProgram = createProgram(tsConfig.fileNames, tsConfig.options, compilerHost);\n\n  clearDiagnostics(context, DiagnosticsType.TypeScript);\n\n  if (isNg5(context.angularVersion)) {\n    await runNg5Aot(context, tsConfig, aggregateCompilerOption, compilerHost);\n  } else {\n    await runNg4Aot({\n      angularCompilerOptions: aggregateCompilerOption,\n      cliOptions: {\n        i18nFile: undefined,\n        i18nFormat: undefined,\n        locale: undefined,\n        basePath: options.rootDir,\n        missingTranslation: null\n      },\n      program: tsProgram,\n      compilerHost: compilerHost,\n      compilerOptions: tsConfig.options\n    });\n  }\n\n  errorCheckProgram(context, tsConfig, compilerHost, tsProgram);\n\n  // update bootstrap in main.ts\n  const mailFilePath = isNg5(context.angularVersion) ? changeExtension(options.entryPoint, '.js') : options.entryPoint;\n  const mainFile = context.fileCache.get(mailFilePath);\n  const modifiedBootstrapContent = replaceBootstrap(mainFile, options.appNgModulePath, options.appNgModuleClass, options);\n  mainFile.content = modifiedBootstrapContent;\n\n  if (isTranspileRequired(context.angularVersion)) {\n    transpileFiles(context, tsConfig, fileSystem);\n  }\n}\n\nfunction errorCheckProgram(context: BuildContext, tsConfig: TsConfig, compilerHost: FileSystemCompilerHost, cachedProgram: Program) {\n  // Create a new Program, based on the old one. This will trigger a resolution of all\n  // transitive modules, which include files that might just have been generated.\n  const program = createProgram(tsConfig.fileNames, tsConfig.options, compilerHost, cachedProgram);\n  const globalDiagnostics = program.getGlobalDiagnostics();\n  const tsDiagnostics = program.getSyntacticDiagnostics()\n                    .concat(program.getSemanticDiagnostics())\n                    .concat(program.getOptionsDiagnostics());\n\n  if (globalDiagnostics.length) {\n    const diagnostics = runTypeScriptDiagnostics(context, globalDiagnostics);\n    printDiagnostics(context, DiagnosticsType.TypeScript, diagnostics, true, false);\n    throw new BuildError(new Error('Failed to transpile TypeScript'));\n  }\n  if (tsDiagnostics.length) {\n    const diagnostics = runTypeScriptDiagnostics(context, tsDiagnostics);\n    printDiagnostics(context, DiagnosticsType.TypeScript, diagnostics, true, false);\n    throw new BuildError(new Error('Failed to transpile TypeScript'));\n  }\n  return program;\n}\n\nfunction replaceBootstrap(mainFile: File, appNgModulePath: string, appNgModuleClass: string, options: AotOptions) {\n  if (!mainFile) {\n    throw new BuildError(new Error(`Could not find entry point (bootstrap file) ${options.entryPoint}`));\n  }\n  let modifiedFileContent: string = null;\n  try {\n    Logger.debug('[AotCompiler] compile: Dynamically changing entry point content to AOT mode content');\n    modifiedFileContent = replaceBootstrapImpl(mainFile.path, mainFile.content, appNgModulePath, appNgModuleClass);\n  } catch (ex) {\n    Logger.debug(`Failed to parse bootstrap: `, ex.message);\n    Logger.warn(`Failed to parse and update ${options.entryPoint} content for AoT compilation.\n                For now, the default fallback content will be used instead.\n                Please consider updating ${options.entryPoint} with the content from the following link:\n                https://github.com/ionic-team/ionic2-app-base/tree/master/src/app/main.ts`);\n    modifiedFileContent = getFallbackMainContent();\n  }\n  return modifiedFileContent;\n}\n\nexport function isTranspileRequired(angularVersion: SemverVersion) {\n  return angularVersion.major <= 4;\n}\n\nexport function transpileFiles(context: BuildContext, tsConfig: TsConfig, fileSystem: HybridFileSystem) {\n  const tsFiles = context.fileCache.getAll().filter(file => extname(file.path) === '.ts' && file.path.indexOf('.d.ts') === -1);\n  for (const tsFile of tsFiles) {\n    Logger.debug(`[AotCompiler] transpileFiles: Transpiling file ${tsFile.path} ...`);\n    const transpileOutput = transpileFileContent(tsFile.path, tsFile.content, tsConfig.options);\n    const diagnostics = runTypeScriptDiagnostics(context, transpileOutput.diagnostics);\n    if (diagnostics.length) {\n      // darn, we've got some things wrong, transpile failed :(\n      printDiagnostics(context, DiagnosticsType.TypeScript, diagnostics, true, true);\n      throw new BuildError(new Error('Failed to transpile TypeScript'));\n    }\n\n    const jsFilePath = changeExtension(tsFile.path, '.js');\n    fileSystem.addVirtualFile(jsFilePath, transpileOutput.outputText);\n    fileSystem.addVirtualFile(jsFilePath + '.map', transpileOutput.sourceMapText);\n\n    Logger.debug(`[AotCompiler] transpileFiles: Transpiling file ${tsFile.path} ... DONE`);\n  }\n}\n\nfunction transpileFileContent(fileName: string, sourceText: string, options: CompilerOptions): TranspileOutput {\n  const transpileOptions: TranspileOptions = {\n    compilerOptions: options,\n    fileName: fileName,\n    reportDiagnostics: true\n  };\n\n  return transpileModule(sourceText, transpileOptions);\n}\n\nexport function isNg5(version: SemverVersion) {\n  return version.major >= 5;\n}\n\nexport async function runNg4Aot(options: CodegenOptions) {\n  const module = await import('@angular/compiler-cli');\n  return await module.__NGTOOLS_PRIVATE_API_2.codeGen({\n    angularCompilerOptions: options.angularCompilerOptions,\n    basePath: options.cliOptions.basePath,\n    program: options.program,\n    host: options.compilerHost,\n    compilerOptions: options.compilerOptions,\n    i18nFile: options.cliOptions.i18nFile,\n    i18nFormat: options.cliOptions.i18nFormat,\n    locale: options.cliOptions.locale,\n    readResource: (fileName: string) => {\n      return readFileAsync(fileName);\n    }\n  });\n}\n\nexport async function runNg5Aot(context: BuildContext, tsConfig: TsConfig, aggregateCompilerOptions: CompilerOptions, compilerHost: CompilerHost) {\n  const ngTools2 = await import('@angular/compiler-cli/ngtools2');\n  const angularCompilerHost = ngTools2.createCompilerHost({options: aggregateCompilerOptions, tsHost: compilerHost});\n  const program = ngTools2.createProgram({\n    rootNames: tsConfig.fileNames,\n    options: aggregateCompilerOptions,\n    host: angularCompilerHost,\n    oldProgram: null\n  });\n\n  await program.loadNgStructureAsync();\n\n  const transformations: any[] = [];\n\n  const transformers = {\n    beforeTs: transformations\n  };\n\n  const result = program.emit({ emitFlags: ngTools2.EmitFlags.Default, customTransformers: transformers });\n\n  const tsDiagnostics = program.getTsSyntacticDiagnostics()\n                                .concat(program.getTsOptionDiagnostics())\n                                .concat(program.getTsSemanticDiagnostics());\n\n  const angularDiagnostics = program.getNgStructuralDiagnostics()\n                              .concat(program.getNgOptionDiagnostics());\n\n\n  if (tsDiagnostics.length) {\n    const diagnostics = runTypeScriptDiagnostics(context, tsDiagnostics);\n    printDiagnostics(context, DiagnosticsType.TypeScript, diagnostics, true, false);\n    throw new BuildError(new Error('The Angular AoT build failed. See the issues above'));\n  }\n\n  if (angularDiagnostics.length) {\n    const diagnostics = runTypeScriptDiagnostics(context, angularDiagnostics as any[]);\n    printDiagnostics(context, DiagnosticsType.TypeScript, diagnostics, true, false);\n    throw new BuildError(new Error('The Angular AoT build failed. See the issues above'));\n  }\n}\n\nexport interface AotOptions {\n  tsConfigPath: string;\n  rootDir: string;\n  entryPoint: string;\n  appNgModulePath: string;\n  appNgModuleClass: string;\n}\n"
  },
  {
    "path": "src/aot/compiler-host-factory.ts",
    "content": "import { CompilerOptions } from 'typescript';\nimport { FileSystemCompilerHost } from './compiler-host';\nimport { getInstance as getFileSystemInstance } from '../util/hybrid-file-system-factory';\n\nlet instance: FileSystemCompilerHost = null;\n\nexport function getFileSystemCompilerHostInstance(options: CompilerOptions) {\n  if (!instance) {\n    instance = new FileSystemCompilerHost(options, getFileSystemInstance(false));\n  }\n  return instance;\n}\n"
  },
  {
    "path": "src/aot/compiler-host.ts",
    "content": "import { normalize } from 'path';\nimport { CancellationToken, CompilerHost, CompilerOptions, createCompilerHost, ScriptTarget, SourceFile } from 'typescript';\nimport { VirtualFileSystem } from '../util/interfaces';\nimport { getTypescriptSourceFile } from '../util/typescript-utils';\nimport { Logger } from '../logger/logger';\n\nexport interface OnErrorFn {\n  (message: string): void;\n}\n\nexport class FileSystemCompilerHost implements CompilerHost {\n  private diskCompilerHost: CompilerHost;\n\n  constructor(private options: CompilerOptions, private fileSystem: VirtualFileSystem, private setParentNodes = true) {\n    this.diskCompilerHost = createCompilerHost(this.options, this.setParentNodes);\n  }\n\n  fileExists(filePath: string): boolean {\n    filePath = normalize(filePath);\n    const fileContent = this.fileSystem.getFileContent(filePath);\n    if (fileContent) {\n      return true;\n    }\n    return this.diskCompilerHost.fileExists(filePath);\n  }\n\n  readFile(filePath: string): string {\n    filePath = normalize(filePath);\n    const fileContent = this.fileSystem.getFileContent(filePath);\n    if (fileContent) {\n      return fileContent;\n    }\n    return this.diskCompilerHost.readFile(filePath);\n  }\n\n  directoryExists(directoryPath: string): boolean {\n    directoryPath = normalize(directoryPath);\n    const stats = this.fileSystem.getDirectoryStats(directoryPath);\n    if (stats) {\n      return true;\n    }\n    return this.diskCompilerHost.directoryExists(directoryPath);\n  }\n\n  getFiles(directoryPath: string): string[] {\n    directoryPath = normalize(directoryPath);\n    return this.fileSystem.getFileNamesInDirectory(directoryPath);\n  }\n\n  getDirectories(directoryPath: string): string[] {\n    directoryPath = normalize(directoryPath);\n    const subdirs = this.fileSystem.getSubDirs(directoryPath);\n\n    let delegated: string[];\n    try {\n      delegated = this.diskCompilerHost.getDirectories(directoryPath);\n    } catch (e) {\n      delegated = [];\n    }\n    return delegated.concat(subdirs);\n  }\n\n  getSourceFile(filePath: string, languageVersion: ScriptTarget, onError?: OnErrorFn) {\n    filePath = normalize(filePath);\n    // we haven't created a source file for this yet, so try to use what's in memory\n    const fileContentFromMemory = this.fileSystem.getFileContent(filePath);\n    if (fileContentFromMemory) {\n      const typescriptSourceFile = getTypescriptSourceFile(filePath, fileContentFromMemory, languageVersion, this.setParentNodes);\n      return typescriptSourceFile;\n    }\n    const diskSourceFile = this.diskCompilerHost.getSourceFile(filePath, languageVersion, onError);\n    return diskSourceFile;\n  }\n\n  getCancellationToken(): CancellationToken {\n    return this.diskCompilerHost.getCancellationToken();\n  }\n\n  getDefaultLibFileName(options: CompilerOptions) {\n    return this.diskCompilerHost.getDefaultLibFileName(options);\n  }\n\n  writeFile(fileName: string, data: string, writeByteOrderMark: boolean, onError?: OnErrorFn) {\n    fileName = normalize(fileName);\n    Logger.debug(`[NgcCompilerHost] writeFile: adding ${fileName} to virtual file system`);\n    this.fileSystem.addVirtualFile(fileName, data);\n  }\n\n  getCurrentDirectory(): string {\n    return this.diskCompilerHost.getCurrentDirectory();\n  }\n\n  getCanonicalFileName(fileName: string): string {\n    return this.diskCompilerHost.getCanonicalFileName(fileName);\n  }\n\n  useCaseSensitiveFileNames(): boolean {\n    return this.diskCompilerHost.useCaseSensitiveFileNames();\n  }\n\n  getNewLine(): string {\n    return this.diskCompilerHost.getNewLine();\n  }\n}\n"
  },
  {
    "path": "src/aot/utils.ts",
    "content": "import { basename, dirname, join, normalize, relative, resolve } from 'path';\nimport {\n  CallExpression,\n  Identifier,\n  PropertyAccessExpression,\n  SyntaxKind,\n  ScriptTarget\n} from 'typescript';\n\nimport { appendBefore, checkIfFunctionIsCalled, getTypescriptSourceFile, findNodes, insertNamedImportIfNeeded, replaceImportModuleSpecifier, replaceNamedImport, replaceNode } from '../util/typescript-utils';\n\nexport function getFallbackMainContent() {\n  return `\nimport { platformBrowser } from '@angular/platform-browser';\nimport { enableProdMode } from '@angular/core';\n\nimport { AppModuleNgFactory } from './app.module.ngfactory';\n\nenableProdMode();\nplatformBrowser().bootstrapModuleFactory(AppModuleNgFactory);`;\n}\n\nfunction getBootstrapNodes(allCalls: CallExpression[]) {\n  return allCalls\n    .filter(call => call.expression.kind === SyntaxKind.PropertyAccessExpression)\n    .map(call => call.expression as PropertyAccessExpression)\n    .filter(access => {\n      return access.name.kind === SyntaxKind.Identifier\n          && access.name.text === 'bootstrapModule';\n    });\n}\n\nfunction replaceNgModuleClassName(filePath: string, fileContent: string, className: string) {\n  const sourceFile = getTypescriptSourceFile(filePath, fileContent, ScriptTarget.Latest, false);\n  const allCalls = findNodes(sourceFile, sourceFile, SyntaxKind.CallExpression, true) as CallExpression[];\n  const bootstraps = getBootstrapNodes(allCalls);\n  let modifiedContent = fileContent;\n  allCalls.filter(call => bootstraps.some(bs => bs === call.expression)).forEach((call: CallExpression) => {\n    modifiedContent = replaceNode(filePath, modifiedContent, call.arguments[0], className + 'NgFactory');\n  });\n  return modifiedContent;\n}\n\nfunction replacePlatformBrowser(filePath: string, fileContent: string) {\n  const sourceFile = getTypescriptSourceFile(filePath, fileContent, ScriptTarget.Latest, false);\n  const allCalls = findNodes(sourceFile, sourceFile, SyntaxKind.CallExpression, true) as CallExpression[];\n  const bootstraps = getBootstrapNodes(allCalls);\n  const calls: CallExpression[] = bootstraps.reduce((previous, access) => {\n      const expressions = findNodes(sourceFile, access, SyntaxKind.CallExpression, true) as CallExpression[];\n      return previous.concat(expressions);\n    }, [])\n    .filter((call: CallExpression) => {\n      return call.expression.kind === SyntaxKind.Identifier\n          && (call.expression as Identifier).text === 'platformBrowserDynamic';\n    });\n  let modifiedContent = fileContent;\n  calls.forEach(call => {\n    modifiedContent = replaceNode(filePath, modifiedContent, call.expression, 'platformBrowser');\n  });\n  return modifiedContent;\n}\n\nfunction checkForPlatformDynamicBrowser(filePath: string, fileContent: string) {\n  const sourceFile = getTypescriptSourceFile(filePath, fileContent, ScriptTarget.Latest, false);\n  const allCalls = findNodes(sourceFile, sourceFile, SyntaxKind.CallExpression, true) as CallExpression[];\n  const bootstraps = getBootstrapNodes(allCalls);\n  const calls: CallExpression[] = bootstraps.reduce((previous, access) => {\n      const expressions = findNodes(sourceFile, access, SyntaxKind.CallExpression, true) as CallExpression[];\n      return previous.concat(expressions);\n    }, [])\n    .filter((call: CallExpression) => {\n      return call.expression.kind === SyntaxKind.Identifier\n          && (call.expression as Identifier).text === 'platformBrowserDynamic';\n    });\n  return calls && calls.length;\n}\n\nfunction replaceBootstrapModuleFactory(filePath: string, fileContent: string) {\n  const sourceFile = getTypescriptSourceFile(filePath, fileContent, ScriptTarget.Latest, false);\n  const allCalls = findNodes(sourceFile, sourceFile, SyntaxKind.CallExpression, true) as CallExpression[];\n  const bootstraps = getBootstrapNodes(allCalls);\n  let modifiedContent = fileContent;\n  bootstraps.forEach((bs: PropertyAccessExpression) => {\n    modifiedContent = replaceNode(filePath, modifiedContent, bs.name, 'bootstrapModuleFactory');\n  });\n  return modifiedContent;\n}\n\nfunction getPlatformBrowserFunctionNode(filePath: string, fileContent: string) {\n  let modifiedFileContent = fileContent;\n  const sourceFile = getTypescriptSourceFile(filePath, modifiedFileContent, ScriptTarget.Latest, false);\n  const allCalls = findNodes(sourceFile, sourceFile, SyntaxKind.CallExpression, true) as CallExpression[];\n  const callsToPlatformBrowser = allCalls.filter(call => call.expression && call.expression.kind === SyntaxKind.Identifier && (call.expression as Identifier).text === 'platformBrowser');\n  const toAppend = `enableProdMode();\\n`;\n  if (callsToPlatformBrowser.length) {\n    modifiedFileContent = appendBefore(filePath, modifiedFileContent, callsToPlatformBrowser[0].expression, toAppend);\n  } else {\n    // just throw it at the bottom\n    modifiedFileContent += toAppend;\n  }\n  return modifiedFileContent;\n}\n\nfunction importAndEnableProdMode(filePath: string, fileContent: string) {\n  let modifiedFileContent = fileContent;\n  modifiedFileContent = insertNamedImportIfNeeded(filePath, modifiedFileContent, 'enableProdMode', '@angular/core');\n\n  const isCalled = checkIfFunctionIsCalled(filePath, modifiedFileContent, 'enableProdMode');\n  if (!isCalled) {\n    // go ahead and insert this\n    modifiedFileContent = getPlatformBrowserFunctionNode(filePath, modifiedFileContent);\n  }\n\n  return modifiedFileContent;\n}\n\nexport function replaceBootstrapImpl(filePath: string, fileContent: string, appNgModulePath: string, appNgModuleClassName: string) {\n  if (!fileContent.match(/\\bbootstrapModule\\b/)) {\n    throw new Error(`Could not find bootstrapModule in ${filePath}`);\n  }\n\n  const withoutExtension = join(dirname(appNgModulePath), basename(appNgModulePath, '.ts'));\n  const appModuleAbsoluteFileName = normalize(resolve(withoutExtension));\n  const withNgFactory = appModuleAbsoluteFileName + '.ngfactory';\n  const originalImport = './' + relative(dirname(filePath), appModuleAbsoluteFileName);\n  const ngFactryImport = './' + relative(dirname(filePath), withNgFactory);\n\n  if (!checkForPlatformDynamicBrowser(filePath, fileContent)) {\n    throw new Error(`Could not find any references to \"platformBrowserDynamic\" in ${filePath}`);\n  }\n\n  let modifiedFileContent = fileContent;\n  modifiedFileContent = replaceNgModuleClassName(filePath, modifiedFileContent, appNgModuleClassName);\n  modifiedFileContent = replacePlatformBrowser(filePath, modifiedFileContent);\n  modifiedFileContent = replaceBootstrapModuleFactory(filePath, modifiedFileContent);\n\n  modifiedFileContent = replaceNamedImport(filePath, modifiedFileContent, 'platformBrowserDynamic', 'platformBrowser');\n  modifiedFileContent = replaceNamedImport(filePath, modifiedFileContent, appNgModuleClassName, appNgModuleClassName + 'NgFactory');\n  modifiedFileContent = replaceImportModuleSpecifier(filePath, modifiedFileContent, '@angular/platform-browser-dynamic', '@angular/platform-browser');\n  modifiedFileContent = replaceImportModuleSpecifier(filePath, modifiedFileContent, originalImport, ngFactryImport);\n\n  // check if prod mode is imported and enabled\n  modifiedFileContent = importAndEnableProdMode(filePath, modifiedFileContent);\n\n  return modifiedFileContent;\n}\n"
  },
  {
    "path": "src/build/util.ts",
    "content": "import { join } from 'path';\n\nimport { getTsConfigAsync, TsConfig } from '../transpile';\nimport * as Constants from '../util/constants';\nimport { BuildError } from '../util/errors';\nimport { GlobResult, globAll } from '../util/glob-util';\nimport { getBooleanPropertyValue, getStringPropertyValue, readFileAsync, readJsonAsync, semverStringToObject } from '../util/helpers';\nimport { BuildContext, } from '../util/interfaces';\n\nexport function scanSrcTsFiles(context: BuildContext) {\n  const srcGlob = join(context.srcDir, '**', '*.ts');\n  const globs: string[] = [srcGlob];\n  const deepLinkDir = getStringPropertyValue(Constants.ENV_VAR_DEEPLINKS_DIR);\n  // these two will only not be equal in some weird cases like for building Ionic's demos with our current repository set-up\n  if (deepLinkDir !== context.srcDir) {\n    globs.push(join(deepLinkDir, '**', '*.ts'));\n  }\n  return globAll(globs).then((results: GlobResult[]) => {\n    const promises = results.map(result => {\n      const promise = readFileAsync(result.absolutePath);\n      promise.then((fileContent: string) => {\n        context.fileCache.set(result.absolutePath, { path: result.absolutePath, content: fileContent});\n      });\n      return promise;\n    });\n    return Promise.all(promises);\n  });\n}\n\n\nexport function validateTsConfigSettings(tsConfigFileContents: TsConfig) {\n  return new Promise((resolve, reject) => {\n    try {\n      const isValid = tsConfigFileContents.options &&\n        tsConfigFileContents.options.sourceMap === true;\n      if (!isValid) {\n        const error = new BuildError(['The \"tsconfig.json\" file must have compilerOptions.sourceMap set to true.',\n          'For more information please see the default Ionic project tsconfig.json file here:',\n          'https://github.com/ionic-team/ionic2-app-base/blob/master/tsconfig.json'].join('\\n'));\n        error.isFatal = true;\n        return reject(error);\n      }\n      resolve();\n    } catch (e) {\n      const error = new BuildError('The \"tsconfig.json\" file contains malformed JSON.');\n      error.isFatal = true;\n      return reject(error);\n    }\n  });\n}\n\nexport function validateRequiredFilesExist(context: BuildContext) {\n  return Promise.all([\n    readFileAsync(process.env[Constants.ENV_APP_ENTRY_POINT]),\n    getTsConfigAsync(context, process.env[Constants.ENV_TS_CONFIG])\n  ]).catch((error) => {\n    if (error.code === 'ENOENT' && error.path === process.env[Constants.ENV_APP_ENTRY_POINT]) {\n      error = new BuildError(`${error.path} was not found. The \"main.dev.ts\" and \"main.prod.ts\" files have been deprecated. Please create a new file \"main.ts\" containing the content of \"main.dev.ts\", and then delete the deprecated files.\n                            For more information, please see the default Ionic project main.ts file here:\n                            https://github.com/ionic-team/ionic2-app-base/tree/master/src/app/main.ts`);\n      error.isFatal = true;\n      throw error;\n    }\n    if (error.code === 'ENOENT' && error.path === process.env[Constants.ENV_TS_CONFIG]) {\n      error = new BuildError([`${error.path} was not found. The \"tsconfig.json\" file is missing. This file is required.`,\n        'For more information please see the default Ionic project tsconfig.json file here:',\n        'https://github.com/ionic-team/ionic2-app-base/blob/master/tsconfig.json'].join('\\n'));\n      error.isFatal = true;\n      throw error;\n    }\n    error.isFatal = true;\n    throw error;\n  });\n}\n\nexport async function readVersionOfDependencies(context: BuildContext) {\n  // read the package.json version from ionic, angular/core, and typescript\n  const promises: Promise<any>[] = [];\n  promises.push(readPackageVersion(context.angularCoreDir));\n  if (!getBooleanPropertyValue(Constants.ENV_SKIP_IONIC_ANGULAR_VERSION)) {\n    promises.push(readPackageVersion(context.ionicAngularDir));\n  }\n  promises.push(readPackageVersion(context.typescriptDir));\n\n  const versions = await Promise.all(promises);\n  context.angularVersion = semverStringToObject(versions[0]);\n  if (!getBooleanPropertyValue(Constants.ENV_SKIP_IONIC_ANGULAR_VERSION)) {\n    context.ionicAngularVersion = semverStringToObject(versions[1]);\n  }\n  // index could be 1 or 2 depending on if you read ionic-angular, its always the last one bro\n  context.typescriptVersion = semverStringToObject(versions[versions.length - 1]);\n}\n\nexport async function readPackageVersion(packageDir: string) {\n  const packageJsonPath = join(packageDir, 'package.json');\n  const packageObject = await readJsonAsync(packageJsonPath);\n  return packageObject['version'];\n}\n"
  },
  {
    "path": "src/build.spec.ts",
    "content": "import * as Constants from './util/constants';\nimport { BuildContext } from './util/interfaces';\nimport * as helpers from './util/helpers';\nimport * as build from './build';\nimport * as buildUtils from './build/util';\n\nimport * as bundle from './bundle';\nimport * as copy from './copy';\nimport * as clean from './clean';\nimport * as deepLinking from './deep-linking';\nimport * as lint from './lint';\nimport * as minify from './minify';\nimport * as ngc from './ngc';\nimport * as postprocess from './postprocess';\nimport * as preprocess from './preprocess';\nimport * as sass from './sass';\nimport * as transpile from './transpile';\n\ndescribe('build', () => {\n  beforeEach(() => {\n    spyOn(clean, 'clean');\n    spyOn(helpers, helpers.readFileAsync.name).and.returnValue(Promise.resolve());\n    spyOn(transpile, transpile.getTsConfigAsync.name).and.callFake(() => {\n      return Promise.resolve({\n        'options': {\n          'sourceMap': true\n        }\n      });\n    });\n\n    spyOn(buildUtils, buildUtils.scanSrcTsFiles.name).and.returnValue(Promise.resolve());\n    spyOn(buildUtils, buildUtils.validateRequiredFilesExist.name).and.returnValue(Promise.resolve(['fileOneContent', 'fileTwoContent']));\n    spyOn(buildUtils, buildUtils.validateTsConfigSettings.name).and.returnValue(Promise.resolve());\n    spyOn(buildUtils, buildUtils.readVersionOfDependencies.name).and.returnValue(Promise.resolve());\n    spyOn(bundle, bundle.bundle.name).and.returnValue(Promise.resolve());\n    spyOn(copy, copy.copy.name).and.returnValue(Promise.resolve());\n    spyOn(deepLinking, deepLinking.deepLinking.name).and.returnValue(Promise.resolve());\n    spyOn(minify, minify.minifyCss.name).and.returnValue(Promise.resolve());\n    spyOn(minify, minify.minifyJs.name).and.returnValue(Promise.resolve());\n    spyOn(lint, lint.lint.name).and.returnValue(Promise.resolve());\n    spyOn(ngc, ngc.ngc.name).and.returnValue(Promise.resolve());\n    spyOn(postprocess, postprocess.postprocess.name).and.returnValue(Promise.resolve());\n    spyOn(preprocess, preprocess.preprocess.name).and.returnValue(Promise.resolve());\n    spyOn(sass, sass.sass.name).and.returnValue(Promise.resolve());\n    spyOn(transpile, transpile.transpile.name).and.returnValue(Promise.resolve());\n  });\n\n  it('should do a prod build', () => {\n    let context: BuildContext = {\n      isProd: true,\n      optimizeJs: true,\n      runMinifyJs: true,\n      runMinifyCss: true,\n      runAot: true\n    };\n\n    const getBooleanPropertyValueSpy = spyOn(helpers, helpers.getBooleanPropertyValue.name).and.returnValue(true);\n\n    return build.build(context).then(() => {\n      expect(buildUtils.scanSrcTsFiles).toHaveBeenCalled();\n      expect(copy.copy).toHaveBeenCalled();\n      expect(deepLinking.deepLinking).toHaveBeenCalled();\n      expect(ngc.ngc).toHaveBeenCalled();\n      expect(bundle.bundle).toHaveBeenCalled();\n      expect(minify.minifyJs).toHaveBeenCalled();\n      expect(sass.sass).toHaveBeenCalled();\n      expect(minify.minifyCss).toHaveBeenCalled();\n      expect(lint.lint).toHaveBeenCalled();\n      expect(getBooleanPropertyValueSpy.calls.all()[1].args[0]).toEqual(Constants.ENV_ENABLE_LINT);\n\n      expect(transpile.transpile).not.toHaveBeenCalled();\n    });\n  });\n\n  it('should do a dev build', () => {\n    let context: BuildContext = {\n      isProd: false,\n      optimizeJs: false,\n      runMinifyJs: false,\n      runMinifyCss: false,\n      runAot: false\n    };\n\n    const getBooleanPropertyValueSpy = spyOn(helpers, helpers.getBooleanPropertyValue.name).and.returnValue(true);\n\n    return build.build(context).then(() => {\n      expect(buildUtils.scanSrcTsFiles).toHaveBeenCalled();\n      expect(copy.copy).toHaveBeenCalled();\n      expect(deepLinking.deepLinking).toHaveBeenCalled();\n      expect(transpile.transpile).toHaveBeenCalled();\n      expect(bundle.bundle).toHaveBeenCalled();\n      expect(sass.sass).toHaveBeenCalled();\n      expect(lint.lint).toHaveBeenCalled();\n      expect(getBooleanPropertyValueSpy.calls.all()[1].args[0]).toEqual(Constants.ENV_ENABLE_LINT);\n      expect(postprocess.postprocess).toHaveBeenCalled();\n      expect(preprocess.preprocess).toHaveBeenCalled();\n      expect(ngc.ngc).not.toHaveBeenCalled();\n      expect(minify.minifyJs).not.toHaveBeenCalled();\n      expect(minify.minifyCss).not.toHaveBeenCalled();\n    });\n  });\n\n  it('should skip lint', () => {\n    let context: BuildContext = {\n      isProd: false,\n      optimizeJs: false,\n      runMinifyJs: false,\n      runMinifyCss: false,\n      runAot: false\n    };\n\n    const getBooleanPropertyValueSpy = spyOn(helpers, helpers.getBooleanPropertyValue.name).and.returnValue(false);\n\n    return build.build(context).then(() => {\n      expect(buildUtils.scanSrcTsFiles).toHaveBeenCalled();\n      expect(copy.copy).toHaveBeenCalled();\n      expect(transpile.transpile).toHaveBeenCalled();\n      expect(bundle.bundle).toHaveBeenCalled();\n      expect(sass.sass).toHaveBeenCalled();\n      expect(lint.lint).not.toHaveBeenCalled();\n      expect(getBooleanPropertyValueSpy.calls.all()[1].args[0]).toEqual(Constants.ENV_ENABLE_LINT);\n      expect(postprocess.postprocess).toHaveBeenCalled();\n      expect(preprocess.preprocess).toHaveBeenCalled();\n      expect(ngc.ngc).not.toHaveBeenCalled();\n      expect(minify.minifyJs).not.toHaveBeenCalled();\n      expect(minify.minifyCss).not.toHaveBeenCalled();\n    });\n  });\n});\n\ndescribe('test project requirements before building', () => {\n  it('should fail if APP_ENTRY_POINT file does not exist', () => {\n    process.env[Constants.ENV_APP_ENTRY_POINT] = 'src/app/main.ts';\n    process.env[Constants.ENV_TS_CONFIG] = 'tsConfig.js';\n    const error = new Error('App entry point was not found');\n\n    spyOn(helpers, 'readFileAsync').and.returnValue(Promise.reject(error));\n\n    return build.build({}).catch((e) => {\n      expect(helpers.readFileAsync).toHaveBeenCalledTimes(1);\n      expect(e).toEqual(error);\n    });\n  });\n\n  it('should fail if IONIC_TS_CONFIG file does not exist', () => {\n    process.env[Constants.ENV_APP_ENTRY_POINT] = 'src/app/main.ts';\n    process.env[Constants.ENV_TS_CONFIG] = 'tsConfig.js';\n    const error = new Error('Config was not found');\n\n    spyOn(helpers, helpers.readFileAsync.name).and.returnValues(Promise.resolve());\n    spyOn(transpile, transpile.getTsConfigAsync.name).and.returnValues(Promise.reject(error));\n\n    return build.build({}).catch((e) => {\n      expect(transpile.getTsConfigAsync).toHaveBeenCalledTimes(1);\n      expect(helpers.readFileAsync).toHaveBeenCalledTimes(1);\n      expect(e).toEqual(error);\n    });\n  });\n\n  it('should fail fataly if IONIC_TS_CONFIG file does not contain valid JSON', () => {\n    process.env[Constants.ENV_APP_ENTRY_POINT] = 'src/app/main.ts';\n    process.env[Constants.ENV_TS_CONFIG] = 'tsConfig.js';\n    spyOn(transpile, transpile.getTsConfigAsync.name).and.callFake(() => {\n      return Promise.resolve(`{\n        \"options\" {\n          \"sourceMap\": false\n        }\n      }\n      `);\n    });\n    spyOn(buildUtils, buildUtils.scanSrcTsFiles.name).and.returnValue(Promise.resolve());\n    spyOn(buildUtils, buildUtils.readVersionOfDependencies.name).and.returnValue(Promise.resolve());\n\n    return build.build({}).catch((e) => {\n      expect(transpile.getTsConfigAsync).toHaveBeenCalledTimes(1);\n      expect(e.isFatal).toBeTruthy();\n    });\n  });\n\n  it('should fail fataly if IONIC_TS_CONFIG file does not contain compilerOptions.sourceMap === true', () => {\n    process.env[Constants.ENV_APP_ENTRY_POINT] = 'src/app/main.ts';\n    process.env[Constants.ENV_TS_CONFIG] = 'tsConfig.js';\n    spyOn(transpile, transpile.getTsConfigAsync.name).and.callFake(() => {\n      return Promise.resolve(`{\n        \"options\": {\n          \"sourceMap\": false\n        }\n      }\n      `);\n    });\n    spyOn(buildUtils, buildUtils.scanSrcTsFiles.name).and.returnValue(Promise.resolve());\n    spyOn(buildUtils, buildUtils.readVersionOfDependencies.name).and.returnValue(Promise.resolve());\n\n    return build.build({}).catch((e) => {\n      expect(transpile.getTsConfigAsync).toHaveBeenCalledTimes(1);\n      expect(e.isFatal).toBeTruthy();\n    });\n  });\n});\n"
  },
  {
    "path": "src/build.ts",
    "content": "import { join } from 'path';\n\nimport {\n  readVersionOfDependencies,\n  scanSrcTsFiles,\n  validateRequiredFilesExist,\n  validateTsConfigSettings\n} from './build/util';\n\n\nimport { bundle, bundleUpdate } from './bundle';\nimport { clean } from './clean';\nimport { copy } from './copy';\nimport { deepLinking, deepLinkingUpdate } from './deep-linking';\nimport { lint, lintUpdate } from './lint';\nimport { Logger } from './logger/logger';\nimport { minifyCss, minifyJs } from './minify';\nimport { ngc } from './ngc';\nimport { getTsConfigAsync, TsConfig } from './transpile';\nimport { postprocess } from './postprocess';\nimport { preprocess, preprocessUpdate } from './preprocess';\nimport { sass, sassUpdate } from './sass';\nimport { templateUpdate } from './template';\nimport { transpile, transpileUpdate, transpileDiagnosticsOnly } from './transpile';\nimport * as Constants from './util/constants';\nimport { BuildError } from './util/errors';\nimport { emit, EventType } from './util/events';\nimport { getBooleanPropertyValue, readFileAsync, setContext } from './util/helpers';\nimport { BuildContext, BuildState, BuildUpdateMessage, ChangedFile } from './util/interfaces';\n\nexport function build(context: BuildContext) {\n  setContext(context);\n  const logger = new Logger(`build ${(context.isProd ? 'prod' : 'dev')}`);\n\n  return buildWorker(context)\n    .then(() => {\n      // congrats, we did it!  (•_•) / ( •_•)>⌐■-■ / (⌐■_■)\n      logger.finish();\n    })\n    .catch(err => {\n      if (err.isFatal) { throw err; }\n      throw logger.fail(err);\n    });\n}\n\nasync function buildWorker(context: BuildContext) {\n  const promises: Promise<any>[] = [];\n  promises.push(validateRequiredFilesExist(context));\n  promises.push(readVersionOfDependencies(context));\n  const results = await Promise.all(promises);\n  const tsConfigContents = results[0][1];\n  await validateTsConfigSettings(tsConfigContents);\n  await buildProject(context);\n\n}\n\nfunction buildProject(context: BuildContext) {\n  // sync empty the www/build directory\n  clean(context);\n\n  buildId++;\n\n  const copyPromise = copy(context);\n\n  return scanSrcTsFiles(context)\n    .then(() => {\n      if (getBooleanPropertyValue(Constants.ENV_PARSE_DEEPLINKS)) {\n        return deepLinking(context);\n      }\n    })\n    .then(() => {\n      const compilePromise = (context.runAot) ? ngc(context) : transpile(context);\n      return compilePromise;\n    })\n    .then(() => {\n      return preprocess(context);\n    })\n    .then(() => {\n      return bundle(context);\n    })\n    .then(() => {\n      const minPromise = (context.runMinifyJs) ? minifyJs(context) : Promise.resolve();\n      const sassPromise = sass(context)\n        .then(() => {\n          return (context.runMinifyCss) ? minifyCss(context) : Promise.resolve();\n        });\n\n      return Promise.all([\n        minPromise,\n        sassPromise,\n        copyPromise\n      ]);\n    })\n    .then(() => {\n      return postprocess(context);\n    })\n    .then(() => {\n      if (getBooleanPropertyValue(Constants.ENV_ENABLE_LINT)) {\n        // kick off the tslint after everything else\n        // nothing needs to wait on its completion unless bailing on lint error is enabled\n        const result = lint(context, null, false);\n        if (getBooleanPropertyValue(Constants.ENV_BAIL_ON_LINT_ERROR)) {\n          return result;\n        }\n      }\n    })\n    .catch(err => {\n      throw new BuildError(err);\n    });\n}\n\nexport function buildUpdate(changedFiles: ChangedFile[], context: BuildContext) {\n  return new Promise(resolve => {\n    const logger = new Logger('build');\n\n    buildId++;\n\n    const buildUpdateMsg: BuildUpdateMessage = {\n      buildId: buildId,\n      reloadApp: false\n    };\n    emit(EventType.BuildUpdateStarted, buildUpdateMsg);\n\n    function buildTasksDone(resolveValue: BuildTaskResolveValue) {\n      // all build tasks have been resolved or one of them\n      // bailed early, stopping all others to not run\n\n      parallelTasksPromise.then(() => {\n        // all parallel tasks are also done\n        // so now we're done done\n        const buildUpdateMsg: BuildUpdateMessage = {\n          buildId: buildId,\n          reloadApp: resolveValue.requiresAppReload\n        };\n        emit(EventType.BuildUpdateCompleted, buildUpdateMsg);\n\n        if (!resolveValue.requiresAppReload) {\n          // just emit that only a certain file changed\n          // this one is useful when only a sass changed happened\n          // and the webpack only needs to livereload the css\n          // but does not need to do a full page refresh\n          emit(EventType.FileChange, resolveValue.changedFiles);\n        }\n\n        let requiresLintUpdate = false;\n        for (const changedFile of changedFiles) {\n          if (changedFile.ext === '.ts') {\n            if (changedFile.event === 'change' || changedFile.event === 'add') {\n              requiresLintUpdate = true;\n              break;\n            }\n          }\n        }\n        if (requiresLintUpdate) {\n          // a ts file changed, so let's lint it too, however\n          // this task should run as an after thought\n          if (getBooleanPropertyValue(Constants.ENV_ENABLE_LINT)) {\n            lintUpdate(changedFiles, context, false);\n          }\n        }\n\n        logger.finish('green', true);\n        Logger.newLine();\n\n        // we did it!\n        resolve();\n      });\n    }\n\n    // kick off all the build tasks\n    // and the tasks that can run parallel to all the build tasks\n    const buildTasksPromise = buildUpdateTasks(changedFiles, context);\n    const parallelTasksPromise = buildUpdateParallelTasks(changedFiles, context);\n\n    // whether it was resolved or rejected, we need to do the same thing\n    buildTasksPromise\n      .then(buildTasksDone)\n      .catch(() => {\n        buildTasksDone({\n          requiresAppReload: false,\n          changedFiles: changedFiles\n        });\n      });\n  });\n}\n\n/**\n * Collection of all the build tasks than need to run\n * Each task will only run if it's set with eacn BuildState.\n */\nfunction buildUpdateTasks(changedFiles: ChangedFile[], context: BuildContext) {\n  const resolveValue: BuildTaskResolveValue = {\n    requiresAppReload: false,\n    changedFiles: []\n  };\n\n  return loadFiles(changedFiles, context)\n    .then(() => {\n      // DEEP LINKING\n      if (getBooleanPropertyValue(Constants.ENV_PARSE_DEEPLINKS)) {\n        return deepLinkingUpdate(changedFiles, context);\n      }\n    })\n    .then(() => {\n      // TEMPLATE\n      if (context.templateState === BuildState.RequiresUpdate) {\n        resolveValue.requiresAppReload = true;\n        return templateUpdate(changedFiles, context);\n      }\n      // no template updates required\n      return Promise.resolve();\n\n    })\n    .then(() => {\n      // TRANSPILE\n      if (context.transpileState === BuildState.RequiresUpdate) {\n        resolveValue.requiresAppReload = true;\n        // we've already had a successful transpile once, only do an update\n        // not that we've also already started a transpile diagnostics only\n        // build that only needs to be completed by the end of buildUpdate\n        return transpileUpdate(changedFiles, context);\n\n      } else if (context.transpileState === BuildState.RequiresBuild) {\n        // run the whole transpile\n        resolveValue.requiresAppReload = true;\n        return transpile(context);\n      }\n      // no transpiling required\n      return Promise.resolve();\n\n    })\n    .then(() => {\n      // PREPROCESS\n      return preprocessUpdate(changedFiles, context);\n    })\n    .then(() => {\n      // BUNDLE\n      if (context.bundleState === BuildState.RequiresUpdate) {\n        // we need to do a bundle update\n        resolveValue.requiresAppReload = true;\n        return bundleUpdate(changedFiles, context);\n\n      } else if (context.bundleState === BuildState.RequiresBuild) {\n        // we need to do a full bundle build\n        resolveValue.requiresAppReload = true;\n        return bundle(context);\n      }\n      // no bundling required\n      return Promise.resolve();\n\n    })\n    .then(() => {\n      // SASS\n      if (context.sassState === BuildState.RequiresUpdate) {\n        // we need to do a sass update\n        return sassUpdate(changedFiles, context).then(outputCssFile => {\n          const changedFile: ChangedFile = {\n            event: Constants.FILE_CHANGE_EVENT,\n            ext: '.css',\n            filePath: outputCssFile\n          };\n\n          context.fileCache.set(outputCssFile, { path: outputCssFile, content: outputCssFile });\n\n          resolveValue.changedFiles.push(changedFile);\n        });\n\n      } else if (context.sassState === BuildState.RequiresBuild) {\n        // we need to do a full sass build\n        return sass(context).then(outputCssFile => {\n          const changedFile: ChangedFile = {\n            event: Constants.FILE_CHANGE_EVENT,\n            ext: '.css',\n            filePath: outputCssFile\n          };\n\n          context.fileCache.set(outputCssFile, { path: outputCssFile, content: outputCssFile });\n\n          resolveValue.changedFiles.push(changedFile);\n        });\n      }\n      // no sass build required\n      return Promise.resolve();\n    })\n    .then(() => {\n      return resolveValue;\n    });\n}\n\nfunction loadFiles(changedFiles: ChangedFile[], context: BuildContext) {\n  // UPDATE IN-MEMORY FILE CACHE\n  let promises: Promise<any>[] = [];\n  for (const changedFile of changedFiles) {\n    if (changedFile.event === Constants.FILE_DELETE_EVENT) {\n      // remove from the cache on delete\n      context.fileCache.remove(changedFile.filePath);\n    } else {\n      // load the latest since the file changed\n      const promise = readFileAsync(changedFile.filePath);\n      promises.push(promise);\n      promise.then((content: string) => {\n        context.fileCache.set(changedFile.filePath, { path: changedFile.filePath, content: content });\n      });\n    }\n  }\n\n  return Promise.all(promises);\n}\n\ninterface BuildTaskResolveValue {\n  requiresAppReload: boolean;\n  changedFiles: ChangedFile[];\n}\n\n/**\n * parallelTasks are for any tasks that can run parallel to the entire\n * build, but we still need to make sure they've completed before we're\n * all done, it's also possible there are no parallelTasks at all\n */\nfunction buildUpdateParallelTasks(changedFiles: ChangedFile[], context: BuildContext) {\n  const parallelTasks: Promise<any>[] = [];\n\n  if (context.transpileState === BuildState.RequiresUpdate) {\n    parallelTasks.push(transpileDiagnosticsOnly(context));\n  }\n\n  return Promise.all(parallelTasks);\n}\n\nlet buildId = 0;\n"
  },
  {
    "path": "src/bundle.spec.ts",
    "content": "import * as bundle from './bundle';\nimport * as webpack from './webpack';\nimport * as Constants from './util/constants';\nimport { ChangedFile } from './util/interfaces';\n\ndescribe('bundle task', () => {\n\n  describe('bundle', () => {\n\n    it('should return the value webpack task returns', () => {\n      // arrange\n      spyOn(webpack, webpack.webpack.name).and.returnValue(Promise.resolve());\n      const context = { bundler: Constants.BUNDLER_WEBPACK};\n\n      // act\n      return bundle.bundle(context).then(() => {\n        // assert\n        expect(webpack.webpack).toHaveBeenCalled();\n      });\n    });\n\n    it('should throw when webpack throws', () => {\n      const errorText = 'simulating an error';\n      // arrange\n      spyOn(webpack, webpack.webpack.name).and.returnValue(Promise.reject(new Error(errorText)));\n      const context = { bundler: Constants.BUNDLER_WEBPACK};\n\n      // act\n      return bundle.bundle(context).then(() => {\n        throw new Error('Should never happen');\n      }).catch(err => {\n        // assert\n        expect(webpack.webpack).toHaveBeenCalled();\n        expect(err.message).toBe(errorText);\n      });\n    });\n  });\n\n  describe('bundleUpdate', () => {\n\n    it('should return the value webpack returns', () => {\n      // arrange\n      spyOn(webpack, webpack.webpackUpdate.name).and.returnValue(Promise.resolve());\n      const context = { bundler: Constants.BUNDLER_WEBPACK};\n      const changedFiles: ChangedFile[] = [];\n\n      // act\n      return bundle.bundleUpdate(changedFiles, context).then(() => {\n        // assert\n        expect(webpack.webpackUpdate).toHaveBeenCalledWith(changedFiles, context);\n      });\n    });\n\n    it('should throw when webpack throws', () => {\n      const errorText = 'simulating an error';\n      try {\n        // arrange\n        spyOn(webpack, webpack.webpackUpdate.name).and.returnValue(Promise.reject(new Error(errorText)));\n        const context = { bundler: Constants.BUNDLER_WEBPACK};\n        const changedFiles: ChangedFile[] = [];\n\n        // act\n        return bundle.bundleUpdate(changedFiles, context).then(() => {\n          throw new Error('Should never happen');\n        }).catch(err => {\n          // assert\n          expect(webpack.webpackUpdate).toHaveBeenCalled();\n          expect(err.message).toBe(errorText);\n        });\n\n      } catch (ex) {\n\n      }\n    });\n  });\n\n  describe('buildJsSourceMaps', () => {\n\n    it('should get false when devtool is null for webpack', () => {\n      // arrange\n      const config = { };\n      spyOn(webpack, webpack.getWebpackConfig.name).and.returnValue(config);\n      const context = { bundler: Constants.BUNDLER_WEBPACK};\n      // act\n      const result = bundle.buildJsSourceMaps(context);\n\n      // assert\n      expect(webpack.getWebpackConfig).toHaveBeenCalledWith(context, null);\n      expect(result).toEqual(false);\n    });\n\n    it('should get false when devtool is valid', () => {\n      // arrange\n      const config = { devtool: 'someValue'};\n      spyOn(webpack, webpack.getWebpackConfig.name).and.returnValue(config);\n      const context = { bundler: Constants.BUNDLER_WEBPACK};\n      // act\n      const result = bundle.buildJsSourceMaps(context);\n\n      // assert\n      expect(webpack.getWebpackConfig).toHaveBeenCalledWith(context, null);\n      expect(result).toEqual(true);\n    });\n  });\n\n  describe('getJsOutputDest', () => {\n\n    it('should get the value from webpack', () => {\n      // arrange\n      const returnValue = 'someString';\n      spyOn(webpack, webpack.getOutputDest.name).and.returnValue(returnValue);\n      const context = { bundler: Constants.BUNDLER_WEBPACK};\n      // act\n      const result = bundle.getJsOutputDest(context);\n\n      // assert\n      expect(webpack.getOutputDest).toHaveBeenCalledWith(context);\n      expect(result).toEqual(returnValue);\n    });\n  });\n});\n"
  },
  {
    "path": "src/bundle.ts",
    "content": "import { BuildContext, ChangedFile } from './util/interfaces';\nimport { BuildError, IgnorableError } from './util/errors';\nimport * as Constants from './util/constants';\nimport { webpack, webpackUpdate, getWebpackConfig, getOutputDest as webpackGetOutputDest } from './webpack';\n\n\nexport function bundle(context: BuildContext, configFile?: string) {\n  return bundleWorker(context, configFile)\n    .catch((err: Error) => {\n      throw new BuildError(err);\n    });\n}\n\n\nfunction bundleWorker(context: BuildContext, configFile: string) {\n  return webpack(context, configFile);\n}\n\n\nexport function bundleUpdate(changedFiles: ChangedFile[], context: BuildContext) {\n  return webpackUpdate(changedFiles, context)\n    .catch(err => {\n      if (err instanceof IgnorableError) {\n        throw err;\n      }\n      throw new BuildError(err);\n    });\n}\n\n\nexport function buildJsSourceMaps(context: BuildContext) {\n  const webpackConfig = getWebpackConfig(context, null);\n  return !!(webpackConfig.devtool && webpackConfig.devtool.length > 0);\n}\n\n\nexport function getJsOutputDest(context: BuildContext) {\n  return webpackGetOutputDest(context);\n}\n"
  },
  {
    "path": "src/clean.spec.ts",
    "content": "import * as fs from 'fs-extra';\nimport * as clean from './clean';\n\ndescribe('clean task', () => {\n\n  describe('clean', () => {\n    it('should empty the build directory', () => {\n      // arrage\n      spyOn(fs, fs.emptyDirSync.name).and.returnValue('things');\n      const context = { buildDir: 'something' };\n\n      // act\n      return clean.clean(context).then(() => {\n        // assert\n        expect(fs.emptyDirSync).toHaveBeenCalledWith(context.buildDir);\n      });\n    });\n\n    it('should throw when failing to empty dir', () => {\n      // arrage\n      spyOn(fs, fs.emptyDirSync.name).and.throwError('Simulating an error');\n      const context = { buildDir: 'something' };\n\n      // act\n      return clean.clean(context).catch((ex) => {\n        expect(ex instanceof Error).toBe(true);\n        expect(typeof ex.message).toBe('string');\n      });\n    });\n  });\n});\n"
  },
  {
    "path": "src/clean.ts",
    "content": "import { BuildContext } from './util/interfaces';\nimport { BuildError } from './util/errors';\nimport { emptyDirSync } from 'fs-extra';\nimport { Logger } from './logger/logger';\n\n\nexport function clean(context: BuildContext) {\n  return new Promise((resolve, reject) => {\n    const logger = new Logger('clean');\n\n    try {\n      Logger.debug(`[Clean] clean: cleaning ${context.buildDir}`);\n\n      emptyDirSync(context.buildDir);\n      logger.finish();\n\n    } catch (ex) {\n      reject(logger.fail(new BuildError(`Failed to clean directory ${context.buildDir} - ${ex.message}`)));\n    }\n    resolve();\n  });\n}\n"
  },
  {
    "path": "src/cleancss.spec.ts",
    "content": "import { join } from 'path';\nimport * as cleanCss from './cleancss';\n\nimport * as cleanCssFactory from './util/clean-css-factory';\nimport * as config from './util/config';\nimport * as helpers from './util/helpers';\nimport * as workerClient from './worker-client';\n\n\ndescribe('clean css task', () => {\n\n  describe('cleancss', () => {\n    it('should return when the worker returns', () => {\n      // arrange\n      const context = { };\n      const configFile: any = null;\n      const spy = spyOn(workerClient, workerClient.runWorker.name).and.returnValue(Promise.resolve());\n      // act\n      return (cleanCss as any).cleancss(context, null).then(() => {\n        // assert\n        expect(spy).toHaveBeenCalledWith('cleancss', 'cleancssWorker', context, configFile);\n      });\n    });\n\n    it('should throw when the worker throws', () => {\n      // arrange\n      const context = { };\n      const errorMessage = 'Simulating an error';\n      spyOn(workerClient, workerClient.runWorker.name).and.returnValue(Promise.reject(new Error(errorMessage)));\n\n      // act\n      return (cleanCss as any).cleancss(context, null).then(() => {\n        throw new Error('Should never get here');\n      }).catch((err: Error) => {\n        // assert\n        expect(err.message).toEqual(errorMessage);\n      });\n    });\n  });\n\n  describe('cleancssworker', () => {\n    it('should throw when reading the file throws', () => {\n       const errorMessage = 'simulating an error';\n      // arrange\n      const context = { buildDir: 'www'};\n      const cleanCssConfig = { sourceFileName: 'sourceFileName', destFileName: 'destFileName'};\n      spyOn(config, config.generateContext.name).and.returnValue(context);\n      spyOn(config, config.fillConfigDefaults.name).and.returnValue(cleanCssConfig);\n      spyOn(helpers, helpers.readFileAsync.name).and.returnValue(Promise.reject(new Error(errorMessage)));\n\n      // act\n      return (cleanCss as any).cleancssWorker(context, null).then(() => {\n        throw new Error('Should never get here');\n      }).catch((err: Error) => {\n        expect(err.message).toEqual(errorMessage);\n      });\n    });\n\n    it('should return what writeFileAsync returns', () => {\n      // arrange\n      const context = { buildDir: 'www'};\n      const cleanCssConfig = { sourceFileName: 'sourceFileName', destFileName: 'destFileName'};\n      const fileContent = 'content';\n      const minifiedContent = 'someContent';\n      spyOn(config, config.generateContext.name).and.returnValue(context);\n      spyOn(config, config.fillConfigDefaults.name).and.returnValue(cleanCssConfig);\n      spyOn(helpers, helpers.readFileAsync.name).and.returnValue(Promise.resolve(fileContent));\n      spyOn(helpers, helpers.writeFileAsync.name).and.returnValue(Promise.resolve());\n      spyOn(cleanCssFactory, cleanCssFactory.getCleanCssInstance.name).and.returnValue({\n        minify: (content: string, cb: Function) => {\n          cb(null, { styles: minifiedContent });\n        }\n      });\n\n      // act\n      return (cleanCss as any).cleancssWorker(context, null).then(() => {\n        // assert\n        expect(config.generateContext).toHaveBeenCalledWith(context);\n        expect(config.fillConfigDefaults).toHaveBeenCalledWith(null, (cleanCss as any).taskInfo.defaultConfigFile);\n        expect(helpers.readFileAsync).toHaveBeenCalledWith(join(context.buildDir, cleanCssConfig.sourceFileName));\n        expect(helpers.writeFileAsync).toHaveBeenCalledWith(join(context.buildDir, cleanCssConfig.destFileName), minifiedContent);\n      });\n    });\n  });\n\n  describe('runCleanCss', () => {\n    it('should reject when minification errors out', () => {\n      // arrange\n      const errorMessage = 'simulating an error';\n      const configFile = { options: {} };\n      const fileContent = 'fileContent';\n      const destinationFilePath = 'filePath';\n      const mockMinifier = {\n        minify: () => {}\n      };\n      const minifySpy = spyOn(mockMinifier, mockMinifier.minify.name);\n      spyOn(cleanCssFactory, cleanCssFactory.getCleanCssInstance.name).and.returnValue(mockMinifier);\n\n      // act\n      const promise = (cleanCss as any).runCleanCss(configFile, fileContent, destinationFilePath);\n      // call the callback from the spy's args\n      const callback = minifySpy.calls.mostRecent().args[1];\n      callback(new Error(errorMessage), null);\n\n      return promise.then(() => {\n        throw new Error('Should never get here');\n      }).catch((err: Error) => {\n        // assert\n        expect(err.message).toEqual(errorMessage);\n      });\n    });\n\n    it('should reject when minification has one or more errors', () => {\n      // arrange\n      const configFile = { options: {} };\n      const fileContent = 'fileContent';\n      const minificationResponse = {\n        errors: ['some error']\n      };\n      const destinationFilePath = 'filePath';\n      const mockMinifier = {\n        minify: () => {}\n      };\n      const minifySpy = spyOn(mockMinifier, mockMinifier.minify.name);\n      spyOn(cleanCssFactory, cleanCssFactory.getCleanCssInstance.name).and.returnValue(mockMinifier);\n\n      // act\n      const promise = (cleanCss as any).runCleanCss(configFile, fileContent, destinationFilePath);\n      // call the callback from the spy's args\n      const callback = minifySpy.calls.mostRecent().args[1];\n      callback(null, minificationResponse);\n\n      return promise.then(() => {\n        throw new Error('Should never get here');\n      }).catch((err: Error) => {\n        // assert\n        expect(err.message).toEqual(minificationResponse.errors[0]);\n      });\n    });\n\n    it('should return minified content', () => {\n      const configFile = { options: {} };\n      const fileContent = 'fileContent';\n      let minifySpy: jasmine.Spy = null;\n      const minificationResponse = {\n        styles: 'minifiedContent'\n      };\n      const destinationFilePath = 'filePath';\n      const mockMinifier = {\n        minify: () => {}\n      };\n      minifySpy = spyOn(mockMinifier, mockMinifier.minify.name);\n      spyOn(cleanCssFactory, cleanCssFactory.getCleanCssInstance.name).and.returnValue(mockMinifier);\n\n      // act\n      const promise = (cleanCss as any).runCleanCss(configFile, fileContent, destinationFilePath);\n      // call the callback from the spy's args\n      const callback = minifySpy.calls.mostRecent().args[1];\n      callback(null, minificationResponse);\n\n      return promise.then((result: string) => {\n        expect(result).toEqual(minificationResponse.styles);\n        expect(cleanCssFactory.getCleanCssInstance).toHaveBeenCalledWith(configFile.options);\n        expect(minifySpy.calls.mostRecent().args[0]).toEqual(fileContent);\n      });\n    });\n  });\n});\n\n"
  },
  {
    "path": "src/cleancss.ts",
    "content": "import { join } from 'path';\nimport { BuildContext, TaskInfo } from './util/interfaces';\nimport { BuildError } from './util/errors';\nimport { fillConfigDefaults, generateContext, getUserConfigFile } from './util/config';\nimport { Logger } from './logger/logger';\nimport { readFileAsync, writeFileAsync } from './util/helpers';\nimport * as workerClient from './worker-client';\nimport { CleanCssConfig, getCleanCssInstance } from './util/clean-css-factory';\n\n\nexport function cleancss(context: BuildContext, configFile?: string) {\n  const logger = new Logger('cleancss');\n  configFile = getUserConfigFile(context, taskInfo, configFile);\n  return workerClient.runWorker('cleancss', 'cleancssWorker', context, configFile).then(() => {\n    logger.finish();\n  }).catch(err => {\n    throw logger.fail(err);\n  });\n}\n\n\nexport function cleancssWorker(context: BuildContext, configFile: string): Promise<any> {\n  context = generateContext(context);\n  const config: CleanCssConfig = fillConfigDefaults(configFile, taskInfo.defaultConfigFile);\n  const srcFile = join(context.buildDir, config.sourceFileName);\n  const destFilePath = join(context.buildDir, config.destFileName);\n  Logger.debug(`[Clean CSS] cleancssWorker: reading source file ${srcFile}`);\n  return readFileAsync(srcFile).then(fileContent => {\n    return runCleanCss(config, fileContent);\n  }).then(minifiedContent => {\n    Logger.debug(`[Clean CSS] runCleanCss: writing file to disk ${destFilePath}`);\n    return writeFileAsync(destFilePath, minifiedContent);\n  });\n}\n\n// exporting for easier unit testing\nexport function runCleanCss(cleanCssConfig: CleanCssConfig, fileContent: string): Promise<string> {\n  return new Promise((resolve, reject) => {\n    const minifier = getCleanCssInstance(cleanCssConfig.options);\n    minifier.minify(fileContent, (err, minified) => {\n      if (err) {\n        reject(new BuildError(err));\n      } else if (minified.errors && minified.errors.length > 0) {\n        // just return the first error for now I guess\n        minified.errors.forEach(e => {\n          Logger.error(e);\n        });\n        reject(new BuildError(minified.errors[0]));\n      } else {\n        resolve(minified.styles);\n      }\n    });\n  });\n}\n\n// export for testing only\nexport const taskInfo: TaskInfo = {\n  fullArg: '--cleancss',\n  shortArg: '-e',\n  envVar: 'IONIC_CLEANCSS',\n  packageConfig: 'ionic_cleancss',\n  defaultConfigFile: 'cleancss.config'\n};\n"
  },
  {
    "path": "src/copy.spec.ts",
    "content": "import * as copy from './copy';\n\nimport * as config from './util/config';\n\ndescribe('copy task', () => {\n  describe('copyConfigToWatchConfig', () => {\n    it('should convert to watch config format', () => {\n      // arrange\n      const context = { };\n      const configFile = 'configFile';\n      const sampleConfig: copy.CopyConfig = {\n        copyAssets: {\n          src: ['{{SRC}}/assets/**/*'],\n          dest: '{{WWW}}/assets'\n        },\n        copyIndexContent: {\n          src: ['{{SRC}}/index.html', '{{SRC}}/manifest.json', '{{SRC}}/service-worker.js'],\n          dest: '{{WWW}}'\n        },\n        copyFonts: {\n          src: ['{{ROOT}}/node_modules/ionicons/dist/fonts/**/*', '{{ROOT}}/node_modules/ionic-angular/fonts/**/*'],\n          dest: '{{WWW}}/assets/fonts'\n        },\n        copyPolyfills: {\n          src: [`{{ROOT}}/node_modules/ionic-angular/polyfills/${process.env.POLLYFILL_NAME}.js`],\n          dest: '{{BUILD}}'\n        },\n        someOtherOption: {\n          src: ['{{ROOT}}/whatever'],\n          dest: '{{BUILD}}'\n        }\n      };\n      let combinedSource: string[] = [];\n      Object.keys(sampleConfig).forEach(entry => combinedSource = combinedSource.concat(sampleConfig[entry].src));\n\n      spyOn(config, config.generateContext.name).and.returnValue(context);\n      spyOn(config, config.getUserConfigFile.name).and.returnValue(configFile);\n      spyOn(config, config.fillConfigDefaults.name).and.returnValue(sampleConfig);\n\n      // act\n      const result = copy.copyConfigToWatchConfig(null);\n\n      // assert\n      expect(config.generateContext).toHaveBeenCalledWith(null);\n      expect(config.getUserConfigFile).toHaveBeenCalledWith(context, copy.taskInfo, '');\n      expect(config.fillConfigDefaults).toHaveBeenCalledWith(configFile, copy.taskInfo.defaultConfigFile);\n      (result.paths as string[]).forEach(glob => {\n        expect(combinedSource.indexOf(glob)).not.toEqual(-1);\n      });\n      expect(result.callback).toBeDefined();\n      expect(result.options).toBeDefined();\n    });\n  });\n});\n"
  },
  {
    "path": "src/copy.ts",
    "content": "import { mkdirpSync } from 'fs-extra';\nimport { dirname as pathDirname, join as pathJoin, relative as pathRelative, resolve as pathResolve } from 'path';\nimport { Logger } from './logger/logger';\nimport { fillConfigDefaults, generateContext, getUserConfigFile, replacePathVars } from './util/config';\nimport * as Constants from './util/constants';\nimport { emit, EventType } from './util/events';\nimport { generateGlobTasks, globAll, GlobObject, GlobResult } from './util/glob-util';\nimport { copyFileAsync, getBooleanPropertyValue, rimRafAsync, unlinkAsync } from './util/helpers';\nimport { BuildContext, ChangedFile, TaskInfo } from './util/interfaces';\nimport { Watcher, copyUpdate as watchCopyUpdate } from './watch';\n\nconst copyFilePathCache = new Map<string, CopyToFrom[]>();\n\nconst FILTER_OUT_DIRS_FOR_CLEAN = ['{{WWW}}', '{{BUILD}}'];\n\nexport function copy(context: BuildContext, configFile?: string) {\n  configFile = getUserConfigFile(context, taskInfo, configFile);\n\n  const logger = new Logger('copy');\n\n  return copyWorker(context, configFile)\n    .then(() => {\n      logger.finish();\n    })\n    .catch(err => {\n      throw logger.fail(err);\n    });\n}\n\nexport function copyWorker(context: BuildContext, configFile: string) {\n  const copyConfig: CopyConfig = fillConfigDefaults(configFile, taskInfo.defaultConfigFile);\n  const keys = Object.keys(copyConfig);\n  const directoriesToCreate = new Set<string>();\n  const toCopyList: CopyToFrom[] = [];\n  return Promise.resolve().then(() => {\n    // for each entry, make sure each glob in the list of globs has had string replacement performed on it\n    cleanConfigContent(keys, copyConfig, context);\n    return getFilesPathsForConfig(keys, copyConfig);\n  }).then((resultMap: Map<string, GlobResult[]>) => {\n    // sweet, we have the absolute path of the files in the glob, and the ability to get the relative path\n    // basically, we've got a stew goin'\n    return populateFileAndDirectoryInfo(resultMap, copyConfig, toCopyList, directoriesToCreate);\n  }).then(() => {\n    if (getBooleanPropertyValue(Constants.ENV_CLEAN_BEFORE_COPY)) {\n      cleanDirectories(context, directoriesToCreate);\n    }\n  }).then(() => {\n    // create the directories synchronously to avoid any disk locking issues\n    const directoryPathList = Array.from(directoriesToCreate);\n    for (const directoryPath of directoryPathList) {\n      mkdirpSync(directoryPath);\n    }\n  }).then(() => {\n    // sweet, the directories are created, so now let's stream the files\n    const promises: Promise<void>[] = [];\n    for (const file of toCopyList) {\n      cacheCopyData(file);\n      const promise = copyFileAsync(file.absoluteSourcePath, file.absoluteDestPath);\n      promise.then(() => {\n        Logger.debug(`Successfully copied ${file.absoluteSourcePath} to ${file.absoluteDestPath}`);\n      }).catch(err => {\n        Logger.warn(`Failed to copy ${file.absoluteSourcePath} to ${file.absoluteDestPath}`);\n      });\n      promises.push(promise);\n    }\n    return Promise.all(promises);\n  });\n}\n\nexport function copyUpdate(changedFiles: ChangedFile[], context: BuildContext) {\n  const logger = new Logger('copy update');\n  const configFile = getUserConfigFile(context, taskInfo, null);\n  const copyConfig: CopyConfig = fillConfigDefaults(configFile, taskInfo.defaultConfigFile);\n  const keys = Object.keys(copyConfig);\n  const directoriesToCreate = new Set<string>();\n  const toCopyList: CopyToFrom[] = [];\n  return Promise.resolve().then(() => {\n    changedFiles.forEach(changedFile => Logger.debug(`copyUpdate, event: ${changedFile.event}, path: ${changedFile.filePath}`));\n    // for each entry, make sure each glob in the list of globs has had string replacement performed on it\n    cleanConfigContent(keys, copyConfig, context);\n\n    return getFilesPathsForConfig(keys, copyConfig);\n  }).then((resultMap: Map<string, GlobResult[]>) => {\n    // sweet, we have the absolute path of the files in the glob, and the ability to get the relative path\n    // basically, we've got a stew goin'\n    return populateFileAndDirectoryInfo(resultMap, copyConfig, toCopyList, directoriesToCreate);\n  }).then(() => {\n    // first, process any deleted directories\n    const promises: Promise<void>[] = [];\n    const directoryDeletions = changedFiles.filter(changedFile => changedFile.event === 'unlinkDir');\n    directoryDeletions.forEach(changedFile => promises.push(processRemoveDir(changedFile)));\n    return Promise.all(promises);\n  }).then(() => {\n    // process any deleted files\n    const promises: Promise<any>[] = [];\n    const fileDeletions = changedFiles.filter(changedFile => changedFile.event === 'unlink');\n    fileDeletions.forEach(changedFile => promises.push(processRemoveFile(changedFile)));\n    return Promise.all(promises);\n  }).then(() => {\n    const promises: Promise<void>[] = [];\n    const additions = changedFiles.filter(changedFile => changedFile.event === 'change' || changedFile.event === 'add' || changedFile.event === 'addDir');\n    additions.forEach(changedFile => {\n      const matchingItems = toCopyList.filter(toCopyEntry => toCopyEntry.absoluteSourcePath === changedFile.filePath);\n      matchingItems.forEach(matchingItem => {\n        // create the directories first (if needed)\n        mkdirpSync(pathDirname(matchingItem.absoluteDestPath));\n        // cache the data and copy the files\n        cacheCopyData(matchingItem);\n        promises.push(copyFileAsync(matchingItem.absoluteSourcePath, matchingItem.absoluteDestPath));\n        emit(EventType.FileChange, additions);\n      });\n    });\n    return Promise.all(promises);\n  }).then(() => {\n    logger.finish('green', true);\n    Logger.newLine();\n  }).catch(err => {\n    throw logger.fail(err);\n  });\n}\n\nfunction cleanDirectories(context: BuildContext, directoriesToCreate: Set<string>) {\n  const filterOut = replacePathVars(context, FILTER_OUT_DIRS_FOR_CLEAN);\n  const directoryPathList = Array.from(directoriesToCreate);\n  // filter out any directories that we don't want to allow a clean on\n  const cleanableDirectories = directoryPathList.filter(directoryPath => {\n    for (const uncleanableDir of filterOut) {\n      if (uncleanableDir === directoryPath) {\n        return false;\n      }\n    }\n    return true;\n  });\n  return deleteDirectories(cleanableDirectories);\n}\n\nfunction deleteDirectories(directoryPaths: string[]) {\n  const promises: Promise<void>[] = [];\n  for (const directoryPath of directoryPaths) {\n   promises.push(rimRafAsync(directoryPath));\n  }\n  return Promise.all(promises);\n}\n\nfunction processRemoveFile(changedFile: ChangedFile) {\n  // delete any destination files that match the source file\n  const list = copyFilePathCache.get(changedFile.filePath) || [];\n  copyFilePathCache.delete(changedFile.filePath);\n  const promises: Promise<void[]>[] = [];\n  const deletedFilePaths: string[] = [];\n  list.forEach(copiedFile => {\n    const promise = unlinkAsync(copiedFile.absoluteDestPath);\n    promises.push(promise);\n    promise.catch(err => {\n      if (err && err.message && err.message.indexOf('ENOENT') >= 0) {\n        Logger.warn(`Failed to delete ${copiedFile.absoluteDestPath} because it doesn't exist`);\n        return;\n      }\n      throw err;\n    });\n    deletedFilePaths.push(copiedFile.absoluteDestPath);\n  });\n  emit(EventType.FileDelete, deletedFilePaths);\n  return Promise.all(promises).catch(err => {\n  });\n}\n\nfunction processRemoveDir(changedFile: ChangedFile): Promise<any> {\n  // remove any files from the cache where the dirname equals the provided path\n  const keysToRemove: string[] = [];\n  const directoriesToRemove = new Set<string>();\n  copyFilePathCache.forEach((copiedFiles: CopyToFrom[], filePath: string) => {\n    if (pathDirname(filePath) === changedFile.filePath) {\n      keysToRemove.push(filePath);\n      copiedFiles.forEach(copiedFile => directoriesToRemove.add(pathDirname(copiedFile.absoluteDestPath)));\n    }\n  });\n  keysToRemove.forEach(keyToRemove => copyFilePathCache.delete(keyToRemove));\n  // the entries are removed from the cache, now just rim raf the directoryPath\n  const promises: Promise<void>[] = [];\n  directoriesToRemove.forEach(directoryToRemove => {\n    promises.push(rimRafAsync(directoryToRemove));\n  });\n  emit(EventType.DirectoryDelete, Array.from(directoriesToRemove));\n  return Promise.all(promises);\n}\n\nfunction cacheCopyData(copyObject: CopyToFrom) {\n  let list = copyFilePathCache.get(copyObject.absoluteSourcePath);\n  if (!list) {\n    list = [];\n  }\n  list.push(copyObject);\n  copyFilePathCache.set(copyObject.absoluteSourcePath, list);\n}\n\nfunction getFilesPathsForConfig(copyConfigKeys: string[], copyConfig: CopyConfig): Promise<Map<String, GlobResult[]>> {\n  // execute the glob functions to determine what files match each glob\n  const srcToResultsMap = new Map<string, GlobResult[]>();\n  const promises: Promise<GlobResult[]>[] = [];\n  copyConfigKeys.forEach(key => {\n    const copyOptions = copyConfig[key];\n    if (copyOptions && copyOptions.src) {\n      const promise = globAll(copyOptions.src);\n      promises.push(promise);\n      promise.then(globResultList => {\n        srcToResultsMap.set(key, globResultList);\n      });\n    }\n  });\n\n  return Promise.all(promises).then(() => {\n    return srcToResultsMap;\n  });\n}\n\nfunction populateFileAndDirectoryInfo(resultMap: Map<string, GlobResult[]>, copyConfig: CopyConfig, toCopyList: CopyToFrom[], directoriesToCreate: Set<string>) {\n  resultMap.forEach((globResults: GlobResult[], dictionaryKey: string) => {\n    globResults.forEach(globResult => {\n      // get the relative path from the of each file from the glob\n      const relativePath = pathRelative(globResult.base, globResult.absolutePath);\n      // append the relative path to the dest\n      const destFileName = pathResolve(pathJoin(copyConfig[dictionaryKey].dest, relativePath));\n      // store the file information\n      toCopyList.push({\n        absoluteSourcePath: globResult.absolutePath,\n        absoluteDestPath: destFileName\n      });\n      const directoryToCreate = pathDirname(destFileName);\n      directoriesToCreate.add(directoryToCreate);\n    });\n  });\n}\n\nfunction cleanConfigContent(dictionaryKeys: string[], copyConfig: CopyConfig, context: BuildContext) {\n  dictionaryKeys.forEach(key => {\n    const copyOption = copyConfig[key];\n    if (copyOption && copyOption.src && copyOption.src.length) {\n      const cleanedUpSrc = replacePathVars(context, copyOption.src);\n      copyOption.src = cleanedUpSrc;\n      const cleanedUpDest = replacePathVars(context, copyOption.dest);\n      copyOption.dest = cleanedUpDest;\n    }\n  });\n}\n\nexport function copyConfigToWatchConfig(context: BuildContext): Watcher {\n  if (!context) {\n    context = generateContext(context);\n  }\n  const configFile = getUserConfigFile(context, taskInfo, '');\n  const copyConfig: CopyConfig = fillConfigDefaults(configFile, taskInfo.defaultConfigFile);\n  let results: GlobObject[] = [];\n  for (const key of Object.keys(copyConfig)) {\n    if (copyConfig[key] && copyConfig[key].src) {\n      const list = generateGlobTasks(copyConfig[key].src, {});\n      results = results.concat(list);\n    }\n  }\n\n  const paths: string[] = [];\n  let ignored: string[] = [];\n  for (const result of results) {\n    paths.push(result.pattern);\n    if (result.opts && result.opts.ignore) {\n      ignored = ignored.concat(result.opts.ignore);\n    }\n  }\n\n  return {\n    paths: paths,\n    options: {\n      ignored: ignored\n    },\n    callback: watchCopyUpdate\n  };\n}\n\nexport interface CopySrcToDestResult {\n  success: boolean;\n  src: string;\n  dest: string;\n  errorMessage: string;\n}\n\nexport const taskInfo: TaskInfo = {\n  fullArg: '--copy',\n  shortArg: '-y',\n  envVar: 'IONIC_COPY',\n  packageConfig: 'ionic_copy',\n  defaultConfigFile: 'copy.config'\n};\n\n\nexport interface CopyConfig {\n  [index: string]: CopyOptions;\n}\n\nexport interface CopyToFrom {\n  absoluteSourcePath: string;\n  absoluteDestPath: string;\n}\n\nexport interface CopyOptions {\n  // https://www.npmjs.com/package/fs-extra\n  src: string[];\n  dest: string;\n}\n"
  },
  {
    "path": "src/core/bundle-components.ts",
    "content": "import { BuildContext, CoreCompiler } from '../util/interfaces';\nimport { Logger } from '../logger/logger';\nimport * as fs from 'fs';\nimport * as path from 'path';\nimport * as nodeSass from 'node-sass';\nimport * as rollup from 'rollup';\nimport * as typescript from 'typescript';\nimport * as uglify from 'uglify-es';\nimport * as cleanCss from 'clean-css';\n\n\nexport function bundleCoreComponents(context: BuildContext) {\n  const compiler = getCoreCompiler(context);\n\n  if (!compiler) {\n    Logger.debug(`skipping core component bundling`);\n    return Promise.resolve();\n  }\n\n  const config = {\n    srcDir: context.coreDir,\n    destDir: context.buildDir,\n    attrCase: 'lower',\n    packages: {\n      cleanCss: cleanCss,\n      fs: fs,\n      path: path,\n      nodeSass: nodeSass,\n      rollup: rollup,\n      typescript: typescript,\n      uglify: uglify\n    },\n    watch: context.isWatch\n  };\n\n  return compiler.bundle(config).then(results => {\n    if (results.errors) {\n      results.errors.forEach((err: string) => {\n        Logger.error(`compiler.bundle, results: ${err}`);\n      });\n\n    } else if (results.componentRegistry) {\n      // add the component registry to the global window.Ionic\n      context.ionicGlobal = context.ionicGlobal || {};\n      context.ionicGlobal['components'] = results.componentRegistry;\n    }\n  }).catch(err => {\n    if (err) {\n      if (err.stack) {\n        Logger.error(`compiler.bundle: ${err.stack}`);\n      } else {\n        Logger.error(`compiler.bundle: ${err}`);\n      }\n    } else {\n      Logger.error(`compiler.bundle error`);\n    }\n  });\n}\n\n\nfunction getCoreCompiler(context: BuildContext): CoreCompiler {\n  try {\n    return require(context.coreCompilerFilePath);\n  } catch (e) {\n    Logger.debug(`error loading core compiler: ${context.coreCompilerFilePath}, ${e}`);\n  }\n  return null;\n}\n"
  },
  {
    "path": "src/core/inject-script.spec.ts",
    "content": "import { injectCoreHtml } from './inject-scripts';\n\n\ndescribe('Inject Scripts', () => {\n\n  describe('injectCoreHtml', () => {\n\n    it('should replace an existed injected script tag', () => {\n      const inputHtml = '' +\n        '<html>\\n' +\n        '<head>\\n' +\n        '  <script data-ionic=\"inject\">\\n' +\n        '    alert(11111);\\n' +\n        '  </script>\\n' +\n        '</head>\\n' +\n        '<body>\\n' +\n        '</body>\\n' +\n        '</html>';\n\n      const output = injectCoreHtml(inputHtml, '  <script data-ionic=\"inject\">\\n' +\n                                               '    alert(55555);\\n' +\n                                               '  </script>');\n\n      expect(output).toEqual(\n        '<html>\\n' +\n        '<head>\\n' +\n        '  <script data-ionic=\"inject\">\\n' +\n        '    alert(55555);\\n' +\n        '  </script>\\n' +\n        '</head>\\n' +\n        '<body>\\n' +\n        '</body>\\n' +\n        '</html>');\n    });\n\n    it('should replace only one existed injected script tag', () => {\n      const inputHtml = '' +\n        '<html>\\n' +\n        '<head>\\n' +\n        '  <script data-ionic=\"inject\">\\n' +\n        '    alert(11111);\\n' +\n        '  </script>\\n' +\n        '  <script>\\n' +\n        '    alert(222);\\n' +\n        '  </script>\\n' +\n        '</head>\\n' +\n        '<body>\\n' +\n        '</body>\\n' +\n        '</html>';\n\n      const output = injectCoreHtml(inputHtml, '  <script data-ionic=\"inject\">\\n' +\n                                               '    alert(55555);\\n' +\n                                               '  </script>');\n\n      expect(output).toEqual(\n        '<html>\\n' +\n        '<head>\\n' +\n        '  <script data-ionic=\"inject\">\\n' +\n        '    alert(55555);\\n' +\n        '  </script>\\n' +\n        '  <script>\\n' +\n        '    alert(222);\\n' +\n        '  </script>\\n' +\n        '</head>\\n' +\n        '<body>\\n' +\n        '</body>\\n' +\n        '</html>');\n    });\n\n    it('should add script to top of file when no html tag', () => {\n      const inputHtml = '' +\n        '<body>\\n' +\n        '</body>';\n\n      const output = injectCoreHtml(inputHtml, '<injected></injected>');\n\n      expect(output).toEqual(\n        '<injected></injected>\\n' +\n        '<body>\\n' +\n        '</body>');\n    });\n\n    it('should add script below <html> with attributes', () => {\n      const inputHtml = '' +\n        '<html dir=\"rtl\">\\n' +\n        '<body>\\n' +\n        '</body>\\n' +\n        '</html>';\n\n      const output = injectCoreHtml(inputHtml, '<injected></injected>');\n\n      expect(output).toEqual(\n        '<html dir=\"rtl\">\\n' +\n        '<injected></injected>\\n' +\n        '<body>\\n' +\n        '</body>\\n' +\n        '</html>');\n    });\n\n    it('should add script below <html> when no head tag', () => {\n      const inputHtml = '' +\n        '<html>\\n' +\n        '<body>\\n' +\n        '</body>\\n' +\n        '</html>';\n\n      const output = injectCoreHtml(inputHtml, '<injected></injected>');\n\n      expect(output).toEqual(\n        '<html>\\n' +\n        '<injected></injected>\\n' +\n        '<body>\\n' +\n        '</body>\\n' +\n        '</html>');\n    });\n\n    it('should add script below <head>', () => {\n      const inputHtml = '' +\n        '<html>\\n' +\n        '<head>\\n' +\n        '</head>\\n' +\n        '<body>\\n' +\n        '</body>\\n' +\n        '</html>';\n\n      const output = injectCoreHtml(inputHtml, '<injected></injected>');\n\n      expect(output).toEqual(\n        '<html>\\n' +\n        '<head>\\n' +\n        '<injected></injected>\\n' +\n        '</head>\\n' +\n        '<body>\\n' +\n        '</body>\\n' +\n        '</html>');\n    });\n\n    it('should add script below <head> with attributes and all caps tag', () => {\n      const inputHtml = '' +\n        '<html>\\n' +\n        '<HEAD data-attr=\"yup\">\\n' +\n        '</HEAD>\\n' +\n        '<body>\\n' +\n        '</body>\\n' +\n        '</html>';\n\n      const output = injectCoreHtml(inputHtml, '<injected></injected>');\n\n      expect(output).toEqual(\n        '<html>\\n' +\n        '<HEAD data-attr=\"yup\">\\n' +\n        '<injected></injected>\\n' +\n        '</HEAD>\\n' +\n        '<body>\\n' +\n        '</body>\\n' +\n        '</html>');\n    });\n\n  });\n\n});\n\n\n\n"
  },
  {
    "path": "src/core/inject-scripts.ts",
    "content": "import { BuildContext } from '../util/interfaces';\nimport { buildIonicGlobal } from './ionic-global';\nimport { readFileAsync, writeFileAsync } from '../util/helpers';\nimport { join } from 'path';\n\n\nexport function updateIndexHtml(context: BuildContext) {\n  const indexPath = join(context.wwwDir, context.wwwIndex);\n\n  return readFileAsync(indexPath).then(indexHtml => {\n    if (!indexHtml) {\n      return Promise.resolve(null);\n    }\n\n    indexHtml = injectCoreScripts(context, indexHtml);\n\n    return writeFileAsync(indexPath, indexHtml);\n  });\n}\n\n\nexport function injectCoreScripts(context: BuildContext, indexHtml: string) {\n  const inject = [];\n\n  inject.push(`  <script data-ionic=\"inject\">`);\n\n  inject.push(`    ${buildIonicGlobal(context)}`);\n\n  inject.push(`  </script>`);\n\n  return injectCoreHtml(indexHtml, inject.join('\\n'));\n}\n\n\nexport function injectCoreHtml(indexHtml: string, inject: string) {\n  // see if we can find an existing ionic script tag and replace it entirely\n  const existingTag = indexHtml.match(/<script data-ionic=\"inject\">[\\s\\S]*?<\\/script>/gi);\n  if (existingTag) {\n    return indexHtml.replace(existingTag[0], inject.trim());\n  }\n\n  // see if we can find the head tag and inject it immediate below it\n  const headTag = indexHtml.match(/<head[^>]*>/gi);\n  if (headTag) {\n    return indexHtml.replace(headTag[0], `${headTag[0]}\\n${inject}`);\n  }\n\n  // see if we can find the html tag and inject it immediate below it\n  const htmlTag = indexHtml.match(/<html[^>]*>/gi);\n  if (htmlTag) {\n    return indexHtml.replace(htmlTag[0], `${htmlTag[0]}\\n${inject}`);\n  }\n\n  return `${inject}\\n${indexHtml}`;\n}\n"
  },
  {
    "path": "src/core/ionic-global.spec.ts",
    "content": "import { BuildContext } from '../util/interfaces';\nimport { buildIonicGlobal } from './ionic-global';\n\n\ndescribe('Ionic Global', () => {\n\n  describe('buildIonicGlobal', () => {\n\n    it('should cache windowIonic', () => {\n      const ctx: BuildContext = {\n        rootDir: '/Users/elliemae/myapp',\n        wwwDir: '/Users/elliemae/myapp/www',\n        buildDir: '/Users/elliemae/myapp/www/build'\n      };\n\n      const r = buildIonicGlobal(ctx);\n\n      expect(r).toBeDefined();\n    });\n\n  });\n\n});\n"
  },
  {
    "path": "src/core/ionic-global.ts",
    "content": "import { BuildContext } from '../util/interfaces';\nimport { getSystemData, toUnixPath } from '../util/helpers';\n\n\nexport function buildIonicGlobal(context: BuildContext) {\n  context.ionicGlobal = context.ionicGlobal || {};\n\n  // gather data to add to window.Ionic\n  const systemData = getSystemData(context.rootDir);\n  if (systemData.ionicFramework) {\n    context.ionicGlobal['version'] = `'${systemData.ionicFramework}'`;\n  }\n  if (systemData.angularCore) {\n    context.ionicGlobal['angular'] = `'${systemData.angularCore}'`;\n  }\n  if (systemData.ionicNative) {\n    context.ionicGlobal['ionicNative'] = `'${systemData.ionicNative}'`;\n  }\n\n  let staticDir = toUnixPath(context.buildDir.replace(context.wwwDir, ''));\n  staticDir += '/';\n\n  if (staticDir.charAt(0) === '/') {\n    staticDir = staticDir.substring(1);\n  }\n\n  context.ionicGlobal['staticDir'] = `'${staticDir}'`;\n\n  // output the JS\n  let o = [\n    '(function(w){',\n    'var i=w.Ionic=w.Ionic||{};'\n  ];\n\n  Object.keys(context.ionicGlobal).forEach(key => {\n    o.push(`i.${key}=${context.ionicGlobal[key]};`);\n  });\n\n  o.push('})(window);');\n\n  return o.join('');\n}\n"
  },
  {
    "path": "src/declarations.d.ts",
    "content": "declare module 'autoprefixer';\ndeclare module 'chalk';\ndeclare module 'cross-spawn';\ndeclare module 'mime-types';\ndeclare module 'proxyquire';\ndeclare module 'os-name';\ndeclare module 'proxy-middleware';\ndeclare module 'rollup-pluginutils';\ndeclare module 'rollup';\ndeclare module 'tiny-lr';\ndeclare module 'uglify-es';\ndeclare module 'ws';\ndeclare module 'xml2js';"
  },
  {
    "path": "src/deep-linking/util.spec.ts",
    "content": "import { join } from 'path';\n\nimport * as ts from 'typescript';\n\nimport * as util from './util';\nimport * as transpile from '../transpile';\n\nimport * as Constants from '../util/constants';\nimport { FileCache } from '../util/file-cache';\nimport *  as helpers from '../util/helpers';\nimport { BuildContext, ChangedFile, DeepLinkConfigEntry } from '../util/interfaces';\nimport * as tsUtils from '../util/typescript-utils';\n\ndescribe('util', () => {\n  describe('filterTypescriptFilesForDeepLinks', () => {\n    it('should return a list of files that are in the directory specified for deeplinking', () => {\n      const pagesDir = join(process.cwd(), 'myApp', 'src', 'pages');\n\n      const knownFileContent = 'Some string';\n      const pageOneTs = join(pagesDir, 'page-one', 'page-one.ts');\n      const pageOneHtml = join(pagesDir, 'page-one', 'page-one.html');\n      const pageOneModule = join(pagesDir, 'page-one', 'page-one.module.ts');\n\n      const pageTwoTs = join(pagesDir, 'page-two', 'page-two.ts');\n      const pageTwoHtml = join(pagesDir, 'page-two', 'page-two.html');\n      const pageTwoModule = join(pagesDir, 'page-two', 'page-two.module.ts');\n\n      const pageThreeTs = join(pagesDir, 'page-three', 'page-three.ts');\n      const pageThreeHtml = join(pagesDir, 'page-three', 'page-three.html');\n      const pageThreeModule = join(pagesDir, 'page-three', 'page-three.module.ts');\n\n      const someOtherFile = join('Users', 'hans-gruber', 'test.ts');\n\n      const fileCache = new FileCache();\n      fileCache.set(pageOneTs, { path: pageOneTs, content: knownFileContent});\n      fileCache.set(pageOneHtml, { path: pageOneHtml, content: knownFileContent});\n      fileCache.set(pageOneModule, { path: pageOneModule, content: knownFileContent});\n      fileCache.set(pageTwoTs, { path: pageTwoTs, content: knownFileContent});\n      fileCache.set(pageTwoHtml, { path: pageTwoHtml, content: knownFileContent});\n      fileCache.set(pageTwoModule, { path: pageTwoModule, content: knownFileContent});\n      fileCache.set(pageThreeTs, { path: pageThreeTs, content: knownFileContent});\n      fileCache.set(pageThreeHtml, { path: pageThreeHtml, content: knownFileContent});\n      fileCache.set(pageThreeModule, { path: pageThreeModule, content: knownFileContent});\n      fileCache.set(someOtherFile, { path: someOtherFile, content: knownFileContent});\n\n      spyOn(helpers, helpers.getStringPropertyValue.name).and.callFake((input: string) => {\n        if (input === Constants.ENV_VAR_DEEPLINKS_DIR) {\n          return pagesDir;\n        }\n        return '.module.ts';\n      });\n\n      const results = util.filterTypescriptFilesForDeepLinks(fileCache);\n      expect(results.length).toEqual(3);\n      expect(results[0].path).toEqual(pageOneTs);\n      expect(results[1].path).toEqual(pageTwoTs);\n      expect(results[2].path).toEqual(pageThreeTs);\n    });\n  });\n  describe('parseDeepLinkDecorator', () => {\n    it('should return the decorator content from fully hydrated decorator', () => {\n      const knownContent = `\nimport { Component } from '@angular/core';\n\nimport { IonicPage, NavController } from 'ionic-angular';\n\n@IonicPage({\n  name: 'someName',\n  segment: 'someSegmentBro',\n  defaultHistory: ['page-one', 'page-two'],\n  priority: 'high'\n})\n@Component({\n  selector: 'page-home',\n  template: \\`\n  <ion-header>\n    <ion-navbar>\n      <ion-title>\n        Ionic Blank\n      </ion-title>\n    </ion-navbar>\n  </ion-header>\n\n  <ion-content padding>\n    The world is your oyster.\n    <p>\n      If you get lost, the <a href=\"http://ionicframework.com/docs/v2\">docs</a> will be your guide.\n    </p>\n    <button ion-button (click)=\"nextPage()\">Next Page</button>\n  </ion-content>\n  \\`\n})\nexport class HomePage {\n\n  constructor(public navCtrl: NavController) {\n  }\n\n  nextPage() {\n    this.navCtrl.push('PageOne');\n    console.log()\n  }\n}\n\n      `;\n\n      const knownPath = join(process.cwd(), 'some', 'fake', 'path');\n\n      const sourceFile = tsUtils.getTypescriptSourceFile(knownPath, knownContent);\n\n      const result = util.getDeepLinkDecoratorContentForSourceFile(sourceFile);\n      expect(result.name).toEqual('someName');\n      expect(result.segment).toEqual('someSegmentBro');\n      expect(result.defaultHistory[0]).toEqual('page-one');\n      expect(result.defaultHistory[1]).toEqual('page-two');\n      expect(result.priority).toEqual('high');\n      expect(knownContent.indexOf(result.rawString)).toBeGreaterThan(-1);\n\n    });\n\n    it('should default to using class name when name is missing', () => {\n      const knownContent = `\nimport { Component } from '@angular/core';\n\nimport { IonicPage, NavController } from 'ionic-angular';\n\n@IonicPage({\n  segment: 'someSegmentBro',\n  defaultHistory: ['page-one', 'page-two'],\n  priority: 'high'\n})\n@Component({\n  selector: 'page-home',\n  template: \\`\n  <ion-header>\n    <ion-navbar>\n      <ion-title>\n        Ionic Blank\n      </ion-title>\n    </ion-navbar>\n  </ion-header>\n\n  <ion-content padding>\n    The world is your oyster.\n    <p>\n      If you get lost, the <a href=\"http://ionicframework.com/docs/v2\">docs</a> will be your guide.\n    </p>\n    <button ion-button (click)=\"nextPage()\">Next Page</button>\n  </ion-content>\n  \\`\n})\nexport class HomePage {\n\n  constructor(public navCtrl: NavController) {\n  }\n\n  nextPage() {\n    this.navCtrl.push('PageOne');\n    console.log()\n  }\n}\n\n      `;\n\n      const knownPath = join(process.cwd(), 'some', 'fake', 'path');\n\n      const sourceFile = tsUtils.getTypescriptSourceFile(knownPath, knownContent);\n\n      const result = util.getDeepLinkDecoratorContentForSourceFile(sourceFile);\n      expect(result.name).toEqual('HomePage');\n      expect(result.segment).toEqual('someSegmentBro');\n      expect(result.defaultHistory[0]).toEqual('page-one');\n      expect(result.defaultHistory[1]).toEqual('page-two');\n      expect(result.priority).toEqual('high');\n      expect(knownContent.indexOf(result.rawString)).toBeGreaterThan(-1);\n\n    });\n\n    it('should return null segment when not in decorator', () => {\n      const knownContent = `\nimport { Component } from '@angular/core';\n\nimport { IonicPage, NavController } from 'ionic-angular';\n\n@IonicPage({\n  defaultHistory: ['page-one', 'page-two'],\n  priority: 'high'\n})\n@Component({\n  selector: 'page-home',\n  template: \\`\n  <ion-header>\n    <ion-navbar>\n      <ion-title>\n        Ionic Blank\n      </ion-title>\n    </ion-navbar>\n  </ion-header>\n\n  <ion-content padding>\n    The world is your oyster.\n    <p>\n      If you get lost, the <a href=\"http://ionicframework.com/docs/v2\">docs</a> will be your guide.\n    </p>\n    <button ion-button (click)=\"nextPage()\">Next Page</button>\n  </ion-content>\n  \\`\n})\nexport class HomePage {\n\n  constructor(public navCtrl: NavController) {\n  }\n\n  nextPage() {\n    this.navCtrl.push('PageOne');\n    console.log()\n  }\n}\n\n      `;\n\n      const knownPath = join(process.cwd(), 'some', 'fake', 'path');\n\n      const sourceFile = tsUtils.getTypescriptSourceFile(knownPath, knownContent);\n\n      const result = util.getDeepLinkDecoratorContentForSourceFile(sourceFile);\n      expect(result.name).toEqual('HomePage');\n      expect(result.segment).toEqual('path');\n      expect(result.defaultHistory[0]).toEqual('page-one');\n      expect(result.defaultHistory[1]).toEqual('page-two');\n      expect(result.priority).toEqual('high');\n      expect(knownContent.indexOf(result.rawString)).toBeGreaterThan(-1);\n\n    });\n\n    it('should return empty array for defaultHistory when not in decorator', () => {\n      const knownContent = `\nimport { Component } from '@angular/core';\n\nimport { IonicPage, NavController } from 'ionic-angular';\n\n@IonicPage({\n  priority: 'high'\n})\n@Component({\n  selector: 'page-home',\n  template: \\`\n  <ion-header>\n    <ion-navbar>\n      <ion-title>\n        Ionic Blank\n      </ion-title>\n    </ion-navbar>\n  </ion-header>\n\n  <ion-content padding>\n    The world is your oyster.\n    <p>\n      If you get lost, the <a href=\"http://ionicframework.com/docs/v2\">docs</a> will be your guide.\n    </p>\n    <button ion-button (click)=\"nextPage()\">Next Page</button>\n  </ion-content>\n  \\`\n})\nexport class HomePage {\n\n  constructor(public navCtrl: NavController) {\n  }\n\n  nextPage() {\n    this.navCtrl.push('PageOne');\n    console.log()\n  }\n}\n\n      `;\n\n      const knownPath = join(process.cwd(), 'myApp', 'src', 'pages', 'about.ts');\n\n      const sourceFile = tsUtils.getTypescriptSourceFile(knownPath, knownContent);\n\n      const result = util.getDeepLinkDecoratorContentForSourceFile(sourceFile);\n      expect(result.name).toEqual('HomePage');\n      expect(result.segment).toEqual('about');\n      expect(result.defaultHistory).toBeTruthy();\n      expect(result.defaultHistory.length).toEqual(0);\n      expect(result.priority).toEqual('high');\n      expect(knownContent.indexOf(result.rawString)).toBeGreaterThan(-1);\n\n    });\n\n    it('should return priority of low when not in decorator', () => {\n      const knownContent = `\nimport { Component } from '@angular/core';\n\nimport { IonicPage, NavController } from 'ionic-angular';\n\n@IonicPage({\n})\n@Component({\n  selector: 'page-home',\n  template: \\`\n  <ion-header>\n    <ion-navbar>\n      <ion-title>\n        Ionic Blank\n      </ion-title>\n    </ion-navbar>\n  </ion-header>\n\n  <ion-content padding>\n    The world is your oyster.\n    <p>\n      If you get lost, the <a href=\"http://ionicframework.com/docs/v2\">docs</a> will be your guide.\n    </p>\n    <button ion-button (click)=\"nextPage()\">Next Page</button>\n  </ion-content>\n  \\`\n})\nexport class HomePage {\n\n  constructor(public navCtrl: NavController) {\n  }\n\n  nextPage() {\n    this.navCtrl.push('PageOne');\n    console.log()\n  }\n}\n\n      `;\n\n      const knownPath = join(process.cwd(), 'some', 'fake', 'path');\n\n      const sourceFile = tsUtils.getTypescriptSourceFile(knownPath, knownContent);\n\n      const result = util.getDeepLinkDecoratorContentForSourceFile(sourceFile);\n      expect(result.name).toEqual('HomePage');\n      expect(result.segment).toEqual('path');\n      expect(result.defaultHistory).toBeTruthy();\n      expect(result.defaultHistory.length).toEqual(0);\n      expect(result.priority).toEqual('low');\n      expect(knownContent.indexOf(result.rawString)).toBeGreaterThan(-1);\n\n    });\n\n    it('should return correct defaults when no param passed to decorator', () => {\n      const knownContent = `\nimport { Component } from '@angular/core';\n\nimport { IonicPage, NavController } from 'ionic-angular';\n\n@IonicPage()\n@Component({\n  selector: 'page-home',\n  template: \\`\n  <ion-header>\n    <ion-navbar>\n      <ion-title>\n        Ionic Blank\n      </ion-title>\n    </ion-navbar>\n  </ion-header>\n\n  <ion-content padding>\n    The world is your oyster.\n    <p>\n      If you get lost, the <a href=\"http://ionicframework.com/docs/v2\">docs</a> will be your guide.\n    </p>\n    <button ion-button (click)=\"nextPage()\">Next Page</button>\n  </ion-content>\n  \\`\n})\nexport class HomePage {\n\n  constructor(public navCtrl: NavController) {\n  }\n\n  nextPage() {\n    this.navCtrl.push('PageOne');\n    console.log()\n  }\n}\n\n      `;\n\n      const knownPath = join(process.cwd(), 'some', 'fake', 'path.ts');\n\n      const sourceFile = tsUtils.getTypescriptSourceFile(knownPath, knownContent);\n\n      const result = util.getDeepLinkDecoratorContentForSourceFile(sourceFile);\n      expect(result.name).toEqual('HomePage');\n      expect(result.segment).toEqual('path');\n      expect(result.defaultHistory).toBeTruthy();\n      expect(result.defaultHistory.length).toEqual(0);\n      expect(result.priority).toEqual('low');\n      expect(knownContent.indexOf(result.rawString)).toBeGreaterThan(-1);\n\n    });\n\n    it('should throw an error when multiple deeplink decorators are found', () => {\n\n      const knownContent = `\nimport { Component } from '@angular/core';\n\nimport { IonicPage, NavController } from 'ionic-angular';\n\n@IonicPage({\n})\n@IonicPage({\n})\n@Component({\n  selector: 'page-home',\n  template: \\`\n  <ion-header>\n    <ion-navbar>\n      <ion-title>\n        Ionic Blank\n      </ion-title>\n    </ion-navbar>\n  </ion-header>\n\n  <ion-content padding>\n    The world is your oyster.\n    <p>\n      If you get lost, the <a href=\"http://ionicframework.com/docs/v2\">docs</a> will be your guide.\n    </p>\n    <button ion-button (click)=\"nextPage()\">Next Page</button>\n  </ion-content>\n  \\`\n})\nexport class HomePage {\n\n  constructor(public navCtrl: NavController) {\n  }\n\n  nextPage() {\n    this.navCtrl.push('PageOne');\n    console.log()\n  }\n}\n\n      `;\n\n      const knownPath = join(process.cwd(), 'some', 'fake', 'path');\n\n      const sourceFile = tsUtils.getTypescriptSourceFile(knownPath, knownContent);\n      const knownErrorMsg = 'Should never get here';\n\n      try {\n\n        util.getDeepLinkDecoratorContentForSourceFile(sourceFile);\n        throw new Error(knownErrorMsg);\n      } catch (ex) {\n        expect(ex.message).not.toEqual(knownErrorMsg);\n      }\n    });\n\n    it('should return null when no deeplink decorator is found', () => {\n      const knownContent = `\nimport { Component } from '@angular/core';\n\nimport { IonicPage, NavController } from 'ionic-angular';\n\n@Component({\n  selector: 'page-home',\n  template: \\`\n  <ion-header>\n    <ion-navbar>\n      <ion-title>\n        Ionic Blank\n      </ion-title>\n    </ion-navbar>\n  </ion-header>\n\n  <ion-content padding>\n    The world is your oyster.\n    <p>\n      If you get lost, the <a href=\"http://ionicframework.com/docs/v2\">docs</a> will be your guide.\n    </p>\n    <button ion-button (click)=\"nextPage()\">Next Page</button>\n  </ion-content>\n  \\`\n})\nexport class HomePage {\n\n  constructor(public navCtrl: NavController) {\n  }\n\n  nextPage() {\n    this.navCtrl.push('PageOne');\n    console.log()\n  }\n}\n\n      `;\n\n      const knownPath = join(process.cwd(), 'some', 'fake', 'path');\n\n      const sourceFile = tsUtils.getTypescriptSourceFile(knownPath, knownContent);\n      const result = util.getDeepLinkDecoratorContentForSourceFile(sourceFile);\n      expect(result).toEqual(null);\n    });\n\n    it('should return null when there isn\\'t a class declaration', () => {\n      const knownContent = `\nimport {\n  CallExpression,\n  createSourceFile,\n  Identifier,\n  ImportClause,\n  ImportDeclaration,\n  ImportSpecifier,\n  NamedImports,\n  Node,\n  ScriptTarget,\n  SourceFile,\n  StringLiteral,\n  SyntaxKind\n} from 'typescript';\n\nimport { rangeReplace, stringSplice } from './helpers';\n\nexport function getTypescriptSourceFile(filePath: string, fileContent: string, languageVersion: ScriptTarget = ScriptTarget.Latest, setParentNodes: boolean = false): SourceFile {\n  return createSourceFile(filePath, fileContent, languageVersion, setParentNodes);\n}\n\nexport function removeDecorators(fileName: string, source: string): string {\n  const sourceFile = createSourceFile(fileName, source, ScriptTarget.Latest);\n  const decorators = findNodes(sourceFile, sourceFile, SyntaxKind.Decorator, true);\n  decorators.sort((a, b) => b.pos - a.pos);\n  decorators.forEach(d => {\n    source = source.slice(0, d.pos) + source.slice(d.end);\n  });\n\n  return source;\n}\n\n      `;\n\n      const knownPath = join(process.cwd(), 'some', 'fake', 'path');\n\n      const sourceFile = tsUtils.getTypescriptSourceFile(knownPath, knownContent);\n      const result = util.getDeepLinkDecoratorContentForSourceFile(sourceFile);\n      expect(result).toEqual(null);\n    });\n  });\n\n  describe('getNgModuleDataFromCorrespondingPage', () => {\n    it('should call the file cache with the path to an ngmodule', () => {\n      const basePath = join(process.cwd(), 'some', 'fake', 'path');\n      const pagePath = join(basePath, 'my-page', 'my-page.ts');\n      const ngModulePath = join(basePath, 'my-page', 'my-page.module.ts');\n\n      spyOn(helpers, helpers.getStringPropertyValue.name).and.returnValue('.module.ts');\n\n      const result = util.getNgModulePathFromCorrespondingPage(pagePath);\n      expect(result).toEqual(ngModulePath);\n    });\n  });\n\n  describe('getRelativePathToPageNgModuleFromAppNgModule', () => {\n    it('should return the relative path', () => {\n      const prefix = join(process.cwd(), 'myApp', 'src');\n      const appNgModulePath = join(prefix, 'app', 'app.module.ts');\n      const pageNgModulePath = join(prefix, 'pages', 'page-one', 'page-one.module.ts');\n      const result = util.getRelativePathToPageNgModuleFromAppNgModule(appNgModulePath, pageNgModulePath);\n      expect(result).toEqual(join('..', 'pages', 'page-one', 'page-one.module.ts'));\n    });\n  });\n\n  describe('getNgModuleDataFromPage', () => {\n    it('should throw when NgModule is not in cache and create default ngModule flag is off', () => {\n      const prefix = join(process.cwd(), 'myApp', 'src');\n      const appNgModulePath = join(prefix, 'app', 'app.module.ts');\n      const pagePath = join(prefix, 'pages', 'page-one', 'page-one.ts');\n      const knownClassName = 'PageOne';\n      const fileCache = new FileCache();\n      spyOn(helpers, helpers.getStringPropertyValue.name).and.returnValue('.module.ts');\n\n      const knownErrorMsg = 'Should never happen';\n      try {\n        util.getNgModuleDataFromPage(appNgModulePath, pagePath, knownClassName, fileCache, false);\n        throw new Error(knownErrorMsg);\n      } catch (ex) {\n        expect(ex.message).not.toEqual(knownErrorMsg);\n      }\n    });\n\n    it('should return non-aot adjusted paths when not in AoT', () => {\n      const pageNgModuleContent = `\nimport { NgModule } from '@angular/core';\nimport { IonicPageModule } from 'ionic-angular';\n\nimport { HomePage } from './home';\n\n@NgModule({\n  declarations: [\n    HomePage,\n  ],\n  imports: [\n    IonicPageModule.forChild(HomePage),\n  ]\n})\nexport class HomePageModule {}\n      `;\n      const prefix = join(process.cwd(), 'myApp', 'src');\n      const appNgModulePath = join(prefix, 'app', 'app.module.ts');\n      const pageNgModulePath = join(prefix, 'pages', 'page-one', 'page-one.module.ts');\n      const pagePath = join(prefix, 'pages', 'page-one', 'page-one.ts');\n      const knownClassName = 'PageOne';\n      const fileCache = new FileCache();\n      fileCache.set(pageNgModulePath, { path: pageNgModulePath, content: pageNgModuleContent});\n      spyOn(helpers, helpers.getStringPropertyValue.name).and.returnValue('.module.ts');\n\n      const result = util.getNgModuleDataFromPage(appNgModulePath, pagePath, knownClassName, fileCache, false);\n\n      expect(result.absolutePath).toEqual(pageNgModulePath);\n      expect(result.userlandModulePath).toEqual('../pages/page-one/page-one.module');\n      expect(result.className).toEqual('HomePageModule');\n    });\n\n    it('should return adjusted paths to account for AoT', () => {\n      const pageNgModuleContent = `\nimport { NgModule } from '@angular/core';\nimport { IonicPageModule } from 'ionic-angular';\n\nimport { HomePage } from './home';\n\n@NgModule({\n  declarations: [\n    HomePage,\n  ],\n  imports: [\n    IonicPageModule.forChild(HomePage),\n  ]\n})\nexport class HomePageModule {}\n      `;\n      const prefix = join(process.cwd(), 'myApp', 'src');\n      const appNgModulePath = join(prefix, 'app', 'app.module.ts');\n      const pageNgModulePath = join(prefix, 'pages', 'page-one', 'page-one.module.ts');\n      const pagePath = join(prefix, 'pages', 'page-one', 'page-one.ts');\n      const knownClassName = 'PageOne';\n      const fileCache = new FileCache();\n      fileCache.set(pageNgModulePath, { path: pageNgModulePath, content: pageNgModuleContent});\n      spyOn(helpers, helpers.getStringPropertyValue.name).and.returnValue('.module.ts');\n\n      const result = util.getNgModuleDataFromPage(appNgModulePath, pagePath, knownClassName, fileCache, true);\n      expect(result.absolutePath).toEqual(helpers.changeExtension(pageNgModulePath, '.ngfactory.js'));\n      expect(result.userlandModulePath).toEqual('../pages/page-one/page-one.module.ngfactory');\n      expect(result.className).toEqual('HomePageModuleNgFactory');\n    });\n  });\n\n  describe('getDeepLinkData', () => {\n    it('should return an empty list when no deep link decorators are found', () => {\n\n      const pageOneContent = `\nimport { Component } from '@angular/core';\nimport { IonicPage, NavController } from 'ionic-angular';\n\n\n@Component({\n  selector: 'page-page-one',\n  templateUrl: './page-one.html'\n})\nexport class PageOne {\n\n  constructor(public navCtrl: NavController) {}\n\n  ionViewDidLoad() {\n  }\n\n  nextPage() {\n    this.navCtrl.push('PageTwo');\n  }\n\n  previousPage() {\n    this.navCtrl.pop();\n  }\n\n}\n      `;\n\n      const pageOneNgModuleContent = `\nimport { NgModule } from '@angular/core';\nimport { PageOne } from './page-one';\nimport { IonicPageModule } from 'ionic-angular';\n\n@NgModule({\n  declarations: [\n    PageOne,\n  ],\n  imports: [\n    IonicPageModule.forChild(PageOne)\n  ],\n  entryComponents: [\n    PageOne\n  ]\n})\nexport class PageOneModule {}\n\n      `;\n\n      const pageTwoContent = `\nimport { Component } from '@angular/core';\nimport { LoadingController, ModalController, NavController, PopoverController } from 'ionic-angular';\n\n\n@Component({\n  selector: 'page-page-two',\n  templateUrl: './page-two.html'\n})\nexport class PageTwo {\n\n  constructor(public loadingController: LoadingController, public modalController: ModalController, public navCtrl: NavController, public popoverCtrl: PopoverController) {}\n\n  ionViewDidLoad() {\n  }\n\n  goBack() {\n    this.navCtrl.pop();\n  }\n\n  showLoader() {\n    const viewController = this.loadingController.create({\n      duration: 2000\n    });\n\n    viewController.present();\n  }\n\n  openModal() {\n    /*const viewController = this.modalController.create('PageThree');\n    viewController.present();\n    */\n\n    const viewController = this.popoverCtrl.create('PageThree');\n    viewController.present();\n\n\n    //this.navCtrl.push('PageThree');\n  }\n}\n\n      `;\n\n      const pageTwoNgModuleContent = `\nimport { NgModule } from '@angular/core';\nimport { PageTwo } from './page-two';\nimport { IonicPageModule } from 'ionic-angular';\n\n@NgModule({\n  declarations: [\n    PageTwo,\n  ],\n  imports: [\n    IonicPageModule.forChild(PageTwo)\n  ]\n})\nexport class PageTwoModule {\n\n}\n      `;\n\n      const pageSettingsContent = `\nimport { Component } from '@angular/core';\nimport { IonicPage, NavController } from 'ionic-angular';\n\n/*\n  Generated class for the PageTwo page.\n\n  See http://ionicframework.com/docs/v2/components/#navigation for more info on\n  Ionic pages and navigation.\n*/\n@Component({\n  selector: 'page-three',\n  templateUrl: './page-three.html'\n})\nexport class PageThree {\n\n  constructor(public navCtrl: NavController) {}\n\n  ionViewDidLoad() {\n  }\n\n  goBack() {\n    this.navCtrl.pop();\n  }\n\n}\n\n      `;\n\n      const pageSettingsNgModuleContent = `\nimport { NgModule } from '@angular/core';\nimport { PageThree } from './page-three';\nimport { IonicPageModule } from 'ionic-angular';\n\n@NgModule({\n  declarations: [\n    PageThree,\n  ],\n  imports: [\n    IonicPageModule.forChild(PageThree)\n  ]\n})\nexport class PageThreeModule {\n\n}\n\n      `;\n\n      const prefix = join(process.cwd(), 'myApp', 'src');\n      const appNgModulePath = join(prefix, 'app', 'app.module.ts');\n      const pageOneNgModulePath = join(prefix, 'pages', 'page-one', 'page-one.module.ts');\n      const pageOnePath = join(prefix, 'pages', 'page-one', 'page-one.ts');\n      const pageTwoNgModulePath = join(prefix, 'pages', 'page-two', 'page-two.module.ts');\n      const pageTwoPath = join(prefix, 'pages', 'page-two', 'page-two.ts');\n      const pageSettingsNgModulePath = join(prefix, 'pages', 'settings-page', 'settings-page.module.ts');\n      const pageSettingsPath = join(prefix, 'pages', 'settings-page', 'settings-page.ts');\n\n      const fileCache = new FileCache();\n      fileCache.set(pageOnePath, { path: pageOnePath, content: pageOneContent});\n      fileCache.set(pageOneNgModulePath, { path: pageOneNgModulePath, content: pageOneNgModuleContent});\n      fileCache.set(pageTwoPath, { path: pageTwoPath, content: pageTwoContent});\n      fileCache.set(pageTwoNgModulePath, { path: pageTwoNgModulePath, content: pageTwoNgModuleContent});\n      fileCache.set(pageTwoPath, { path: pageTwoPath, content: pageTwoContent});\n      fileCache.set(pageTwoNgModulePath, { path: pageTwoNgModulePath, content: pageTwoNgModuleContent});\n      fileCache.set(pageSettingsPath, { path: pageSettingsPath, content: pageSettingsContent});\n      fileCache.set(pageSettingsNgModulePath, { path: pageSettingsNgModulePath, content: pageSettingsNgModuleContent});\n\n      spyOn(helpers, helpers.getStringPropertyValue.name).and.returnValue('.module.ts');\n\n      const map = util.getDeepLinkData(appNgModulePath, fileCache, false);\n      expect(map.size).toEqual(0);\n    });\n\n    it('should return an a list of deeplink configs from all pages that have them, and not include pages that dont', () => {\n\n      const pageOneContent = `\nimport { Component } from '@angular/core';\nimport { IonicPage, NavController } from 'ionic-angular';\n\n\n@IonicPage({\n  name: 'SomeOtherName'\n})\n@Component({\n  selector: 'page-page-one',\n  templateUrl: './page-one.html'\n})\nexport class PageOne {\n\n  constructor(public navCtrl: NavController) {}\n\n  ionViewDidLoad() {\n  }\n\n  nextPage() {\n    this.navCtrl.push('PageTwo');\n  }\n\n  previousPage() {\n    this.navCtrl.pop();\n  }\n\n}\n      `;\n\n      const pageOneNgModuleContent = `\nimport { NgModule } from '@angular/core';\nimport { PageOne } from './page-one';\nimport { IonicPageModule } from 'ionic-angular';\n\n@NgModule({\n  declarations: [\n    PageOne,\n  ],\n  imports: [\n    IonicPageModule.forChild(PageOne)\n  ],\n  entryComponents: [\n    PageOne\n  ]\n})\nexport class PageOneModule {}\n\n      `;\n\n      const pageTwoContent = `\nimport { Component } from '@angular/core';\nimport { LoadingController, ModalController, NavController, PopoverController } from 'ionic-angular';\n\n\n@Component({\n  selector: 'page-page-two',\n  templateUrl: './page-two.html'\n})\nexport class PageTwo {\n\n  constructor(public loadingController: LoadingController, public modalController: ModalController, public navCtrl: NavController, public popoverCtrl: PopoverController) {}\n\n  ionViewDidLoad() {\n  }\n\n  goBack() {\n    this.navCtrl.pop();\n  }\n\n  showLoader() {\n    const viewController = this.loadingController.create({\n      duration: 2000\n    });\n\n    viewController.present();\n  }\n\n  openModal() {\n    /*const viewController = this.modalController.create('PageThree');\n    viewController.present();\n    */\n\n    const viewController = this.popoverCtrl.create('PageThree');\n    viewController.present();\n\n\n    //this.navCtrl.push('PageThree');\n  }\n}\n\n      `;\n\n      const pageTwoNgModuleContent = `\nimport { NgModule } from '@angular/core';\nimport { PageTwo } from './page-two';\nimport { IonicPageModule } from 'ionic-angular';\n\n@NgModule({\n  declarations: [\n    PageTwo,\n  ],\n  imports: [\n    IonicPageModule.forChild(PageTwo)\n  ]\n})\nexport class PageTwoModule {\n\n}\n      `;\n\n      const pageSettingsContent = `\nimport { Component } from '@angular/core';\nimport { IonicPage, NavController } from 'ionic-angular';\n\n@IonicPage({\n  segment: 'someSegmentBro',\n  defaultHistory: ['page-one', 'page-two'],\n  priority: 'high'\n})\n@Component({\n  selector: 'page-three',\n  templateUrl: './page-three.html'\n})\nexport class PageThree {\n\n  constructor(public navCtrl: NavController) {}\n\n  ionViewDidLoad() {\n  }\n\n  goBack() {\n    this.navCtrl.pop();\n  }\n\n}\n\n      `;\n\n      const pageSettingsNgModuleContent = `\nimport { NgModule } from '@angular/core';\nimport { PageThree } from './page-three';\nimport { IonicPageModule } from 'ionic-angular';\n\n@NgModule({\n  declarations: [\n    PageThree,\n  ],\n  imports: [\n    IonicPageModule.forChild(PageThree)\n  ]\n})\nexport class PageThreeModule {\n\n}\n\n      `;\n\n      const srcDir = join(process.cwd(), 'myApp', 'src');\n      const appNgModulePath = join(srcDir, 'app', 'app.module.ts');\n      const pageOneNgModulePath = join(srcDir, 'pages', 'page-one', 'page-one.module.ts');\n      const pageOnePath = join(srcDir, 'pages', 'page-one', 'page-one.ts');\n      const pageTwoNgModulePath = join(srcDir, 'pages', 'page-two', 'page-two.module.ts');\n      const pageTwoPath = join(srcDir, 'pages', 'page-two', 'page-two.ts');\n      const pageSettingsNgModulePath = join(srcDir, 'pages', 'settings-page', 'settings-page.module.ts');\n      const pageSettingsPath = join(srcDir, 'pages', 'settings-page', 'settings-page.ts');\n\n      const fileCache = new FileCache();\n      fileCache.set(pageOnePath, { path: pageOnePath, content: pageOneContent});\n      fileCache.set(pageOneNgModulePath, { path: pageOneNgModulePath, content: pageOneNgModuleContent});\n      fileCache.set(pageTwoPath, { path: pageTwoPath, content: pageTwoContent});\n      fileCache.set(pageTwoNgModulePath, { path: pageTwoNgModulePath, content: pageTwoNgModuleContent});\n      fileCache.set(pageTwoPath, { path: pageTwoPath, content: pageTwoContent});\n      fileCache.set(pageTwoNgModulePath, { path: pageTwoNgModulePath, content: pageTwoNgModuleContent});\n      fileCache.set(pageSettingsPath, { path: pageSettingsPath, content: pageSettingsContent});\n      fileCache.set(pageSettingsNgModulePath, { path: pageSettingsNgModulePath, content: pageSettingsNgModuleContent});\n\n      spyOn(helpers, helpers.getStringPropertyValue.name).and.callFake((input: string) => {\n        if (input === Constants.ENV_VAR_DEEPLINKS_DIR) {\n          return srcDir;\n        } else {\n          return '.module.ts';\n        }\n      });\n\n      const map = util.getDeepLinkData(appNgModulePath, fileCache, false);\n      expect(map.size).toEqual(2);\n    });\n\n    it('should return an a list of deeplink configs from all pages that have them', () => {\n\n      const pageOneContent = `\nimport { Component } from '@angular/core';\nimport { IonicPage, NavController } from 'ionic-angular';\n\n\n@IonicPage({\n  name: 'SomeOtherName'\n})\n@Component({\n  selector: 'page-page-one',\n  templateUrl: './page-one.html'\n})\nexport class PageOne {\n\n  constructor(public navCtrl: NavController) {}\n\n  ionViewDidLoad() {\n  }\n\n  nextPage() {\n    this.navCtrl.push('PageTwo');\n  }\n\n  previousPage() {\n    this.navCtrl.pop();\n  }\n\n}\n      `;\n\n      const pageOneNgModuleContent = `\nimport { NgModule } from '@angular/core';\nimport { PageOne } from './page-one';\nimport { IonicPageModule } from 'ionic-angular';\n\n@NgModule({\n  declarations: [\n    PageOne,\n  ],\n  imports: [\n    IonicPageModule.forChild(PageOne)\n  ],\n  entryComponents: [\n    PageOne\n  ]\n})\nexport class PageOneModule {}\n\n      `;\n\n      const pageTwoContent = `\nimport { Component } from '@angular/core';\nimport { LoadingController, ModalController, NavController, PopoverController } from 'ionic-angular';\n\n\n\n@Component({\n  selector: 'page-page-two',\n  templateUrl: './page-two.html'\n})\n@IonicPage()\nexport class PageTwo {\n\n  constructor(public loadingController: LoadingController, public modalController: ModalController, public navCtrl: NavController, public popoverCtrl: PopoverController) {}\n\n  ionViewDidLoad() {\n  }\n\n  goBack() {\n    this.navCtrl.pop();\n  }\n\n  showLoader() {\n    const viewController = this.loadingController.create({\n      duration: 2000\n    });\n\n    viewController.present();\n  }\n\n  openModal() {\n    /*const viewController = this.modalController.create('PageThree');\n    viewController.present();\n    */\n\n    const viewController = this.popoverCtrl.create('PageThree');\n    viewController.present();\n\n\n    //this.navCtrl.push('PageThree');\n  }\n}\n\n      `;\n\n      const pageTwoNgModuleContent = `\nimport { NgModule } from '@angular/core';\nimport { PageTwo } from './page-two';\nimport { IonicPageModule } from 'ionic-angular';\n\n@NgModule({\n  declarations: [\n    PageTwo,\n  ],\n  imports: [\n    IonicPageModule.forChild(PageTwo)\n  ]\n})\nexport class PageTwoModule {\n\n}\n      `;\n\n      const pageSettingsContent = `\nimport { Component } from '@angular/core';\nimport { IonicPage, NavController } from 'ionic-angular';\n\n@IonicPage({\n  segment: 'someSegmentBro',\n  defaultHistory: ['page-one', 'page-two'],\n  priority: 'high'\n})\n@Component({\n  selector: 'page-three',\n  templateUrl: './page-three.html'\n})\nexport class PageThree {\n\n  constructor(public navCtrl: NavController) {}\n\n  ionViewDidLoad() {\n  }\n\n  goBack() {\n    this.navCtrl.pop();\n  }\n\n}\n\n      `;\n\n      const pageSettingsNgModuleContent = `\nimport { NgModule } from '@angular/core';\nimport { PageThree } from './page-three';\nimport { IonicPageModule } from 'ionic-angular';\n\n@NgModule({\n  declarations: [\n    PageThree,\n  ],\n  imports: [\n    IonicPageModule.forChild(PageThree)\n  ]\n})\nexport class PageThreeModule {\n\n}\n\n      `;\n\n      const srcDir = join(process.cwd(), 'myApp', 'src');\n      const appNgModulePath = join(srcDir, 'app', 'app.module.ts');\n      const pageOneNgModulePath = join(srcDir, 'pages', 'page-one', 'page-one.module.ts');\n      const pageOnePath = join(srcDir, 'pages', 'page-one', 'page-one.ts');\n      const pageTwoNgModulePath = join(srcDir, 'pages', 'page-two', 'page-two.module.ts');\n      const pageTwoPath = join(srcDir, 'pages', 'page-two', 'page-two.ts');\n      const pageSettingsNgModulePath = join(srcDir, 'pages', 'settings-page', 'fake-dir', 'settings-page.module.ts');\n      const pageSettingsPath = join(srcDir, 'pages', 'settings-page', 'fake-dir', 'settings-page.ts');\n\n      const fileCache = new FileCache();\n      fileCache.set(pageOnePath, { path: pageOnePath, content: pageOneContent});\n      fileCache.set(pageOneNgModulePath, { path: pageOneNgModulePath, content: pageOneNgModuleContent});\n      fileCache.set(pageTwoPath, { path: pageTwoPath, content: pageTwoContent});\n      fileCache.set(pageTwoNgModulePath, { path: pageTwoNgModulePath, content: pageTwoNgModuleContent});\n      fileCache.set(pageTwoPath, { path: pageTwoPath, content: pageTwoContent});\n      fileCache.set(pageTwoNgModulePath, { path: pageTwoNgModulePath, content: pageTwoNgModuleContent});\n      fileCache.set(pageSettingsPath, { path: pageSettingsPath, content: pageSettingsContent});\n      fileCache.set(pageSettingsNgModulePath, { path: pageSettingsNgModulePath, content: pageSettingsNgModuleContent});\n\n      spyOn(helpers, helpers.getStringPropertyValue.name).and.callFake((input: string) => {\n        if (input === Constants.ENV_VAR_DEEPLINKS_DIR) {\n          return srcDir;\n        } else {\n          return '.module.ts';\n        }\n      });\n\n      const map = util.getDeepLinkData(appNgModulePath, fileCache, false);\n      expect(map.size).toEqual(3);\n\n      const entryOne = map.get('SomeOtherName');\n      expect(entryOne.name).toEqual('SomeOtherName');\n      expect(entryOne.segment).toEqual('page-one');\n      expect(entryOne.priority).toEqual('low');\n      expect(entryOne.defaultHistory.length).toEqual(0);\n      expect(entryOne.absolutePath).toEqual(join(srcDir, 'pages', 'page-one', 'page-one.module.ts'));\n      expect(entryOne.userlandModulePath).toEqual('../pages/page-one/page-one.module');\n      expect(entryOne.className).toEqual('PageOneModule');\n\n      const entryTwo = map.get('PageTwo');\n      expect(entryTwo.name).toEqual('PageTwo');\n      expect(entryTwo.segment).toEqual('page-two');\n      expect(entryTwo.priority).toEqual('low');\n      expect(entryTwo.defaultHistory.length).toEqual(0);\n      expect(entryTwo.absolutePath).toEqual(join(srcDir, 'pages', 'page-two', 'page-two.module.ts'));\n      expect(entryTwo.userlandModulePath).toEqual('../pages/page-two/page-two.module');\n      expect(entryTwo.className).toEqual('PageTwoModule');\n\n      const entryThree = map.get('PageThree');\n      expect(entryThree.name).toEqual('PageThree');\n      expect(entryThree.segment).toEqual('someSegmentBro');\n      expect(entryThree.priority).toEqual('high');\n      expect(entryThree.defaultHistory.length).toEqual(2);\n      expect(entryThree.defaultHistory[0]).toEqual('page-one');\n      expect(entryThree.defaultHistory[1]).toEqual('page-two');\n      expect(entryThree.absolutePath).toEqual(join(srcDir, 'pages', 'settings-page', 'fake-dir', 'settings-page.module.ts'));\n      expect(entryThree.userlandModulePath).toEqual('../pages/settings-page/fake-dir/settings-page.module');\n      expect(entryThree.className).toEqual('PageThreeModule');\n    });\n\n    it('should throw when it cant find an NgModule as a peer to the page with a deep link config', () => {\n      const pageOneContent = `\nimport { Component } from '@angular/core';\nimport { IonicPage, NavController } from 'ionic-angular';\n\n\n@IonicPage({\n  name: 'SomeOtherName'\n})\n@Component({\n  selector: 'page-page-one',\n  templateUrl: './page-one.html'\n})\nexport class PageOne {\n\n  constructor(public navCtrl: NavController) {}\n\n  ionViewDidLoad() {\n  }\n\n  nextPage() {\n    this.navCtrl.push('PageTwo');\n  }\n\n  previousPage() {\n    this.navCtrl.pop();\n  }\n\n}\n      `;\n\n      const pageOneNgModuleContent = `\nimport { NgModule } from '@angular/core';\nimport { PageOne } from './page-one';\nimport { IonicPageModule } from 'ionic-angular';\n\n@NgModule({\n  declarations: [\n    PageOne,\n  ],\n  imports: [\n    IonicPageModule.forChild(PageOne)\n  ],\n  entryComponents: [\n    PageOne\n  ]\n})\nexport class PageOneModule {}\n\n      `;\n\n      const pageTwoContent = `\nimport { Component } from '@angular/core';\nimport { LoadingController, ModalController, NavController, PopoverController } from 'ionic-angular';\n\n\n\n@Component({\n  selector: 'page-page-two',\n  templateUrl: './page-two.html'\n})\n@IonicPage()\nexport class PageTwo {\n\n  constructor(public loadingController: LoadingController, public modalController: ModalController, public navCtrl: NavController, public popoverCtrl: PopoverController) {}\n\n  ionViewDidLoad() {\n  }\n\n  goBack() {\n    this.navCtrl.pop();\n  }\n\n  showLoader() {\n    const viewController = this.loadingController.create({\n      duration: 2000\n    });\n\n    viewController.present();\n  }\n\n  openModal() {\n    /*const viewController = this.modalController.create('PageThree');\n    viewController.present();\n    */\n\n    const viewController = this.popoverCtrl.create('PageThree');\n    viewController.present();\n\n\n    //this.navCtrl.push('PageThree');\n  }\n}\n\n      `;\n\n      const pageTwoNgModuleContent = `\nimport { NgModule } from '@angular/core';\nimport { PageTwo } from './page-two';\nimport { IonicPageModule } from 'ionic-angular';\n\n@NgModule({\n  declarations: [\n    PageTwo,\n  ],\n  imports: [\n    IonicPageModule.forChild(PageTwo)\n  ]\n})\nexport class PageTwoModule {\n\n}\n      `;\n\n      const pageSettingsContent = `\nimport { Component } from '@angular/core';\nimport { IonicPage, NavController } from 'ionic-angular';\n\n@IonicPage({\n  segment: 'someSegmentBro',\n  defaultHistory: ['page-one', 'page-two'],\n  priority: 'high'\n})\n@Component({\n  selector: 'page-three',\n  templateUrl: './page-three.html'\n})\nexport class PageThree {\n\n  constructor(public navCtrl: NavController) {}\n\n  ionViewDidLoad() {\n  }\n\n  goBack() {\n    this.navCtrl.pop();\n  }\n\n}\n\n      `;\n\n      const pageSettingsNgModuleContent = `\nimport { NgModule } from '@angular/core';\nimport { PageThree } from './page-three';\nimport { IonicPageModule } from 'ionic-angular';\n\n@NgModule({\n  declarations: [\n    PageThree,\n  ],\n  imports: [\n    IonicPageModule.forChild(PageThree)\n  ]\n})\nexport class PageThreeModule {\n\n}\n\n      `;\n\n      const srcDir = join(process.cwd(), 'myApp', 'src');\n      const appNgModulePath = join(srcDir, 'app', 'app.module.ts');\n      const pageOneNgModulePath = join(srcDir, 'pages', 'page-one', 'page-one.not-module.ts');\n      const pageOnePath = join(srcDir, 'pages', 'page-one', 'page-one.ts');\n      const pageTwoNgModulePath = join(srcDir, 'pages', 'page-two', 'page-two.module.ts');\n      const pageTwoPath = join(srcDir, 'pages', 'page-two', 'page-two.ts');\n      const pageSettingsNgModulePath = join(srcDir, 'pages', 'settings-page', 'fake-dir', 'settings-page.module.ts');\n      const pageSettingsPath = join(srcDir, 'pages', 'settings-page', 'fake-dir', 'settings-page.ts');\n\n      const fileCache = new FileCache();\n      fileCache.set(pageOnePath, { path: pageOnePath, content: pageOneContent});\n      fileCache.set(pageOneNgModulePath, { path: pageOneNgModulePath, content: pageOneNgModuleContent});\n      fileCache.set(pageTwoPath, { path: pageTwoPath, content: pageTwoContent});\n      fileCache.set(pageTwoNgModulePath, { path: pageTwoNgModulePath, content: pageTwoNgModuleContent});\n      fileCache.set(pageTwoPath, { path: pageTwoPath, content: pageTwoContent});\n      fileCache.set(pageTwoNgModulePath, { path: pageTwoNgModulePath, content: pageTwoNgModuleContent});\n      fileCache.set(pageSettingsPath, { path: pageSettingsPath, content: pageSettingsContent});\n      fileCache.set(pageSettingsNgModulePath, { path: pageSettingsNgModulePath, content: pageSettingsNgModuleContent});\n\n      spyOn(helpers, helpers.getStringPropertyValue.name).and.callFake((input: string) => {\n        if (input === Constants.ENV_VAR_DEEPLINKS_DIR) {\n          return srcDir;\n        } else {\n          return '.module.ts';\n        }\n      });\n\n      const knownError = 'should never get here';\n\n      try {\n        util.getDeepLinkData(appNgModulePath, fileCache, false);\n        throw new Error(knownError);\n      } catch (ex) {\n        expect(ex.message).not.toEqual(knownError);\n      }\n    });\n  });\n\n  describe('hasExistingDeepLinkConfig', () => {\n    it('should return true when there is an existing deep link config', () => {\n      const knownContent = `\nimport { BrowserModule } from '@angular/platform-browser';\nimport { NgModule } from '@angular/core';\nimport { IonicApp, IonicModule } from 'ionic-angular';\nimport { MyApp } from './app.component';\n\nimport { HomePageModule } from '../pages/home/home.module';\n\n@NgModule({\n  declarations: [\n    MyApp,\n  ],\n  imports: [\n    BrowserModule,\n    IonicModule.forRoot(MyApp, {}, {\n      links: [\n        { loadChildren: '../pages/page-one/page-one.module#PageOneModule', name: 'PageOne' },\n        { loadChildren: '../pages/page-two/page-two.module#PageTwoModule', name: 'PageTwo' },\n        { loadChildren: '../pages/page-three/page-three.module#PageThreeModule', name: 'PageThree' }\n      ]\n    }),\n    HomePageModule,\n  ],\n  bootstrap: [IonicApp],\n  providers: []\n})\nexport class AppModule {}\n      `;\n\n      const knownPath = '/idk/yo/some/path';\n\n      const result = util.hasExistingDeepLinkConfig(knownPath, knownContent);\n      expect(result).toEqual(true);\n    });\n\n\n    it('should return false when there isnt a deeplink config', () => {\n      const knownContent = `\nimport { BrowserModule } from '@angular/platform-browser';\nimport { NgModule } from '@angular/core';\nimport { IonicApp, IonicModule } from 'ionic-angular';\nimport { MyApp } from './app.component';\n\nimport { HomePageModule } from '../pages/home/home.module';\n\n@NgModule({\n  declarations: [\n    MyApp,\n  ],\n  imports: [\n    BrowserModule,\n    IonicModule.forRoot(MyApp, {}),\n    HomePageModule,\n  ],\n  bootstrap: [IonicApp],\n  providers: []\n})\nexport class AppModule {}\n      `;\n\n      const knownPath = join(process.cwd(), 'idk', 'some', 'fake', 'path');\n\n      const result = util.hasExistingDeepLinkConfig(knownPath, knownContent);\n      expect(result).toEqual(false);\n    });\n\n    it('should return false when null/undefined is passed in place on deeplink config', () => {\n      const knownContent = `\nimport { BrowserModule } from '@angular/platform-browser';\nimport { NgModule } from '@angular/core';\nimport { IonicApp, IonicModule } from 'ionic-angular';\nimport { MyApp } from './app.component';\n\nimport { HomePageModule } from '../pages/home/home.module';\n\n@NgModule({\n  declarations: [\n    MyApp,\n  ],\n  imports: [\n    BrowserModule,\n    IonicModule.forRoot(MyApp, {}, null),\n    HomePageModule,\n  ],\n  bootstrap: [IonicApp],\n  providers: []\n})\nexport class AppModule {}\n      `;\n\n      const knownPath = join(process.cwd(), 'idk', 'some', 'fake', 'path');\n\n      const result = util.hasExistingDeepLinkConfig(knownPath, knownContent);\n      expect(result).toEqual(false);\n    });\n\n    it('should return true where there is an existing deep link config associated with a variable', () => {\n      const knownContent = `\nimport { BrowserModule } from '@angular/platform-browser';\nimport { NgModule } from '@angular/core';\nimport { IonicApp, IonicModule } from 'ionic-angular';\nimport { MyApp } from './app.component';\n\nimport { HomePageModule } from '../pages/home/home.module';\n\nconst deepLinkConfig = {\n  links: [\n    { loadChildren: '../pages/page-one/page-one.module#PageOneModule', name: 'PageOne' },\n    { loadChildren: '../pages/page-two/page-two.module#PageTwoModule', name: 'PageTwo' },\n    { loadChildren: '../pages/page-three/page-three.module#PageThreeModule', name: 'PageThree' }\n  ]\n};\n\n@NgModule({\n  declarations: [\n    MyApp,\n  ],\n  imports: [\n    BrowserModule,\n    IonicModule.forRoot(MyApp, {}, deepLinkConfig),\n    HomePageModule,\n  ],\n  bootstrap: [IonicApp],\n  providers: []\n})\nexport class AppModule {}\n      `;\n\n      const knownPath = join(process.cwd(), 'idk', 'some', 'fake', 'path');\n\n      const result = util.hasExistingDeepLinkConfig(knownPath, knownContent);\n      expect(result).toEqual(true);\n    });\n\n  });\n\n  describe('convertDeepLinkEntryToJsObjectString', () => {\n    it('should convert to a flat string format', () => {\n      const entry: DeepLinkConfigEntry = {\n        name: 'HomePage',\n        segment: null,\n        defaultHistory: [],\n        priority: 'low',\n        rawString: 'irrelevant for this test',\n        absolutePath: join(process.cwd(), 'myApp', 'pages', 'home-page', 'home-page.module.ts'),\n        userlandModulePath: '../pages/home-page/home-page.module',\n        className: 'HomePageModule'\n      };\n\n      const result = util.convertDeepLinkEntryToJsObjectString(entry);\n      expect(result).toEqual(`{ loadChildren: '../pages/home-page/home-page.module#HomePageModule', name: 'HomePage', segment: null, priority: 'low', defaultHistory: [] }`);\n    });\n\n    it('should handle defaultHistory entries and segment', () => {\n      const entry: DeepLinkConfigEntry = {\n        name: 'HomePage',\n        segment: 'idkMan',\n        defaultHistory: ['page-two', 'page-three', 'page-four'],\n        priority: 'low',\n        rawString: 'irrelevant for this test',\n        absolutePath: join(process.cwd(), 'myApp', 'pages', 'home-page', 'home-page.module.ts'),\n        userlandModulePath: '../pages/home-page/home-page.module',\n        className: 'HomePageModule'\n      };\n\n      const result = util.convertDeepLinkEntryToJsObjectString(entry);\n      expect(result).toEqual(`{ loadChildren: '../pages/home-page/home-page.module#HomePageModule', name: 'HomePage', segment: 'idkMan', priority: 'low', defaultHistory: ['page-two', 'page-three', 'page-four'] }`);\n    });\n  });\n\n  describe('convertDeepLinkConfigEntriesToString', () => {\n    it('should convert list of decorator data to legacy ionic data structure as a string', () => {\n      const map = new Map<string, DeepLinkConfigEntry>();\n\n      map.set('HomePage', {\n        name: 'HomePage',\n        segment: 'idkMan',\n        defaultHistory: ['page-two', 'page-three', 'page-four'],\n        priority: 'low',\n        rawString: 'irrelevant for this test',\n        absolutePath: join(process.cwd(), 'myApp', 'pages', 'home-page', 'home-page.module.ts'),\n        userlandModulePath: '../pages/home-page/home-page.module',\n        className: 'HomePageModule'\n      });\n\n      map.set('PageTwo', {\n        name: 'PageTwo',\n        segment: null,\n        defaultHistory: [],\n        priority: 'low',\n        rawString: 'irrelevant for this test',\n        absolutePath: join(process.cwd(), 'myApp', 'pages', 'home-page', 'home-page.module.ts'),\n        userlandModulePath: '../pages/page-two/page-two.module',\n        className: 'PageTwoModule'\n      });\n\n      map.set('SettingsPage', {\n        name: 'SettingsPage',\n        segment: null,\n        defaultHistory: [],\n        priority: 'low',\n        rawString: 'irrelevant for this test',\n        absolutePath: join(process.cwd(), 'myApp', 'pages', 'home-page', 'home-page.module.ts'),\n        userlandModulePath: '../pages/settings-page/setting-page.module',\n        className: 'SettingsPageModule'\n      });\n\n      const result = util.convertDeepLinkConfigEntriesToString(map);\n      expect(result.indexOf('links: [')).toBeGreaterThanOrEqual(0);\n      expect(result.indexOf(`{ loadChildren: '../pages/home-page/home-page.module#HomePageModule', name: 'HomePage', segment: 'idkMan', priority: 'low', defaultHistory: ['page-two', 'page-three', 'page-four'] },`)).toBeGreaterThanOrEqual(0);\n      expect(result.indexOf(`{ loadChildren: '../pages/page-two/page-two.module#PageTwoModule', name: 'PageTwo', segment: null, priority: 'low', defaultHistory: [] },`)).toBeGreaterThanOrEqual(0);\n      expect(result.indexOf(`{ loadChildren: '../pages/settings-page/setting-page.module#SettingsPageModule', name: 'SettingsPage', segment: null, priority: 'low', defaultHistory: [] }`)).toBeGreaterThanOrEqual(0);\n    });\n  });\n\n  describe('getUpdatedAppNgModuleContentWithDeepLinkConfig', () => {\n    it('should add a default argument for the second param of forRoot, then add the deeplink config', () => {\n      const knownStringToInject = `{\n  links: [\n    { loadChildren: '../pages/home-page/home-page.module#HomePageModule', name: 'HomePage', segment: 'idkMan', priority: 'low', defaultHistory: ['page-two', 'page-three', 'page-four'] },\n    { loadChildren: '../pages/page-two/page-two.module#PageTwoModule', name: 'PageTwo', segment: null, priority: 'low', defaultHistory: [] },\n    { loadChildren: '../pages/settings-page/setting-page.module#SettingsPageModule', name: 'SettingsPage', segment: null, priority: 'low', defaultHistory: [] }\n  ]\n}\n`;\n\n      const knownContent = `\nimport { BrowserModule } from '@angular/platform-browser';\nimport { NgModule } from '@angular/core';\nimport { IonicApp, IonicModule } from 'ionic-angular';\nimport { MyApp } from './app.component';\n\nimport { HomePageModule } from '../pages/home/home.module';\n\n@NgModule({\n  declarations: [\n    MyApp,\n  ],\n  imports: [\n    BrowserModule,\n    IonicModule.forRoot(MyApp),\n    HomePageModule,\n  ],\n  bootstrap: [IonicApp],\n  providers: []\n})\nexport class AppModule {}\n      `;\n\n      const expectedResult = `\nimport { BrowserModule } from '@angular/platform-browser';\nimport { NgModule } from '@angular/core';\nimport { IonicApp, IonicModule } from 'ionic-angular';\nimport { MyApp } from './app.component';\n\nimport { HomePageModule } from '../pages/home/home.module';\n\n@NgModule({\n  declarations: [\n    MyApp,\n  ],\n  imports: [\n    BrowserModule,\n    IonicModule.forRoot(MyApp, {}, {\n  links: [\n    { loadChildren: '../pages/home-page/home-page.module#HomePageModule', name: 'HomePage', segment: 'idkMan', priority: 'low', defaultHistory: ['page-two', 'page-three', 'page-four'] },\n    { loadChildren: '../pages/page-two/page-two.module#PageTwoModule', name: 'PageTwo', segment: null, priority: 'low', defaultHistory: [] },\n    { loadChildren: '../pages/settings-page/setting-page.module#SettingsPageModule', name: 'SettingsPage', segment: null, priority: 'low', defaultHistory: [] }\n  ]\n}\n),\n    HomePageModule,\n  ],\n  bootstrap: [IonicApp],\n  providers: []\n})\nexport class AppModule {}\n      `;\n\n      const knownPath = join('some', 'fake', 'path');\n\n      const result = util.getUpdatedAppNgModuleContentWithDeepLinkConfig(knownPath, knownContent, knownStringToInject);\n      expect(result).toEqual(expectedResult);\n\n    });\n\n    it('should append the deeplink config as the third argument when second arg is null', () => {\n      const knownStringToInject = `{\n  links: [\n    { loadChildren: '../pages/home-page/home-page.module#HomePageModule', name: 'HomePage', segment: 'idkMan', priority: 'low', defaultHistory: ['page-two', 'page-three', 'page-four'] },\n    { loadChildren: '../pages/page-two/page-two.module#PageTwoModule', name: 'PageTwo', segment: null, priority: 'low', defaultHistory: [] },\n    { loadChildren: '../pages/settings-page/setting-page.module#SettingsPageModule', name: 'SettingsPage', segment: null, priority: 'low', defaultHistory: [] }\n  ]\n}\n`;\n\n      const knownContent = `\nimport { BrowserModule } from '@angular/platform-browser';\nimport { NgModule } from '@angular/core';\nimport { IonicApp, IonicModule } from 'ionic-angular';\nimport { MyApp } from './app.component';\n\nimport { HomePageModule } from '../pages/home/home.module';\n\n@NgModule({\n  declarations: [\n    MyApp,\n  ],\n  imports: [\n    BrowserModule,\n    IonicModule.forRoot(MyApp, null),\n    HomePageModule,\n  ],\n  bootstrap: [IonicApp],\n  providers: []\n})\nexport class AppModule {}\n      `;\n\n      const expectedResult = `\nimport { BrowserModule } from '@angular/platform-browser';\nimport { NgModule } from '@angular/core';\nimport { IonicApp, IonicModule } from 'ionic-angular';\nimport { MyApp } from './app.component';\n\nimport { HomePageModule } from '../pages/home/home.module';\n\n@NgModule({\n  declarations: [\n    MyApp,\n  ],\n  imports: [\n    BrowserModule,\n    IonicModule.forRoot(MyApp, null, {\n  links: [\n    { loadChildren: '../pages/home-page/home-page.module#HomePageModule', name: 'HomePage', segment: 'idkMan', priority: 'low', defaultHistory: ['page-two', 'page-three', 'page-four'] },\n    { loadChildren: '../pages/page-two/page-two.module#PageTwoModule', name: 'PageTwo', segment: null, priority: 'low', defaultHistory: [] },\n    { loadChildren: '../pages/settings-page/setting-page.module#SettingsPageModule', name: 'SettingsPage', segment: null, priority: 'low', defaultHistory: [] }\n  ]\n}\n),\n    HomePageModule,\n  ],\n  bootstrap: [IonicApp],\n  providers: []\n})\nexport class AppModule {}\n      `;\n\n      const knownPath = join('some', 'fake', 'path');\n\n      const result = util.getUpdatedAppNgModuleContentWithDeepLinkConfig(knownPath, knownContent, knownStringToInject);\n      expect(result).toEqual(expectedResult);\n\n    });\n\n    it('should append the deeplink config as the third argument when second arg is object', () => {\n      const knownStringToInject = `{\n  links: [\n    { loadChildren: '../pages/home-page/home-page.module#HomePageModule', name: 'HomePage', segment: 'idkMan', priority: 'low', defaultHistory: ['page-two', 'page-three', 'page-four'] },\n    { loadChildren: '../pages/page-two/page-two.module#PageTwoModule', name: 'PageTwo', segment: null, priority: 'low', defaultHistory: [] },\n    { loadChildren: '../pages/settings-page/setting-page.module#SettingsPageModule', name: 'SettingsPage', segment: null, priority: 'low', defaultHistory: [] }\n  ]\n}\n`;\n\n      const knownContent = `\nimport { BrowserModule } from '@angular/platform-browser';\nimport { NgModule } from '@angular/core';\nimport { IonicApp, IonicModule } from 'ionic-angular';\nimport { MyApp } from './app.component';\n\nimport { HomePageModule } from '../pages/home/home.module';\n\n@NgModule({\n  declarations: [\n    MyApp,\n  ],\n  imports: [\n    BrowserModule,\n    IonicModule.forRoot(MyApp, {}),\n    HomePageModule,\n  ],\n  bootstrap: [IonicApp],\n  providers: []\n})\nexport class AppModule {}\n      `;\n\n      const expectedResult = `\nimport { BrowserModule } from '@angular/platform-browser';\nimport { NgModule } from '@angular/core';\nimport { IonicApp, IonicModule } from 'ionic-angular';\nimport { MyApp } from './app.component';\n\nimport { HomePageModule } from '../pages/home/home.module';\n\n@NgModule({\n  declarations: [\n    MyApp,\n  ],\n  imports: [\n    BrowserModule,\n    IonicModule.forRoot(MyApp, {}, {\n  links: [\n    { loadChildren: '../pages/home-page/home-page.module#HomePageModule', name: 'HomePage', segment: 'idkMan', priority: 'low', defaultHistory: ['page-two', 'page-three', 'page-four'] },\n    { loadChildren: '../pages/page-two/page-two.module#PageTwoModule', name: 'PageTwo', segment: null, priority: 'low', defaultHistory: [] },\n    { loadChildren: '../pages/settings-page/setting-page.module#SettingsPageModule', name: 'SettingsPage', segment: null, priority: 'low', defaultHistory: [] }\n  ]\n}\n),\n    HomePageModule,\n  ],\n  bootstrap: [IonicApp],\n  providers: []\n})\nexport class AppModule {}\n      `;\n\n      const knownPath = join('some', 'fake', 'path');\n\n      const result = util.getUpdatedAppNgModuleContentWithDeepLinkConfig(knownPath, knownContent, knownStringToInject);\n      expect(result).toEqual(expectedResult);\n\n    });\n\n    it('should replace the third argument with deeplink config', () => {\n      const knownStringToInject = `{\n  links: [\n    { loadChildren: '../pages/home-page/home-page.module#HomePageModule', name: 'HomePage', segment: 'idkMan', priority: 'low', defaultHistory: ['page-two', 'page-three', 'page-four'] },\n    { loadChildren: '../pages/page-two/page-two.module#PageTwoModule', name: 'PageTwo', segment: null, priority: 'low', defaultHistory: [] },\n    { loadChildren: '../pages/settings-page/setting-page.module#SettingsPageModule', name: 'SettingsPage', segment: null, priority: 'low', defaultHistory: [] }\n  ]\n}\n`;\n\n      const knownContent = `\nimport { BrowserModule } from '@angular/platform-browser';\nimport { NgModule } from '@angular/core';\nimport { IonicApp, IonicModule } from 'ionic-angular';\nimport { MyApp } from './app.component';\n\nimport { HomePageModule } from '../pages/home/home.module';\n\n@NgModule({\n  declarations: [\n    MyApp,\n  ],\n  imports: [\n    BrowserModule,\n    IonicModule.forRoot(MyApp, {}, null),\n    HomePageModule,\n  ],\n  bootstrap: [IonicApp],\n  providers: []\n})\nexport class AppModule {}\n      `;\n\n      const expectedResult = `\nimport { BrowserModule } from '@angular/platform-browser';\nimport { NgModule } from '@angular/core';\nimport { IonicApp, IonicModule } from 'ionic-angular';\nimport { MyApp } from './app.component';\n\nimport { HomePageModule } from '../pages/home/home.module';\n\n@NgModule({\n  declarations: [\n    MyApp,\n  ],\n  imports: [\n    BrowserModule,\n    IonicModule.forRoot(MyApp, {}, {\n  links: [\n    { loadChildren: '../pages/home-page/home-page.module#HomePageModule', name: 'HomePage', segment: 'idkMan', priority: 'low', defaultHistory: ['page-two', 'page-three', 'page-four'] },\n    { loadChildren: '../pages/page-two/page-two.module#PageTwoModule', name: 'PageTwo', segment: null, priority: 'low', defaultHistory: [] },\n    { loadChildren: '../pages/settings-page/setting-page.module#SettingsPageModule', name: 'SettingsPage', segment: null, priority: 'low', defaultHistory: [] }\n  ]\n}\n),\n    HomePageModule,\n  ],\n  bootstrap: [IonicApp],\n  providers: []\n})\nexport class AppModule {}\n      `;\n\n      const knownPath = join('some', 'fake', 'path');\n\n      const result = util.getUpdatedAppNgModuleContentWithDeepLinkConfig(knownPath, knownContent, knownStringToInject);\n      expect(result).toEqual(expectedResult);\n\n    });\n  });\n\n  describe('generateDefaultDeepLinkNgModuleContent', () => {\n    it('should generate a default NgModule for a DeepLinked component', () => {\n      const knownFileContent = `\nimport { NgModule } from '@angular/core';\nimport { IonicPageModule } from 'ionic-angular';\nimport { PageOne } from './page-one';\n\n@NgModule({\n  declarations: [\n    PageOne,\n  ],\n  imports: [\n    IonicPageModule.forChild(PageOne)\n  ]\n})\nexport class PageOneModule {}\n\n`;\n      const knownFilePath = join(process.cwd(), 'myApp', 'src', 'pages', 'page-one', 'page-one.ts');\n      const knownClassName = 'PageOne';\n      const fileContent = util.generateDefaultDeepLinkNgModuleContent(knownFilePath, knownClassName);\n      expect(fileContent).toEqual(knownFileContent);\n    });\n  });\n\n  describe('updateAppNgModuleWithDeepLinkConfig', () => {\n    it('should throw when app ng module is not in cache', () => {\n      const fileCache = new FileCache();\n      const knownContext = {\n        fileCache: fileCache\n      };\n\n      const knownDeepLinkString = `{\n  links: [\n    { loadChildren: '../pages/home-page/home-page.module#HomePageModule', name: 'HomePage', segment: 'idkMan', priority: 'low', defaultHistory: ['page-two', 'page-three', 'page-four'] },\n    { loadChildren: '../pages/page-two/page-two.module#PageTwoModule', name: 'PageTwo', segment: null, priority: 'low', defaultHistory: [] },\n    { loadChildren: '../pages/settings-page/setting-page.module#SettingsPageModule', name: 'SettingsPage', segment: null, priority: 'low', defaultHistory: [] }\n  ]\n}\n`;\n      const knownAppNgModulePath = join(process.cwd(), 'myApp', 'src', 'app.module.ts');\n      spyOn(helpers, helpers.getStringPropertyValue.name).and.returnValue(knownAppNgModulePath);\n      spyOn(fileCache, 'get').and.callThrough();\n\n      const knownErrorMsg = 'should never get here';\n      try {\n        util.updateAppNgModuleWithDeepLinkConfig(knownContext, knownDeepLinkString, null);\n        throw new Error(knownErrorMsg);\n      } catch (ex) {\n        expect(ex.message).not.toEqual(knownErrorMsg);\n        expect(fileCache.get).toHaveBeenCalledWith(knownAppNgModulePath);\n      }\n    });\n\n    it('should update the cache with updated ts file', () => {\n      const fileCache = new FileCache();\n      const knownContext = {\n        fileCache: fileCache\n      };\n\n      const knownDeepLinkString = `{\n  links: [\n    { loadChildren: '../pages/home-page/home-page.module#HomePageModule', name: 'HomePage', segment: 'idkMan', priority: 'low', defaultHistory: ['page-two', 'page-three', 'page-four'] },\n    { loadChildren: '../pages/page-two/page-two.module#PageTwoModule', name: 'PageTwo', segment: null, priority: 'low', defaultHistory: [] },\n    { loadChildren: '../pages/settings-page/setting-page.module#SettingsPageModule', name: 'SettingsPage', segment: null, priority: 'low', defaultHistory: [] }\n  ]\n}\n`;\n\n      const ngModuleContent = `\nimport { BrowserModule } from '@angular/platform-browser';\nimport { NgModule } from '@angular/core';\nimport { IonicApp, IonicModule } from 'ionic-angular';\nimport { MyApp } from './app.component';\n\nimport { HomePageModule } from '../pages/home/home.module';\n\n@NgModule({\n  declarations: [\n    MyApp,\n  ],\n  imports: [\n    BrowserModule,\n    IonicModule.forRoot(MyApp, {}, null),\n    HomePageModule,\n  ],\n  bootstrap: [IonicApp],\n  providers: []\n})\nexport class AppModule {}\n`;\n\n\n      const knownAppNgModulePath = join(process.cwd(), 'myApp', 'src', 'app.module.ts');\n      fileCache.set(knownAppNgModulePath, { path: knownAppNgModulePath, content: ngModuleContent});\n      spyOn(helpers, helpers.getStringPropertyValue.name).and.returnValue(knownAppNgModulePath);\n\n      const changedFiles: ChangedFile[] = [];\n      util.updateAppNgModuleWithDeepLinkConfig(knownContext, knownDeepLinkString, changedFiles);\n\n      expect(fileCache.getAll().length).toEqual(1);\n      expect(fileCache.get(knownAppNgModulePath).content.indexOf(knownDeepLinkString)).toBeGreaterThanOrEqual(0);\n      expect(changedFiles.length).toEqual(1);\n      expect(changedFiles[0].event).toEqual('change');\n      expect(changedFiles[0].ext).toEqual('.ts');\n      expect(changedFiles[0].filePath).toEqual(knownAppNgModulePath);\n    });\n  });\n\n  describe('purgeDeepLinkDecorator', () => {\n    it('should remove the IonicPage decorator from the ts source', () => {\n      const input = `\nimport { Component } from '@angular/core';\n\nimport { IonicPage, PopoverController } from 'ionic-angular';\n\n@IonicPage()\n@Component({\n  selector: 'page-about',\n  templateUrl: 'about.html'\n})\nexport class AboutPage {\n  conferenceDate = '2047-05-17';\n\n  constructor(public popoverCtrl: PopoverController) { }\n\n  presentPopover(event: Event) {\n    let popover = this.popoverCtrl.create('PopoverPage');\n    popover.present({ ev: event });\n  }\n}\n`;\n\nconst expectedContent = `\nimport { Component } from '@angular/core';\n\nimport { PopoverController } from 'ionic-angular';\n\n\n@Component({\n  selector: 'page-about',\n  templateUrl: 'about.html'\n})\nexport class AboutPage {\n  conferenceDate = '2047-05-17';\n\n  constructor(public popoverCtrl: PopoverController) { }\n\n  presentPopover(event: Event) {\n    let popover = this.popoverCtrl.create('PopoverPage');\n    popover.present({ ev: event });\n  }\n}\n`;\n      const result = util.purgeDeepLinkDecorator(input);\n      expect(result).toEqual(expectedContent);\n    });\n  });\n\n  describe('purgeDeepLinkImport', () => {\n    it('should remove the IonicPage decorator but preserve others', () => {\n      const input = `\nimport { Component } from '@angular/core';\n\nimport { IonicPage, PopoverController } from 'ionic-angular';\n\n@IonicPage()\n@Component({\n  selector: 'page-about',\n  templateUrl: 'about.html'\n})\nexport class AboutPage {\n  conferenceDate = '2047-05-17';\n\n  constructor(public popoverCtrl: PopoverController) { }\n\n  presentPopover(event: Event) {\n    let popover = this.popoverCtrl.create('PopoverPage');\n    popover.present({ ev: event });\n  }\n}\n`;\n      const expectedText = `\nimport { Component } from '@angular/core';\n\nimport { PopoverController } from 'ionic-angular';\n\n@IonicPage()\n@Component({\n  selector: 'page-about',\n  templateUrl: 'about.html'\n})\nexport class AboutPage {\n  conferenceDate = '2047-05-17';\n\n  constructor(public popoverCtrl: PopoverController) { }\n\n  presentPopover(event: Event) {\n    let popover = this.popoverCtrl.create('PopoverPage');\n    popover.present({ ev: event });\n  }\n}\n`;\n      const result = util.purgeDeepLinkImport(input);\n      expect(result).toEqual(expectedText);\n    });\n\n    it('should remove the entire import statement', () => {\n      const input = `\nimport { Component } from '@angular/core';\n\nimport { IonicPage } from 'ionic-angular';\n\n@IonicPage()\n@Component({\n  selector: 'page-about',\n  templateUrl: 'about.html'\n})\nexport class AboutPage {\n  conferenceDate = '2047-05-17';\n\n  constructor(public popoverCtrl: PopoverController) { }\n\n  presentPopover(event: Event) {\n    let popover = this.popoverCtrl.create('PopoverPage');\n    popover.present({ ev: event });\n  }\n}\n`;\n      const expectedText = `\nimport { Component } from '@angular/core';\n\n\n\n@IonicPage()\n@Component({\n  selector: 'page-about',\n  templateUrl: 'about.html'\n})\nexport class AboutPage {\n  conferenceDate = '2047-05-17';\n\n  constructor(public popoverCtrl: PopoverController) { }\n\n  presentPopover(event: Event) {\n    let popover = this.popoverCtrl.create('PopoverPage');\n    popover.present({ ev: event });\n  }\n}\n`;\n      const result = util.purgeDeepLinkImport(input);\n      expect(result).toEqual(expectedText);\n    });\n  });\n\n  describe('purgeDeepLinkDecoratorTSTransform', () => {\n    it('should do something', () => {\n      const input = `\nimport { Component } from '@angular/core';\n\nimport { IonicPage } from 'ionic-angular';\n\n@IonicPage()\n@Component({\n  selector: 'page-about',\n  templateUrl: 'about.html'\n})\nexport class AboutPage {\n  conferenceDate = '2047-05-17';\n\n  constructor(public popoverCtrl: PopoverController) { }\n\n  presentPopover(event: Event) {\n    let popover = this.popoverCtrl.create('PopoverPage');\n    popover.present({ ev: event });\n  }\n}\n`;\n\nconst expected = `import { Component } from \"@angular/core\";\nimport { } from \"ionic-angular\";\n@Component({\n    selector: \"page-about\",\n    templateUrl: \"about.html\"\n})\nexport class AboutPage {\n    conferenceDate = \"2047-05-17\";\n    constructor(public popoverCtrl: PopoverController) { }\n    presentPopover(event: Event) {\n        let popover = this.popoverCtrl.create(\"PopoverPage\");\n        popover.present({ ev: event });\n    }\n}\n`;\n      const result = transformSourceFile(input, [util.purgeDeepLinkDecoratorTSTransformImpl]);\n      expect(result).toEqual(expected);\n    });\n  });\n});\n\n\n\nexport function transformSourceFile(sourceText: string, transformers: ts.TransformerFactory<ts.SourceFile>[]) {\n  const transformed = ts.transform(ts.createSourceFile('source.ts', sourceText, ts.ScriptTarget.ES2015), transformers);\n  const printer = ts.createPrinter({ newLine: ts.NewLineKind.LineFeed }, {\n      onEmitNode: transformed.emitNodeWithNotification,\n      substituteNode: transformed.substituteNode\n  });\n  const result = printer.printBundle(ts.createBundle(transformed.transformed));\n  transformed.dispose();\n  return result;\n}\n"
  },
  {
    "path": "src/deep-linking/util.ts",
    "content": "import { basename, dirname, extname, relative, sep } from 'path';\n\nimport {\n  ArrayLiteralExpression,\n  CallExpression,\n  ClassDeclaration,\n  createClassDeclaration,\n  createIdentifier,\n  createNamedImports,\n  Decorator,\n  Expression,\n  Identifier,\n  ImportDeclaration,\n  ImportSpecifier,\n  NamedImports,\n  Node,\n  NodeArray,\n  ObjectLiteralExpression,\n  PropertyAccessExpression,\n  PropertyAssignment,\n  SourceFile,\n  StringLiteral,\n  SyntaxKind,\n  TransformationContext,\n  TransformerFactory,\n  updateCall,\n  updateClassDeclaration,\n  updateImportClause,\n  updateImportDeclaration,\n  updateSourceFile,\n  visitEachChild,\n  VisitResult\n} from 'typescript';\n\nimport { Logger } from '../logger/logger';\nimport * as Constants from '../util/constants';\nimport { FileCache } from '../util/file-cache';\nimport { changeExtension, getParsedDeepLinkConfig, getStringPropertyValue, replaceAll, toUnixPath } from '../util/helpers';\nimport { BuildContext, ChangedFile, DeepLinkConfigEntry, DeepLinkDecoratorAndClass, DeepLinkPathInfo, File } from '../util/interfaces';\nimport {\n  NG_MODULE_DECORATOR_TEXT,\n  appendAfter,\n  findNodes,\n  getClassDeclarations,\n  getNgModuleClassName,\n  getNgModuleDecorator,\n  getNgModuleObjectLiteralArg,\n  getTypescriptSourceFile,\n  getNodeStringContent,\n  replaceNode,\n} from '../util/typescript-utils';\n\nimport { transpileTsString } from '../transpile';\n\nexport function getDeepLinkData(appNgModuleFilePath: string, fileCache: FileCache, isAot: boolean): Map<string, DeepLinkConfigEntry> {\n  // we only care about analyzing a subset of typescript files, so do that for efficiency\n  const typescriptFiles = filterTypescriptFilesForDeepLinks(fileCache);\n  const deepLinkConfigEntries = new Map<string, DeepLinkConfigEntry>();\n  const segmentSet = new Set<string>();\n  typescriptFiles.forEach(file => {\n    const sourceFile = getTypescriptSourceFile(file.path, file.content);\n    const deepLinkDecoratorData = getDeepLinkDecoratorContentForSourceFile(sourceFile);\n\n    if (deepLinkDecoratorData) {\n      // sweet, the page has a DeepLinkDecorator, which means it meets the criteria to process that bad boy\n      const pathInfo = getNgModuleDataFromPage(appNgModuleFilePath, file.path, deepLinkDecoratorData.className, fileCache, isAot);\n      const deepLinkConfigEntry = Object.assign({}, deepLinkDecoratorData, pathInfo);\n\n      if (deepLinkConfigEntries.has(deepLinkConfigEntry.name)) {\n        // gadzooks, it's a duplicate name\n        throw new Error(`There are multiple entries in the deeplink config with the name of ${deepLinkConfigEntry.name}`);\n      }\n\n      if (segmentSet.has(deepLinkConfigEntry.segment)) {\n        // gadzooks, it's a duplicate segment\n        throw new Error(`There are multiple entries in the deeplink config with the segment of ${deepLinkConfigEntry.segment}`);\n      }\n\n      segmentSet.add(deepLinkConfigEntry.segment);\n      deepLinkConfigEntries.set(deepLinkConfigEntry.name, deepLinkConfigEntry);\n    }\n  });\n  return deepLinkConfigEntries;\n}\n\nexport function filterTypescriptFilesForDeepLinks(fileCache: FileCache): File[] {\n  return fileCache.getAll().filter(file => isDeepLinkingFile(file.path));\n}\n\nexport function isDeepLinkingFile(filePath: string) {\n  const deepLinksDir = getStringPropertyValue(Constants.ENV_VAR_DEEPLINKS_DIR) + sep;\n  const moduleSuffix = getStringPropertyValue(Constants.ENV_NG_MODULE_FILE_NAME_SUFFIX);\n  const result = extname(filePath) === '.ts' && filePath.indexOf(moduleSuffix) === -1 && filePath.indexOf(deepLinksDir) >= 0;\n  return result;\n}\n\nexport function getNgModulePathFromCorrespondingPage(filePath: string) {\n  const newExtension = getStringPropertyValue(Constants.ENV_NG_MODULE_FILE_NAME_SUFFIX);\n  return changeExtension(filePath, newExtension);\n}\n\nexport function getRelativePathToPageNgModuleFromAppNgModule(pathToAppNgModule: string, pathToPageNgModule: string) {\n  return relative(dirname(pathToAppNgModule), pathToPageNgModule);\n}\n\nexport function getNgModuleDataFromPage(appNgModuleFilePath: string, filePath: string, className: string, fileCache: FileCache, isAot: boolean): DeepLinkPathInfo {\n  const ngModulePath = getNgModulePathFromCorrespondingPage(filePath);\n  let ngModuleFile = fileCache.get(ngModulePath);\n  if (!ngModuleFile) {\n    throw new Error(`${filePath} has a @IonicPage decorator, but it does not have a corresponding \"NgModule\" at ${ngModulePath}`);\n  }\n  // get the class declaration out of NgModule class content\n  const exportedClassName = getNgModuleClassName(ngModuleFile.path, ngModuleFile.content);\n  const relativePathToAppNgModule = getRelativePathToPageNgModuleFromAppNgModule(appNgModuleFilePath, ngModulePath);\n\n  const absolutePath = isAot ? changeExtension(ngModulePath, '.ngfactory.js') : changeExtension(ngModulePath, '.ts');\n  const userlandModulePath = isAot ? changeExtension(relativePathToAppNgModule, '.ngfactory') : changeExtension(relativePathToAppNgModule, '');\n  const namedExport = isAot ? `${exportedClassName}NgFactory` : exportedClassName;\n\n  return {\n    absolutePath: absolutePath,\n    userlandModulePath: toUnixPath(userlandModulePath),\n    className: namedExport\n  };\n}\n\nexport function getDeepLinkDecoratorContentForSourceFile(sourceFile: SourceFile): DeepLinkDecoratorAndClass {\n  const classDeclarations = getClassDeclarations(sourceFile);\n  const defaultSegment = basename(changeExtension(sourceFile.fileName, ''));\n  const list: DeepLinkDecoratorAndClass[] = [];\n\n  classDeclarations.forEach(classDeclaration => {\n    if (classDeclaration.decorators) {\n      classDeclaration.decorators.forEach(decorator => {\n        const className = (classDeclaration.name as Identifier).text;\n        if (decorator.expression && (decorator.expression as CallExpression).expression && ((decorator.expression as CallExpression).expression as Identifier).text === DEEPLINK_DECORATOR_TEXT) {\n\n          const deepLinkArgs = (decorator.expression as CallExpression).arguments;\n          let deepLinkObject: ObjectLiteralExpression = null;\n          if (deepLinkArgs && deepLinkArgs.length) {\n            deepLinkObject = deepLinkArgs[0] as ObjectLiteralExpression;\n          }\n          let propertyList: Node[] = [];\n          if (deepLinkObject && deepLinkObject.properties) {\n            propertyList = deepLinkObject.properties as any as Node[]; // TODO this typing got jacked up\n          }\n\n          const deepLinkName = getStringValueFromDeepLinkDecorator(sourceFile, propertyList, className, DEEPLINK_DECORATOR_NAME_ATTRIBUTE);\n          const deepLinkSegment = getStringValueFromDeepLinkDecorator(sourceFile, propertyList, defaultSegment, DEEPLINK_DECORATOR_SEGMENT_ATTRIBUTE);\n          const deepLinkPriority = getStringValueFromDeepLinkDecorator(sourceFile, propertyList, 'low', DEEPLINK_DECORATOR_PRIORITY_ATTRIBUTE);\n          const deepLinkDefaultHistory = getArrayValueFromDeepLinkDecorator(sourceFile, propertyList, [], DEEPLINK_DECORATOR_DEFAULT_HISTORY_ATTRIBUTE);\n          const rawStringContent = getNodeStringContent(sourceFile, decorator.expression);\n          list.push({\n            name: deepLinkName,\n            segment: deepLinkSegment,\n            priority: deepLinkPriority,\n            defaultHistory: deepLinkDefaultHistory,\n            rawString: rawStringContent,\n            className: className\n          });\n        }\n      });\n    }\n  });\n\n  if (list.length > 1) {\n    throw new Error('Only one @IonicPage decorator is allowed per file.');\n  }\n\n  if (list.length === 1) {\n    return list[0];\n  }\n  return null;\n}\n\nfunction getStringValueFromDeepLinkDecorator(sourceFile: SourceFile, propertyNodeList: Node[], defaultValue: string, identifierToLookFor: string) {\n  try {\n    let valueToReturn = defaultValue;\n    Logger.debug(`[DeepLinking util] getNameValueFromDeepLinkDecorator: Setting default deep link ${identifierToLookFor} to ${defaultValue}`);\n    propertyNodeList.forEach(propertyNode => {\n      if (propertyNode && (propertyNode as PropertyAssignment).name && ((propertyNode as PropertyAssignment).name as Identifier).text === identifierToLookFor) {\n        const initializer = ((propertyNode as PropertyAssignment).initializer as Expression);\n        let stringContent = getNodeStringContent(sourceFile, initializer);\n        stringContent = replaceAll(stringContent, '\\'', '');\n        stringContent = replaceAll(stringContent, '`', '');\n        stringContent = replaceAll(stringContent, '\"', '');\n        stringContent = stringContent.trim();\n        valueToReturn = stringContent;\n      }\n    });\n    Logger.debug(`[DeepLinking util] getNameValueFromDeepLinkDecorator: DeepLink ${identifierToLookFor} set to ${valueToReturn}`);\n    return valueToReturn;\n  } catch (ex) {\n    Logger.error(`Failed to parse the @IonicPage decorator. The ${identifierToLookFor} must be an array of strings`);\n    throw ex;\n  }\n}\n\nfunction getArrayValueFromDeepLinkDecorator(sourceFile: SourceFile, propertyNodeList: Node[], defaultValue: string[], identifierToLookFor: string) {\n  try {\n    let valueToReturn = defaultValue;\n    Logger.debug(`[DeepLinking util] getArrayValueFromDeepLinkDecorator: Setting default deep link ${identifierToLookFor} to ${defaultValue}`);\n    propertyNodeList.forEach(propertyNode => {\n      if (propertyNode && (propertyNode as PropertyAssignment).name && ((propertyNode as PropertyAssignment).name as Identifier).text === identifierToLookFor) {\n        const initializer = ((propertyNode as PropertyAssignment).initializer as ArrayLiteralExpression);\n        if (initializer && initializer.elements) {\n          const stringArray = initializer.elements.map((element: Identifier)  => {\n            let elementText = element.text;\n            elementText = replaceAll(elementText, '\\'', '');\n            elementText = replaceAll(elementText, '`', '');\n            elementText = replaceAll(elementText, '\"', '');\n            elementText = elementText.trim();\n            return elementText;\n          });\n          valueToReturn = stringArray;\n        }\n      }\n    });\n    Logger.debug(`[DeepLinking util] getNameValueFromDeepLinkDecorator: DeepLink ${identifierToLookFor} set to ${valueToReturn}`);\n    return valueToReturn;\n  } catch (ex) {\n    Logger.error(`Failed to parse the @IonicPage decorator. The ${identifierToLookFor} must be an array of strings`);\n    throw ex;\n  }\n}\n\nexport function hasExistingDeepLinkConfig(appNgModuleFilePath: string, appNgModuleFileContent: string) {\n  const sourceFile = getTypescriptSourceFile(appNgModuleFilePath, appNgModuleFileContent);\n  const decorator = getNgModuleDecorator(appNgModuleFilePath, sourceFile);\n  const functionCall = getIonicModuleForRootCall(decorator);\n\n  if (functionCall.arguments.length <= 2) {\n    return false;\n  }\n\n  const deepLinkConfigArg = functionCall.arguments[2];\n  if (deepLinkConfigArg.kind === SyntaxKind.NullKeyword || deepLinkConfigArg.kind === SyntaxKind.UndefinedKeyword) {\n    return false;\n  }\n\n  if (deepLinkConfigArg.kind === SyntaxKind.ObjectLiteralExpression) {\n    return true;\n  }\n\n  if ((deepLinkConfigArg as Identifier).text && (deepLinkConfigArg as Identifier).text.length > 0) {\n    return true;\n  }\n}\n\nfunction getIonicModuleForRootCall(decorator: Decorator) {\n  const argument = getNgModuleObjectLiteralArg(decorator);\n  const properties = argument.properties.filter((property: PropertyAssignment) => {\n    return (property.name as Identifier).text === NG_MODULE_IMPORT_DECLARATION;\n  });\n\n  if (properties.length === 0) {\n    throw new Error('Could not find \"import\" property in NgModule arguments');\n  }\n\n  if (properties.length > 1) {\n    throw new Error('Found multiple \"import\" properties in NgModule arguments. Only one is allowed');\n  }\n\n  const property = properties[0] as PropertyAssignment;\n  const importArrayLiteral = property.initializer as ArrayLiteralExpression;\n  const functionsInImport = importArrayLiteral.elements.filter(element => {\n    return element.kind === SyntaxKind.CallExpression;\n  });\n\n  const ionicModuleFunctionCalls = functionsInImport.filter((functionNode: CallExpression) => {\n\n    return (functionNode.expression\n      && (functionNode.expression as PropertyAccessExpression).name\n      && (functionNode.expression as PropertyAccessExpression).name.text === FOR_ROOT_METHOD\n      && ((functionNode.expression as PropertyAccessExpression).expression as Identifier)\n      && ((functionNode.expression as PropertyAccessExpression).expression as Identifier).text === IONIC_MODULE_NAME);\n  });\n\n  if (ionicModuleFunctionCalls.length === 0) {\n    throw new Error('Could not find IonicModule.forRoot call in \"imports\"');\n  }\n\n  if (ionicModuleFunctionCalls.length > 1) {\n    throw new Error('Found multiple IonicModule.forRoot calls in \"imports\". Only one is allowed');\n  }\n\n  return ionicModuleFunctionCalls[0] as CallExpression;\n}\n\nexport function convertDeepLinkConfigEntriesToString(entries: Map<string, DeepLinkConfigEntry>) {\n  const individualLinks: string[] = [];\n  entries.forEach(entry => {\n    individualLinks.push(convertDeepLinkEntryToJsObjectString(entry));\n  });\n  const deepLinkConfigString =\n`\n{\n  links: [\n    ${individualLinks.join(',\\n    ')}\n  ]\n}`;\n  return deepLinkConfigString;\n}\n\nexport function convertDeepLinkEntryToJsObjectString(entry: DeepLinkConfigEntry) {\n  const defaultHistoryWithQuotes = entry.defaultHistory.map(defaultHistoryEntry => `'${defaultHistoryEntry}'`);\n  const segmentString = entry.segment && entry.segment.length ? `'${entry.segment}'` : null;\n  return `{ loadChildren: '${entry.userlandModulePath}${LOAD_CHILDREN_SEPARATOR}${entry.className}', name: '${entry.name}', segment: ${segmentString}, priority: '${entry.priority}', defaultHistory: [${defaultHistoryWithQuotes.join(', ')}] }`;\n}\n\nexport function updateAppNgModuleWithDeepLinkConfig(context: BuildContext, deepLinkString: string, changedFiles: ChangedFile[]) {\n  const appNgModulePath = getStringPropertyValue(Constants.ENV_APP_NG_MODULE_PATH);\n  const appNgModuleFile = context.fileCache.get(appNgModulePath);\n\n  if (!appNgModuleFile) {\n    throw new Error(`App NgModule ${appNgModulePath} not found in cache`);\n  }\n\n  const updatedAppNgModuleContent = getUpdatedAppNgModuleContentWithDeepLinkConfig(appNgModulePath, appNgModuleFile.content, deepLinkString);\n  context.fileCache.set(appNgModulePath, { path: appNgModulePath, content: updatedAppNgModuleContent});\n\n  if (changedFiles) {\n    changedFiles.push({\n      event: 'change',\n      filePath: appNgModulePath,\n      ext: extname(appNgModulePath).toLowerCase()\n    });\n  }\n}\n\nexport function getUpdatedAppNgModuleContentWithDeepLinkConfig(appNgModuleFilePath: string, appNgModuleFileContent: string, deepLinkStringContent: string) {\n  let sourceFile = getTypescriptSourceFile(appNgModuleFilePath, appNgModuleFileContent);\n  let decorator = getNgModuleDecorator(appNgModuleFilePath, sourceFile);\n  let functionCall = getIonicModuleForRootCall(decorator);\n\n  if (functionCall.arguments.length === 1) {\n    appNgModuleFileContent = addDefaultSecondArgumentToAppNgModule(appNgModuleFileContent, functionCall);\n    sourceFile = getTypescriptSourceFile(appNgModuleFilePath, appNgModuleFileContent);\n    decorator = getNgModuleDecorator(appNgModuleFilePath, sourceFile);\n    functionCall = getIonicModuleForRootCall(decorator);\n  }\n\n  if (functionCall.arguments.length === 2) {\n    // we need to add the node\n    return addDeepLinkArgumentToAppNgModule(appNgModuleFileContent, functionCall, deepLinkStringContent);\n  }\n  // we need to replace whatever node exists here with the deeplink config\n  return replaceNode(appNgModuleFilePath, appNgModuleFileContent, functionCall.arguments[2], deepLinkStringContent);\n}\n\nexport function getUpdatedAppNgModuleFactoryContentWithDeepLinksConfig(appNgModuleFactoryFileContent: string, deepLinkStringContent: string) {\n  // tried to do this with typescript API, wasn't clear on how to do it\n  const regex = /this.*?DeepLinkConfigToken.*?=([\\s\\S]*?);/g;\n  const results = regex.exec(appNgModuleFactoryFileContent);\n  if (results && results.length === 2) {\n    const actualString = results[0];\n    const chunkToReplace = results[1];\n    const fullStringToReplace = actualString.replace(chunkToReplace, deepLinkStringContent);\n    return appNgModuleFactoryFileContent.replace(actualString, fullStringToReplace);\n  }\n\n  throw new Error('The RegExp to find the DeepLinkConfigToken did not return valid data');\n}\n\nexport function addDefaultSecondArgumentToAppNgModule(appNgModuleFileContent: string, ionicModuleForRoot: CallExpression) {\n  const argOneNode = ionicModuleForRoot.arguments[0];\n  const updatedFileContent = appendAfter(appNgModuleFileContent, argOneNode, ', {}');\n  return updatedFileContent;\n}\n\nexport function addDeepLinkArgumentToAppNgModule(appNgModuleFileContent: string, ionicModuleForRoot: CallExpression, deepLinkString: string) {\n  const argTwoNode = ionicModuleForRoot.arguments[1];\n  const updatedFileContent = appendAfter(appNgModuleFileContent, argTwoNode, `, ${deepLinkString}`);\n  return updatedFileContent;\n}\n\nexport function generateDefaultDeepLinkNgModuleContent(pageFilePath: string, className: string) {\n  const importFrom = basename(pageFilePath, '.ts');\n\n  return `\nimport { NgModule } from '@angular/core';\nimport { IonicPageModule } from 'ionic-angular';\nimport { ${className} } from './${importFrom}';\n\n@NgModule({\n  declarations: [\n    ${className},\n  ],\n  imports: [\n    IonicPageModule.forChild(${className})\n  ]\n})\nexport class ${className}Module {}\n\n`;\n}\n\nexport function purgeDeepLinkDecoratorTSTransform(): TransformerFactory<SourceFile> {\n  return purgeDeepLinkDecoratorTSTransformImpl;\n}\n\nexport function purgeDeepLinkDecoratorTSTransformImpl(transformContext: TransformationContext) {\n  function visitClassDeclaration(classDeclaration: ClassDeclaration) {\n    let hasDeepLinkDecorator = false;\n    const diffDecorators: Decorator[] = [];\n    for (const decorator of classDeclaration.decorators || []) {\n      if (decorator.expression && (decorator.expression as CallExpression).expression\n        && ((decorator.expression as CallExpression).expression as Identifier).text === DEEPLINK_DECORATOR_TEXT) {\n        hasDeepLinkDecorator = true;\n      } else {\n        diffDecorators.push(decorator);\n      }\n    }\n\n    if (hasDeepLinkDecorator) {\n      return updateClassDeclaration(\n        classDeclaration,\n        diffDecorators,\n        classDeclaration.modifiers,\n        classDeclaration.name,\n        classDeclaration.typeParameters,\n        classDeclaration.heritageClauses,\n        classDeclaration.members\n      );\n\n    }\n\n    return classDeclaration;\n  }\n\n  function visitImportDeclaration(importDeclaration: ImportDeclaration, sourceFile: SourceFile): ImportDeclaration {\n\n    if (importDeclaration.moduleSpecifier\n        && (importDeclaration.moduleSpecifier as StringLiteral).text === 'ionic-angular'\n        && importDeclaration.importClause\n        && importDeclaration.importClause.namedBindings\n        && (importDeclaration.importClause.namedBindings as NamedImports).elements\n    ) {\n      // loop over each import and store it\n      const importSpecifiers: ImportSpecifier[] = [];\n      (importDeclaration.importClause.namedBindings as NamedImports).elements.forEach((importSpecifier: ImportSpecifier) => {\n\n        if (importSpecifier.name.text !== DEEPLINK_DECORATOR_TEXT) {\n          importSpecifiers.push(importSpecifier);\n        }\n      });\n      const emptyNamedImports = createNamedImports(importSpecifiers);\n      const newImportClause = updateImportClause(importDeclaration.importClause, importDeclaration.importClause.name, emptyNamedImports);\n\n      return updateImportDeclaration(\n        importDeclaration,\n        importDeclaration.decorators,\n        importDeclaration.modifiers,\n        newImportClause,\n        importDeclaration.moduleSpecifier\n      );\n    }\n\n    return importDeclaration;\n  }\n\n  function visit(node: Node, sourceFile: SourceFile): VisitResult<Node> {\n    switch (node.kind) {\n\n      case SyntaxKind.ClassDeclaration:\n        return visitClassDeclaration(node as ClassDeclaration);\n\n      case SyntaxKind.ImportDeclaration:\n        return visitImportDeclaration(node as ImportDeclaration, sourceFile);\n      default:\n        return visitEachChild(node, (node) => {\n          return visit(node, sourceFile);\n        }, transformContext);\n    }\n  }\n\n  return (sourceFile: SourceFile) => {\n    return visit(sourceFile, sourceFile) as SourceFile;\n  };\n}\n\nexport function purgeDeepLinkDecorator(inputText: string): string {\n  const sourceFile = getTypescriptSourceFile('', inputText);\n  const classDeclarations = getClassDeclarations(sourceFile);\n  const toRemove: Node[] = [];\n  let toReturn: string = inputText;\n  for (const classDeclaration of classDeclarations) {\n    for (const decorator of classDeclaration.decorators || []) {\n      if (decorator.expression && (decorator.expression as CallExpression).expression\n        && ((decorator.expression as CallExpression).expression as Identifier).text === DEEPLINK_DECORATOR_TEXT) {\n        toRemove.push(decorator);\n      }\n    }\n  }\n  toRemove.forEach(node => {\n    toReturn = replaceNode('', inputText, node, '');\n  });\n\n  toReturn = purgeDeepLinkImport(toReturn);\n  return toReturn;\n}\n\nexport function purgeDeepLinkImport(inputText: string): string {\n  const sourceFile = getTypescriptSourceFile('', inputText);\n  const importDeclarations = findNodes(sourceFile, sourceFile, SyntaxKind.ImportDeclaration) as ImportDeclaration[];\n\n  importDeclarations.forEach(importDeclaration => {\n    if (importDeclaration.moduleSpecifier\n        && (importDeclaration.moduleSpecifier as StringLiteral).text === 'ionic-angular'\n        && importDeclaration.importClause\n        && importDeclaration.importClause.namedBindings\n        && (importDeclaration.importClause.namedBindings as NamedImports).elements\n    ) {\n      // loop over each import and store it\n      let decoratorIsImported = false;\n      const namedImportStrings: string[] = [];\n      (importDeclaration.importClause.namedBindings as NamedImports).elements.forEach((importSpecifier: ImportSpecifier) => {\n\n        if (importSpecifier.name.text === DEEPLINK_DECORATOR_TEXT) {\n          decoratorIsImported = true;\n        } else {\n          namedImportStrings.push(importSpecifier.name.text as string);\n        }\n      });\n\n      // okay, cool. If namedImportStrings is empty, then just remove the entire import statement\n      // otherwise, just replace the named imports with the namedImportStrings separated by a comma\n      if (decoratorIsImported) {\n        if (namedImportStrings.length) {\n          // okay cool, we only want to remove some of these homies\n          const stringRepresentation = namedImportStrings.join(', ');\n          const namedImportString = `{ ${stringRepresentation} }`;\n          inputText = replaceNode('', inputText, importDeclaration.importClause.namedBindings, namedImportString);\n        } else {\n          // remove the entire import statement\n          inputText = replaceNode('', inputText, importDeclaration, '');\n        }\n      }\n    }\n  });\n\n  return inputText;\n}\n\nexport function getInjectDeepLinkConfigTypescriptTransform() {\n  const deepLinkString = convertDeepLinkConfigEntriesToString(getParsedDeepLinkConfig());\n  const appNgModulePath = toUnixPath(getStringPropertyValue(Constants.ENV_APP_NG_MODULE_PATH));\n  return injectDeepLinkConfigTypescriptTransform(deepLinkString, appNgModulePath);\n}\n\nexport function injectDeepLinkConfigTypescriptTransform(deepLinkString: string, appNgModuleFilePath: string): TransformerFactory<SourceFile> {\n\n  function visitDecoratorNode(decorator: Decorator, sourceFile: SourceFile): Decorator {\n    if (decorator.expression && (decorator.expression as CallExpression).expression && ((decorator.expression as CallExpression).expression as Identifier).text === NG_MODULE_DECORATOR_TEXT) {\n\n      // okay cool, we have the ng module\n      let functionCall = getIonicModuleForRootCall(decorator);\n\n      const updatedArgs: any[] = functionCall.arguments as any as any[];\n\n      if (updatedArgs.length === 1) {\n        updatedArgs.push(createIdentifier('{ }'));\n      }\n\n      if (updatedArgs.length === 2) {\n        updatedArgs.push(createIdentifier(deepLinkString));\n      }\n\n      functionCall = updateCall(\n        functionCall,\n        functionCall.expression,\n        functionCall.typeArguments,\n        updatedArgs\n      );\n\n      // loop over the parent elements and replace the IonicModule expression with ours'\n\n      for (let i = 0; i < ((functionCall.parent as any).elements || []).length; i++) {\n        const element = (functionCall.parent as any).elements[i];\n        if (element.king === SyntaxKind.CallExpression\n            && element.expression\n            && element.expression.expression\n            && element.expression.expression.escapedText === 'IonicModule'\n        ) {\n          (functionCall.parent as any).elements[i] = functionCall;\n        }\n      }\n    }\n\n    return decorator;\n  }\n\n  return (transformContext: TransformationContext) => {\n\n    function visit(node: Node, sourceFile: SourceFile, sourceFilePath: string): VisitResult<Node> {\n      if (sourceFilePath !== appNgModuleFilePath) {\n        return node;\n      }\n\n      switch (node.kind) {\n        case SyntaxKind.Decorator:\n          return visitDecoratorNode(node as Decorator, sourceFile);\n\n        default:\n          return visitEachChild(node, (node) => {\n            return visit(node, sourceFile, sourceFilePath);\n          }, transformContext);\n      }\n\n    }\n\n    return (sourceFile: SourceFile) => {\n      return visit(sourceFile, sourceFile, sourceFile.fileName) as SourceFile;\n    };\n  };\n}\n\nconst DEEPLINK_DECORATOR_TEXT = 'IonicPage';\nconst DEEPLINK_DECORATOR_NAME_ATTRIBUTE = 'name';\nconst DEEPLINK_DECORATOR_SEGMENT_ATTRIBUTE = 'segment';\nconst DEEPLINK_DECORATOR_PRIORITY_ATTRIBUTE = 'priority';\nconst DEEPLINK_DECORATOR_DEFAULT_HISTORY_ATTRIBUTE = 'defaultHistory';\n\nconst NG_MODULE_IMPORT_DECLARATION = 'imports';\nconst IONIC_MODULE_NAME = 'IonicModule';\nconst FOR_ROOT_METHOD = 'forRoot';\nconst LOAD_CHILDREN_SEPARATOR = '#';\n"
  },
  {
    "path": "src/deep-linking.spec.ts",
    "content": "import { join } from 'path';\n\nimport * as deepLinking from './deep-linking';\nimport * as deeplinkUtils from './deep-linking/util';\nimport * as Constants from './util/constants';\nimport { BuildState, ChangedFile, DeepLinkConfigEntry } from './util/interfaces';\nimport { FileCache } from './util/file-cache';\nimport * as helpers from './util/helpers';\n\ndescribe('Deep Linking task', () => {\n  describe('deepLinkingWorkerImpl', () => {\n\n   it('should not update app ngmodule when it has an existing deeplink config', () => {\n      const appNgModulePath = join('some', 'fake', 'path', 'myApp', 'src', 'app', 'app.module.ts');\n      const context = {\n        fileCache: new FileCache()\n      };\n      const knownFileContent = 'someFileContent';\n      const knownDeepLinkString = 'someDeepLinkString';\n      context.fileCache.set(appNgModulePath, { path: appNgModulePath, content: knownFileContent});\n\n      spyOn(helpers, helpers.getStringPropertyValue.name).and.returnValue(appNgModulePath);\n      spyOn(helpers, helpers.readAndCacheFile.name).and.returnValue(Promise.resolve(knownFileContent));\n      spyOn(deeplinkUtils, deeplinkUtils.hasExistingDeepLinkConfig.name).and.returnValue(true);\n\n      const promise = deepLinking.deepLinkingWorkerImpl(context, null);\n\n      return promise.then((results: Map<string, DeepLinkConfigEntry>) => {\n        expect(deeplinkUtils.hasExistingDeepLinkConfig).toHaveBeenCalled();\n        expect(results.size).toEqual(0);\n      });\n    });\n  });\n});\n"
  },
  {
    "path": "src/deep-linking.ts",
    "content": "import { extname } from 'path';\n\nimport { scanSrcTsFiles } from './build/util';\nimport { Logger } from './logger/logger';\nimport * as Constants from './util/constants';\nimport { BuildError } from './util/errors';\nimport { getParsedDeepLinkConfig, getStringPropertyValue, readAndCacheFile, setParsedDeepLinkConfig } from './util/helpers';\nimport { BuildContext, BuildState, ChangedFile, DeepLinkConfigEntry } from './util/interfaces';\n\nimport {\n  convertDeepLinkConfigEntriesToString,\n  getDeepLinkData,\n  hasExistingDeepLinkConfig\n} from './deep-linking/util';\n\nexport let existingDeepLinkConfigString: string = null;\n\nexport function setExistingDeepLinkConfig(newString: string) {\n  existingDeepLinkConfigString = newString;\n}\n\nexport function deepLinking(context: BuildContext) {\n  const logger = new Logger(`deeplinks`);\n\n  return deepLinkingWorker(context).then((map: Map<string, DeepLinkConfigEntry>) => {\n    setParsedDeepLinkConfig(map);\n    logger.finish();\n  })\n  .catch((err: Error) => {\n    const error = new BuildError(err.message);\n    error.isFatal = true;\n    throw logger.fail(error);\n  });\n}\n\n\nfunction deepLinkingWorker(context: BuildContext) {\n  return deepLinkingWorkerImpl(context, []);\n}\n\nexport async function deepLinkingWorkerImpl(context: BuildContext, changedFiles: ChangedFile[]): Promise<Map<string, DeepLinkConfigEntry>> {\n\n  // get the app.module.ts content from ideally the cache, but fall back to disk if needed\n  const appNgModulePath = getStringPropertyValue(Constants.ENV_APP_NG_MODULE_PATH);\n  const appNgModuleFileContent = await getAppMainNgModuleFile(appNgModulePath);\n\n  // is there is an existing (legacy) deep link config, just move on and don't look for decorators\n  const hasExisting = hasExistingDeepLinkConfig(appNgModulePath, appNgModuleFileContent);\n  if (hasExisting) {\n    return new Map<string, DeepLinkConfigEntry>();\n  }\n\n  // okay cool, we need to get the data from each file\n  const results = getDeepLinkData(appNgModulePath, context.fileCache, context.runAot) || new Map<string, DeepLinkConfigEntry>();\n\n  const newDeepLinkString = convertDeepLinkConfigEntriesToString(results);\n  if (!existingDeepLinkConfigString || newDeepLinkString !== existingDeepLinkConfigString || hasAppModuleChanged(changedFiles, appNgModulePath)) {\n\n    existingDeepLinkConfigString = newDeepLinkString;\n\n    if (changedFiles) {\n      changedFiles.push({\n        event: 'change',\n        filePath: appNgModulePath,\n        ext: extname(appNgModulePath).toLowerCase()\n      });\n    }\n  }\n\n  return results;\n}\n\nexport function deepLinkingUpdate(changedFiles: ChangedFile[], context: BuildContext) {\n  if (context.deepLinkState === BuildState.RequiresBuild) {\n    return deepLinkingWorkerFullUpdate(context);\n  } else {\n    return deepLinkingUpdateImpl(changedFiles, context);\n  }\n}\n\nexport function deepLinkingUpdateImpl(changedFiles: ChangedFile[], context: BuildContext) {\n  const tsFiles = changedFiles.filter(changedFile => changedFile.ext === '.ts');\n  if (tsFiles.length === 0) {\n    return Promise.resolve();\n  }\n  const logger = new Logger('deeplinks update');\n  return deepLinkingWorkerImpl(context, changedFiles).then((map: Map<string, DeepLinkConfigEntry>) => {\n    // okay, now that the existing config is updated, go ahead and reset it\n    setParsedDeepLinkConfig(map);\n    logger.finish();\n  }).catch((err: Error) => {\n    Logger.warn(err.message);\n    const error = new BuildError(err.message);\n    throw logger.fail(error);\n  });\n}\n\nexport function deepLinkingWorkerFullUpdate(context: BuildContext) {\n  const logger = new Logger(`deeplinks update`);\n  return deepLinkingWorker(context).then((map: Map<string, DeepLinkConfigEntry>) => {\n    setParsedDeepLinkConfig(map);\n    logger.finish();\n  })\n  .catch((err: Error) => {\n    Logger.warn(err.message);\n    const error = new BuildError(err.message);\n    throw logger.fail(error);\n  });\n}\n\nexport async function getAppMainNgModuleFile(appNgModulePath: string) {\n  try {\n    return await readAndCacheFile(appNgModulePath);\n  } catch (ex) {\n    throw new Error(`The main app NgModule was not found at the following path: ${appNgModulePath}`);\n  }\n}\n\nexport function hasAppModuleChanged(changedFiles: ChangedFile[], appNgModulePath: string) {\n  if (!changedFiles) {\n    changedFiles = [];\n  }\n  for (const changedFile of changedFiles) {\n    if (changedFile.filePath === appNgModulePath) {\n      return true;\n    }\n  }\n  return false;\n}\n"
  },
  {
    "path": "src/dev-client/sass/_code-block.scss",
    "content": "\n#ion-diagnostics .ion-diagnostic-blob {\n  overflow-x: auto;\n  overflow-y: hidden;\n  border-bottom-right-radius: 3px;\n  border-bottom-left-radius: 3px;\n}\n\n#ion-diagnostics .ion-diagnostic-table {\n  border-spacing: 0;\n  border-collapse: collapse;\n  -moz-tab-size: $tab-size;\n  tab-size: $tab-size;\n}\n\n#ion-diagnostics .ion-diagnostic-table td,\n#ion-diagnostics .ion-diagnostic-table th {\n  padding: 0;\n}\n\n#ion-diagnostics  td.ion-diagnostic-blob-num {\n  padding-right: 10px;\n  padding-left: 10px;\n  width: 1%;\n  min-width: 50px;\n  font-family: $code-font-family;\n  font-size: 12px;\n  line-height: 20px;\n  color: rgba(0,0,0,0.3);\n  text-align: right;\n  white-space: nowrap;\n  vertical-align: top;\n  -webkit-user-select: none;\n  -moz-user-select: none;\n  -ms-user-select: none;\n  user-select: none;\n  border: solid #eee;\n  border-width: 0 1px 0 0;\n}\n\n#ion-diagnostics .ion-diagnostic-blob-num::before {\n  content: attr(data-line-number);\n}\n\n#ion-diagnostics .ion-diagnostic-error-line .ion-diagnostic-blob-num {\n  background-color: $code-error-highlight;\n  border-color: darken($code-error-highlight, 4%);\n}\n\n#ion-diagnostics .ion-diagnostic-error-line .ion-diagnostic-blob-code {\n  background: $code-error-background;\n  z-index: -1;\n}\n\n#ion-diagnostics .ion-diagnostics-error-chr {\n  position: relative;\n}\n\n#ion-diagnostics .ion-diagnostics-error-chr::before {\n  position: absolute;\n  z-index: -1;\n  top: -3px;\n  left: 0px;\n  width: 8px;\n  height: 20px;\n  background-color: $code-error-highlight;\n  content: \"\";\n}\n\n#ion-diagnostics td.ion-diagnostic-blob-code {\n  position: relative;\n  padding-right: 10px;\n  padding-left: 10px;\n  line-height: 20px;\n  vertical-align: top;\n  overflow: visible;\n  font-family: $code-font-family;\n  font-size: 12px;\n  color: #333;\n  word-wrap: normal;\n  white-space: pre;\n}\n\n#ion-diagnostics .ion-diagnostic-blob-code::before {\n  content: \"\";\n}\n"
  },
  {
    "path": "src/dev-client/sass/_diagnostics.scss",
    "content": "\n#ion-diagnostics * {\n  box-sizing: border-box;\n}\n\n#ion-diagnostics {\n  direction: ltr;\n  \n  position: absolute;\n  top: 0;\n  right: 0;\n  bottom: 0;\n  left: 0;\n  z-index: $z-index-diagnostics;\n\n  margin: 0;\n  padding: 0;\n\n  font-family: $body-font-family;\n  font-size: 14px;\n  line-height: 1.5;\n  -webkit-font-smoothing: antialiased;\n  font-smoothing: antialiased;\n  text-rendering: optimizeLegibility;\n  text-size-adjust: none;\n  word-wrap: break-word;\n\n  color: $header-text-color;\n  background-color: $background-color;\n\n  box-sizing: border-box;\n  overflow: hidden;\n  user-select: auto;\n}\n\n.ion-diagnostics-content {\n  position: relative;\n  padding: 0 0 30px 0;\n  width: 100%;\n  height: 100%;\n\n  overflow-x: hidden;\n  overflow-y: scroll;\n  -webkit-overflow-scrolling: touch;\n}\n\n#ion-diagnostics .ion-diagnostic {\n  margin: 20px;\n  border: 1px solid $diagnostics-border;\n  border-radius: 3px;\n}\n\n#ion-diagnostics .ion-diagnostic-masthead {\n  padding: 8px 12px 12px 12px;\n}\n\n#ion-diagnostics .ion-diagnostic-title {\n  margin: 0;\n  font-size: 16px;\n  color: $diagnostics-title-color;\n}\n\n#ion-diagnostics .ion-diagnostic-message {\n  margin-top: 4px;\n  color: $diagnostics-message-color;\n}\n\n#ion-diagnostics .ion-diagnostic-file {\n  position: relative;\n  border-top: 1px solid $diagnostics-border;\n}\n\n#ion-diagnostics .ion-diagnostic-file-header {\n  padding: 5px 10px;\n  border-bottom: 1px solid $diagnostics-border;\n  border-top-left-radius: 2px;\n  border-top-right-radius: 2px;\n  background-color: $diagnostics-header-background;\n}\n\n#ion-diagnostics {\n  -webkit-transition: opacity 150ms ease-out;\n  transition: opacity 150ms ease-out;\n}\n\n#ion-diagnostics.ion-diagnostics-fade-out {\n  opacity: 0;\n}\n"
  },
  {
    "path": "src/dev-client/sass/_header.scss",
    "content": "\n.ion-diagnostics-header {\n  background: $header-background;\n  border-bottom: $hairlines-width solid $header-border-color;\n  color: $header-text-color;\n}\n\n.ion-diagnostics-cordova-ios .ion-diagnostics-header {\n  padding-top: 20px;\n}\n\n.ion-diagnostics-header-content {\n  display: -webkit-flex;\n  display: flex;\n\n  overflow: hidden;\n\n  -webkit-flex-direction: row;\n  flex-direction: row;\n\n  -webkit-align-items: center;\n  align-items: center;\n\n  -webkit-justify-content: space-between;\n  justify-content: space-between;\n\n  width: 100%;\n  min-height: 44px;\n}\n\n.ion-diagnostics-header-inner {\n  -webkit-flex: 1;\n  flex: 1;\n\n  display: -webkit-flex;\n  display: flex;\n\n  padding: 0 20px;\n  font-size: 20px;\n}\n\n.ion-diagnostics-buttons {\n  display: -webkit-flex;\n  display: flex;\n\n  padding: 0 0 0 20px;\n}\n\n#ion-diagnostic-close {\n  margin: 0;\n  padding: 10px 20px;\n  border: 0;\n  outline: none;\n  background: transparent;\n\n  font-size: 14px;\n  color: $ionic-blue;\n\n  -moz-appearance: none;\n  -ms-appearance: none;\n  -webkit-appearance: none;\n  appearance: none;\n  cursor: pointer;\n}\n\n#ion-diagnostic-close:hover {\n  opacity: 0.7;\n}\n"
  },
  {
    "path": "src/dev-client/sass/_options-menu.scss",
    "content": "\n$action-sheet-width:                             100% !default;\n$action-sheet-max-width:                         500px !default;\n\n\n#ion-diagnostics-options {\n  position: absolute;\n  top: 0;\n  left: 0;\n  z-index: $z-index-menu;\n\n  width: $action-sheet-width;\n  height: $action-sheet-width;\n\n  font-family: $body-font-family;\n}\n\n.ion-diagnostics-sheet-wrapper {\n  position: absolute;\n  right: 0;\n  bottom: 0;\n  left: 0;\n  z-index: 10;\n  display: block;\n\n  margin: auto;\n\n  width: $action-sheet-width;\n  max-width: $action-sheet-max-width;\n\n  transition: transform 300ms cubic-bezier(.36,.66,.04,1);\n  transform: translate3d(0, 100%, 0);\n}\n\n#ion-diagnostics-backdrop {\n  width: 100%;\n  height: 100%;\n  opacity: 0.01;\n  background: black;\n  transition: opacity 300ms cubic-bezier(.36,.66,.04,1);\n}\n\n.ion-diagnostics-options-show .ion-diagnostics-sheet-wrapper {\n  transform: translate3d(0, 0, 0);\n}\n\n.ion-diagnostics-options-show #ion-diagnostics-backdrop {\n  opacity: 0.4;\n}\n\n.ion-diagnostics-sheet-container {\n  padding: 0 10px;\n}\n\n.ion-diagnostics-sheet-group {\n  overflow: hidden;\n\n  margin-bottom: 10px - 2;\n\n  border-radius: 13px;\n  background: #f9f9f9;\n}\n\n.ion-diagnostics-sheet-group:last-child {\n  margin-bottom: 10px;\n}\n\n.ion-diagnostics-sheet-title {\n  padding: 15px;\n\n  border-bottom: $hairlines-width solid #d6d6da;\n  font-size: 13px;\n  font-weight: 400;\n  text-align: center;\n  color: #8f8f8f;\n}\n\n.ion-diagnostics-sheet-button {\n  margin: 0;\n  padding: 18px;\n\n  width: $action-sheet-width;\n  min-height: 56px;\n\n  border-bottom: $hairlines-width solid #d6d6da;\n  font-size: 20px;\n  color: $ionic-blue;\n  background: transparent;\n}\n\n.ion-diagnostics-sheet-button:last-child {\n  border-bottom: 0;\n}\n"
  },
  {
    "path": "src/dev-client/sass/_stack-block.scss",
    "content": "\n#ion-diagnostics .ion-diagnostic-stack-header {\n  padding: 5px 10px;\n  border-top: 1px solid $diagnostics-border;\n  border-bottom: 1px solid $diagnostics-border;\n  background-color: $diagnostics-header-background;\n}\n\n#ion-diagnostics .ion-diagnostic-stack {\n  font-family: $code-font-family;\n  font-size: 10px;\n  color: #333;\n  word-wrap: normal;\n  white-space: pre;\n  overflow: auto;\n  padding: 10px;\n\n  user-select: auto;\n  -webkit-user-select: text;\n}\n"
  },
  {
    "path": "src/dev-client/sass/_system-info.scss",
    "content": "\n#ion-diagnostics-system-info {\n  margin: 0 20px 20px 20px;\n  padding: 0px 0 20px 0;\n\n  font-family: $code-font-family;\n  font-size: 10px;\n  color: #999;\n\n  overflow: auto;\n  white-space: pre;\n}\n"
  },
  {
    "path": "src/dev-client/sass/_toast.scss",
    "content": "\n#ion-diagnostics-toast {\n  position: absolute;\n  top: 10px;\n  right: 10px;\n  left: 10px;\n  z-index: $z-index-toast;\n  margin: auto;\n  max-width: 700px;\n  border-radius: 3px;\n  font-family: $body-font-family;\n  background: rgba(0,0,0,.9);\n  -webkit-transform: translate3d(0px, -60px, 0px);\n  transform: translate3d(0px, -60px, 0px);\n  -webkit-transition: -webkit-transform 75ms ease-out;\n  transition: transform 75ms ease-out;\n  pointer-events: none;\n}\n\n#ion-diagnostics-toast.ion-diagnostics-toast-active {\n  -webkit-transform: translate3d(0px, 0px, 0px);\n  transform: translate3d(0px, 0px, 0px);\n}\n\n#ion-diagnostics-toast .ion-diagnostics-toast-content {\n  display: flex;\n  -webkit-align-items: center;\n  -ms-flex-align: center;\n  align-items: center;\n  pointer-events: auto;\n}\n\n#ion-diagnostics-toast .ion-diagnostics-toast-message {\n  -webkit-flex: 1;\n  -ms-flex: 1;\n  flex: 1;\n  padding: 15px;\n  font-family: $body-font-family;\n  font-size: 14px;\n  color: #fff;\n}\n\n#ion-diagnostics-toast .ion-diagnostics-toast-spinner {\n  position: relative;\n  display: inline-block;\n  width: 56px;\n  height: 28px;\n}\n\n#ion-diagnostics-toast svg:not(:root) {\n  overflow: hidden;\n}\n\n#ion-diagnostics-toast svg {\n  position: absolute;\n  top: 0;\n  left: 0;\n  width: 100%;\n  height: 100%;\n  -webkit-transform: translateZ(0);\n  transform: translateZ(0);\n  -webkit-animation: ion-diagnostics-spinner-rotate 600ms linear infinite;\n  animation: ion-diagnostics-spinner-rotate 600ms linear infinite;\n}\n\n@-webkit-keyframes ion-diagnostics-spinner-rotate {\n  0% {\n    -webkit-transform: rotate(0deg);\n    transform: rotate(0deg);\n  }\n  100% {\n    -webkit-transform: rotate(360deg);\n    transform: rotate(360deg);\n  }\n}\n\n@keyframes ion-diagnostics-spinner-rotate {\n  0% {\n    -webkit-transform: rotate(0deg);\n    transform: rotate(0deg);\n  }\n  100% {\n    -webkit-transform: rotate(360deg);\n    transform: rotate(360deg);\n  }\n}\n\n#ion-diagnostics-toast svg circle {\n  fill: transparent;\n  stroke: white;\n  stroke-width: 4px;\n  stroke-dasharray: 128px;\n  stroke-dashoffset: 82px;\n}\n"
  },
  {
    "path": "src/dev-client/sass/ion-dev.scss",
    "content": "\n// Font Family\n// -----------------------------\n$body-font-family: -apple-system, \"Roboto\", BlinkMacSystemFont, \"Segoe UI\", \"Helvetica Neue\", sans-serif, \"Apple Color Emoji\", \"Segoe UI Emoji\", \"Segoe UI Symbol\";\n$code-font-family: Consolas, \"Liberation Mono\", Menlo, Courier, monospace;\n\n\n// Colors\n// -----------------------------\n$ionic-blue:                  #478aff;\n$background-color:            #fff;\n$code-error-background:       rgba(255, 221, 221, 0.25);\n$code-error-highlight:        #ffdddd;\n\n$header-background:           #f8f8f8;\n$header-border-color:         #ddd;\n$header-text-color:           #333;\n\n$diagnostics-header-background: $header-background;\n$diagnostics-title-color:     #222;\n$diagnostics-message-color:   #666;\n$diagnostics-border:            $header-border-color;\n\n\n// z-index\n// -----------------------------\n$z-index-diagnostics:         100000;\n$z-index-menu:                100001;\n$z-index-toast:               100002;\n\n\n// Miscellaneous\n// -----------------------------\n$tab-size: 2;\n$hairlines-width: .55px;\n\na,\nbutton {\n  -ms-touch-action: manipulation;\n  touch-action: manipulation;\n}\n\n// Imports\n// -----------------------------\n@import './diagnostics';\n@import './code-block';\n@import './stack-block';\n@import './toast';\n@import './header';\n@import './options-menu';\n@import './system-info';\n@import '../../highlight/github-gist';\n"
  },
  {
    "path": "src/dev-server/http-server.ts",
    "content": "import * as path from 'path';\nimport { injectNotificationScript } from './injector';\nimport { injectLiveReloadScript } from './live-reload';\nimport * as express from 'express';\nimport * as fs from 'fs';\nimport * as url from 'url';\nimport {\n  ServeConfig,\n  LOGGER_DIR,\n  IONIC_LAB_URL,\n  IOS_PLATFORM_PATHS,\n  ANDROID_PLATFORM_PATHS\n} from './serve-config';\nimport { Logger } from '../logger/logger';\nimport * as proxyMiddleware from 'proxy-middleware';\nimport { injectDiagnosticsHtml } from '../logger/logger-diagnostics';\nimport * as Constants from '../util/constants';\nimport { getBooleanPropertyValue } from '../util/helpers';\nimport { getProjectJson, IonicProject } from '../util/ionic-project';\n\nimport { LabAppView, ApiCordovaProject, ApiPackageJson } from './lab';\n\n\n/**\n * Create HTTP server\n */\nexport function createHttpServer(config: ServeConfig): express.Application {\n\n  const app = express();\n  app.set('serveConfig', config);\n\n  app.get('/', serveIndex);\n  app.use('/', express.static(config.wwwDir));\n  app.use(`/${LOGGER_DIR}`, express.static(path.join(__dirname, '..', '..', 'bin'), { maxAge: 31536000 }));\n\n  // Lab routes\n  app.use(IONIC_LAB_URL + '/static', express.static(path.join(__dirname, '..', '..', 'lab', 'static')));\n  app.get(IONIC_LAB_URL, LabAppView);\n  app.get(IONIC_LAB_URL + '/api/v1/cordova', ApiCordovaProject);\n  app.get(IONIC_LAB_URL + '/api/v1/app-config', ApiPackageJson);\n\n  app.get('/cordova.js', servePlatformResource, serveMockCordovaJS);\n  app.get('/cordova_plugins.js', servePlatformResource);\n  app.get('/plugins/*', servePlatformResource);\n\n  if (config.useProxy) {\n    setupProxies(app);\n  }\n\n  return app;\n}\n\nfunction setupProxies(app: express.Application) {\n  if (getBooleanPropertyValue(Constants.ENV_READ_CONFIG_JSON)) {\n    getProjectJson().then(function (projectConfig: IonicProject) {\n      for (const proxy of projectConfig.proxies || []) {\n        let opts: any = url.parse(proxy.proxyUrl);\n        if (proxy.proxyNoAgent) {\n          opts.agent = false;\n        }\n\n        opts.rejectUnauthorized = !(proxy.rejectUnauthorized === false);\n        opts.cookieRewrite = proxy.cookieRewrite;\n\n        app.use(proxy.path, proxyMiddleware(opts));\n        Logger.info('Proxy added:' + proxy.path + ' => ' + url.format(opts));\n      }\n    }).catch((err: Error) => {\n      Logger.error(`Failed to read the projects ionic.config.json file: ${err.message}`);\n    });\n  }\n}\n\n/**\n * http responder for /index.html base entrypoint\n */\nfunction serveIndex(req: express.Request, res: express.Response) {\n  const config: ServeConfig = req.app.get('serveConfig');\n\n  // respond with the index.html file\n  const indexFileName = path.join(config.wwwDir, process.env[Constants.ENV_VAR_HTML_TO_SERVE]);\n  fs.readFile(indexFileName, (err, indexHtml) => {\n    if (!indexHtml) {\n      Logger.error(`Failed to load index.html`);\n      res.send('try again later');\n      return;\n    }\n    if (config.useLiveReload) {\n      indexHtml = injectLiveReloadScript(indexHtml, req.hostname, config.liveReloadPort);\n      indexHtml = injectNotificationScript(config.rootDir, indexHtml, config.notifyOnConsoleLog, config.notificationPort);\n    }\n\n    indexHtml = injectDiagnosticsHtml(config.buildDir, indexHtml);\n\n    res.set('Content-Type', 'text/html');\n    res.send(indexHtml);\n  });\n}\n\n/**\n * http responder for cordova.js file\n */\nfunction serveMockCordovaJS(req: express.Request, res: express.Response) {\n  res.set('Content-Type', 'application/javascript');\n  res.send('// mock cordova file during development');\n}\n\n/**\n * Middleware to serve platform resources\n */\nasync function servePlatformResource(req: express.Request, res: express.Response, next: express.NextFunction) {\n  const config: ServeConfig = req.app.get('serveConfig');\n  const userAgent = req.header('user-agent');\n\n  if (!config.isCordovaServe) {\n    return next();\n  }\n\n  let root = await getResourcePath(req.url, config, userAgent);\n  if (root) {\n    res.sendFile(req.url, { root });\n  } else {\n    next();\n  }\n}\n\n/**\n * Determines the appropriate resource path, and checks if the specified url \n * \n * @returns string of the resource path or undefined if there is no match\n */\nasync function getResourcePath(url: string, config: ServeConfig, userAgent: string): Promise<string> {\n  let searchPaths: string[] = [config.wwwDir];\n  if (isUserAgentIOS(userAgent)) {\n    searchPaths = IOS_PLATFORM_PATHS.map(resourcePath => path.join(config.rootDir, resourcePath));\n  } else if (isUserAgentAndroid(userAgent)) {\n    searchPaths = ANDROID_PLATFORM_PATHS.map(resourcePath => path.join(config.rootDir, resourcePath));\n  }\n\n  for (let i = 0; i < searchPaths.length; i++) {\n    let checkPath = path.join(searchPaths[i], url);\n    try {\n      let result = await checkFile(checkPath);\n      return searchPaths[i];\n    } catch (e) { }\n  }\n}\n\n/**\n * Checks if a file exists (responds to stat)\n */\nfunction checkFile(filePath: string): Promise<void> {\n  return new Promise((resolve, reject) => {\n    fs.stat(filePath, (err: Error, stats: any) => {\n      if (err) {\n        return reject();\n      }\n      resolve();\n    });\n  });\n}\n\nfunction isUserAgentIOS(ua: string): boolean {\n  ua = ua.toLowerCase();\n  return (ua.indexOf('iphone') > -1 || ua.indexOf('ipad') > -1 || ua.indexOf('ipod') > -1);\n}\n\nfunction isUserAgentAndroid(ua: string): boolean {\n  ua = ua.toLowerCase();\n  return ua.indexOf('android') > -1;\n}\n"
  },
  {
    "path": "src/dev-server/injector.ts",
    "content": "import { getAppScriptsVersion, getSystemText } from '../util/helpers';\nimport { LOGGER_DIR } from './serve-config';\n\n\nconst LOGGER_HEADER = '<!-- Ionic Dev Server: Injected Logger Script -->';\n\nexport function injectNotificationScript(rootDir: string, content: any, notifyOnConsoleLog: boolean, notificationPort: Number): any {\n  let contentStr = content.toString();\n  const consoleLogScript = getDevLoggerScript(rootDir, notifyOnConsoleLog, notificationPort);\n\n  if (contentStr.indexOf(LOGGER_HEADER) > -1) {\n    // already added script somehow\n    return content;\n  }\n\n  let match = contentStr.match(/<head>(?![\\s\\S]*<head>)/i);\n  if (!match) {\n    match = contentStr.match(/<body>(?![\\s\\S]*<body>)/i);\n  }\n  if (match) {\n    contentStr = contentStr.replace(match[0], `${match[0]}\\n${consoleLogScript}`);\n  } else {\n    contentStr = consoleLogScript + contentStr;\n  }\n\n  return contentStr;\n}\n\nfunction getDevLoggerScript(rootDir: string, notifyOnConsoleLog: boolean, notificationPort: Number) {\n  const appScriptsVersion = getAppScriptsVersion();\n  const ionDevServer = JSON.stringify({\n    sendConsoleLogs: notifyOnConsoleLog,\n    wsPort: notificationPort,\n    appScriptsVersion: appScriptsVersion,\n    systemInfo: getSystemText(rootDir)\n  });\n\n  return `\n  ${LOGGER_HEADER}\n  <script>var IonicDevServerConfig=${ionDevServer};</script>\n  <link href=\"${LOGGER_DIR}/ion-dev.css?v=${appScriptsVersion}\" rel=\"stylesheet\">\n  <script src=\"${LOGGER_DIR}/ion-dev.js?v=${appScriptsVersion}\"></script>\n  `;\n}\n"
  },
  {
    "path": "src/dev-server/lab.ts",
    "content": "import * as path from 'path';\n\nimport { buildCordovaConfig, CordovaProject } from '../util/cordova-config';\n\n/**\n * Main Lab app view\n */\nexport let LabAppView = (req: any, res: any) => {\n  return res.sendFile('index.html', {root: path.join(__dirname, '..', '..', 'lab')});\n};\n\nexport let ApiCordovaProject = (req: any, res: any) => {\n  buildCordovaConfig((err: any) => {\n    res.status(400).json({ status: 'error', message: 'Unable to load config.xml' });\n  }, (config: CordovaProject) => {\n    res.json(config);\n  });\n};\n\nexport let ApiPackageJson = (req: any, res: any) => {\n  res.sendFile(path.join(process.cwd(), 'package.json'), {\n    headers: {\n      'content-type': 'application/json'\n    }\n  })\n};\n"
  },
  {
    "path": "src/dev-server/live-reload.ts",
    "content": "import { ChangedFile } from '../util/interfaces';\nimport { hasDiagnostics } from '../logger/logger-diagnostics';\nimport * as path from 'path';\nimport * as tinylr from 'tiny-lr';\nimport { ServeConfig } from './serve-config';\nimport * as events from '../util/events';\n\n\nexport function createLiveReloadServer(config: ServeConfig) {\n  const liveReloadServer = tinylr();\n  liveReloadServer.listen(config.liveReloadPort, config.host);\n\n  function fileChange(changedFiles: ChangedFile[]) {\n    // only do a live reload if there are no diagnostics\n    // the notification server takes care of showing diagnostics\n    if (!hasDiagnostics(config.buildDir)) {\n      liveReloadServer.changed({\n        body: {\n          files: changedFiles.map(changedFile => '/' + path.relative(config.wwwDir, changedFile.filePath))\n        }\n      });\n    }\n  }\n\n  events.on(events.EventType.FileChange, fileChange);\n\n  events.on(events.EventType.ReloadApp, () => {\n    fileChange([{ event: 'change', ext: '.html', filePath: 'index.html'}]);\n  });\n}\n\n\nexport function injectLiveReloadScript(content: any, host: string, port: Number): any {\n  let contentStr = content.toString();\n  const liveReloadScript = getLiveReloadScript(host, port);\n\n  if (contentStr.indexOf('/livereload.js') > -1) {\n    // already added script\n    return content;\n  }\n\n  let match = contentStr.match(/<\\/body>(?![\\s\\S]*<\\/body>)/i);\n  if (!match) {\n    match = contentStr.match(/<\\/html>(?![\\s\\S]*<\\/html>)/i);\n  }\n  if (match) {\n    contentStr = contentStr.replace(match[0], `${liveReloadScript}\\n${match[0]}`);\n  } else {\n    contentStr += liveReloadScript;\n  }\n\n  return contentStr;\n}\n\nfunction getLiveReloadScript(host: string, port: Number) {\n  var src = `//${host}:${port}/livereload.js?snipver=1`;\n  return `  <!-- Ionic Dev Server: Injected LiveReload Script -->\\n  <script src=\"${src}\" async=\"\" defer=\"\"></script>`;\n}\n"
  },
  {
    "path": "src/dev-server/notification-server.ts",
    "content": "// Ionic Dev Server: Server Side Logger\nimport { BuildUpdateMessage, WsMessage } from '../util/interfaces';\nimport { Logger } from '../logger/logger';\nimport { generateRuntimeDiagnosticContent } from '../logger/logger-runtime';\nimport { hasDiagnostics, getDiagnosticsHtmlContent } from '../logger/logger-diagnostics';\nimport { on, EventType } from '../util/events';\nimport { Server as WebSocketServer } from 'ws';\nimport { ServeConfig } from './serve-config';\n\n\nexport function createNotificationServer(config: ServeConfig) {\n  let wsServer: any;\n  const msgToClient: WsMessage[] = [];\n\n  // queue up all messages to the client\n  function queueMessageSend(msg: WsMessage) {\n    msgToClient.push(msg);\n    drainMessageQueue({\n      broadcast: true\n    });\n  }\n\n  // drain the queue messages when the server is ready\n  function drainMessageQueue(options = { broadcast: false }) {\n    let sendMethod = wsServer && wsServer.send;\n    if (options.hasOwnProperty('broadcast') && options.broadcast) {\n      sendMethod = wss.broadcast;\n    }\n    if (sendMethod && wss.clients.size > 0) {\n      let msg: any;\n      while (msg = msgToClient.shift()) {\n        try {\n          sendMethod(JSON.stringify(msg));\n        } catch (e) {\n          if (e.message !== 'not opened' && e.message !== `Cannot read property 'readyState' of undefined`) {\n            Logger.error(`error sending client ws - ${e.message}`);\n          }\n        }\n      }\n    }\n  }\n\n  // a build update has started, notify the client\n  on(EventType.BuildUpdateStarted, (buildUpdateMsg: BuildUpdateMessage) => {\n    const msg: WsMessage = {\n      category: 'buildUpdate',\n      type: 'started',\n      data: {\n        buildId: buildUpdateMsg.buildId,\n        reloadApp: buildUpdateMsg.reloadApp,\n        diagnosticsHtml: null\n      }\n    };\n    queueMessageSend(msg);\n  });\n\n  // a build update has completed, notify the client\n  on(EventType.BuildUpdateCompleted, (buildUpdateMsg: BuildUpdateMessage) => {\n    const msg: WsMessage = {\n      category: 'buildUpdate',\n      type: 'completed',\n      data: {\n        buildId: buildUpdateMsg.buildId,\n        reloadApp: buildUpdateMsg.reloadApp,\n        diagnosticsHtml: hasDiagnostics(config.buildDir) ? getDiagnosticsHtmlContent(config.buildDir) : null\n      }\n    };\n    queueMessageSend(msg);\n  });\n\n  // create web socket server\n  const wss = new WebSocketServer({ host: config.host, port: config.notificationPort });\n  wss.broadcast = function broadcast(data: any) {\n    wss.clients.forEach(function each(client: any) {\n      client.send(data);\n    });\n  };\n  wss.on('connection', (ws: any) => {\n    // we've successfully connected\n    wsServer = ws;\n\n    wsServer.on('message', (incomingMessage: any) => {\n      // incoming message from the client\n      try {\n        printMessageFromClient(JSON.parse(incomingMessage));\n      } catch (e) {\n        Logger.error(`error opening ws message: ${incomingMessage}`);\n        Logger.error(e.stack ? e.stack : e);\n      }\n    });\n\n    // now that we're connected, send off any messages\n    // we might has already queued up\n    drainMessageQueue();\n  });\n\n\n  function printMessageFromClient(msg: WsMessage) {\n    if (msg && msg.data) {\n      switch (msg.category) {\n        case 'console':\n          printConsole(msg);\n          break;\n\n        case 'runtimeError':\n          handleRuntimeError(msg);\n          break;\n      }\n    }\n  }\n\n\n  function printConsole(msg: WsMessage) {\n    const args = msg.data;\n    args[0] = `console.${msg.type}: ${args[0]}`;\n    const log = args.join(' ');\n\n    switch (msg.type) {\n      case 'error':\n        Logger.error(log);\n        break;\n\n      case 'warn':\n        Logger.warn(log);\n        break;\n\n      case 'debug':\n        Logger.debug(log);\n        break;\n\n      default:\n        Logger.info(log);\n        break;\n      }\n  }\n\n\n  function handleRuntimeError(clientMsg: WsMessage) {\n    const msg: WsMessage = {\n      category: 'buildUpdate',\n      type: 'completed',\n      data: {\n        diagnosticsHtml: generateRuntimeDiagnosticContent(config.rootDir,\n                                                          config.buildDir,\n                                                          clientMsg.data.message,\n                                                          clientMsg.data.stack)\n      }\n    };\n    queueMessageSend(msg);\n  }\n\n}\n"
  },
  {
    "path": "src/dev-server/serve-config.ts",
    "content": "import * as path from 'path';\n\nexport interface ServeConfig {\n  httpPort: number;\n  host: string;\n  hostBaseUrl: string;\n  rootDir: string;\n  wwwDir: string;\n  buildDir: string;\n  isCordovaServe: boolean;\n  launchBrowser: boolean;\n  launchLab: boolean;\n  browserToLaunch: string;\n  useLiveReload: boolean;\n  liveReloadPort: Number;\n  notificationPort: Number;\n  useServerLogs: boolean;\n  notifyOnConsoleLog: boolean;\n  useProxy: boolean;\n  devapp: boolean;\n}\nexport const LOGGER_DIR = '__ion-dev-server';\nexport const IONIC_LAB_URL = '/ionic-lab';\n\nexport const IOS_PLATFORM_PATHS = [path.join('platforms', 'ios', 'www')];\nexport const ANDROID_PLATFORM_PATHS = [\n  path.join('platforms', 'android', 'assets', 'www'),\n  path.join('platforms', 'android', 'app', 'src', 'main', 'assets', 'www')\n];\n"
  },
  {
    "path": "src/generators/constants.ts",
    "content": "export const CLASSNAME_VARIABLE = '$CLASSNAME';\nexport const TAB_CONTENT_VARIABLE = '$TAB_CONTENT';\nexport const TAB_VARIABLES_VARIABLE = '$TAB_VARIABLES';\nexport const TABS_IMPORTSTATEMENT_VARIABLE = '$TABS_IMPORTSTATEMENT';\nexport const FILENAME_VARIABLE = '$FILENAME';\nexport const PIPENAME_VARIABLE = '$PIPENAME';\nexport const SUPPLIEDNAME_VARIABLE = '$SUPPLIEDNAME';\nexport const IMPORTSTATEMENT_VARIABLE = '$IMPORTSTATEMENT';\nexport const IONICPAGE_VARIABLE = '$IONICPAGE';\nexport const KNOWN_FILE_EXTENSION = '.tmpl';\n\nexport const SPEC_FILE_EXTENSION = 'spec.ts';\nexport const NG_MODULE_FILE_EXTENSION = 'module.ts';\n\n"
  },
  {
    "path": "src/generators/util.spec.ts",
    "content": "import { BuildContext } from '../util/interfaces';\nimport { basename, join } from 'path';\nimport * as fs from 'fs';\nimport * as Constants from '../util/constants';\nimport * as helpers from '../util/helpers';\nimport * as globUtils from '../util/glob-util';\nimport * as util from './util';\nimport * as GeneratorConstants from './constants';\nimport * as TypeScriptUtils from '../util/typescript-utils';\n\ndescribe('util', () => {\n  describe('hydrateRequest', () => {\n    it('should take a component request and return a hydrated component request', () => {\n      // arrange\n      const baseDir = join(process.cwd(), 'someDir', 'project');\n      const componentsDir = join(baseDir, 'src', 'components');\n      const context = {\n        componentsDir: componentsDir\n      };\n      const request = {\n        type: Constants.COMPONENT,\n        name: 'settings view',\n        includeSpec: true,\n        includeNgModule: true\n      };\n\n      const templateDir = join(\n        baseDir,\n        'node_modules',\n        'ionic-angular',\n        'templates'\n      );\n      spyOn(helpers, helpers.getStringPropertyValue.name).and.returnValue(\n        templateDir\n      );\n\n      // act\n      const hydratedRequest = util.hydrateRequest(context, request);\n\n      // assert\n      expect(hydratedRequest).toEqual({\n        className: 'SettingsViewComponent',\n        dirToRead: join(templateDir, 'component'),\n        dirToWrite: join(componentsDir, 'settings-view'),\n        fileName: 'settings-view',\n        importStatement: 'import { IonicPage, NavController, NavParams } from \\'ionic-angular\\';',\n        includeNgModule: true,\n        includeSpec: true,\n        ionicPage: '\\n@IonicPage()',\n        name: 'settings view',\n        type: 'component'\n      });\n      expect(hydratedRequest.type).toEqual(Constants.COMPONENT);\n      expect(hydratedRequest.name).toEqual(request.name);\n      expect(hydratedRequest.includeNgModule).toBeTruthy();\n      expect(hydratedRequest.includeSpec).toBeTruthy();\n      expect(hydratedRequest.className).toEqual('SettingsViewComponent');\n      expect(hydratedRequest.fileName).toEqual('settings-view');\n      expect(hydratedRequest.dirToRead).toEqual(\n        join(templateDir, Constants.COMPONENT)\n      );\n      expect(hydratedRequest.dirToWrite).toEqual(\n        join(componentsDir, hydratedRequest.fileName)\n      );\n    });\n\n    it('should take a page request and return a hydrated page request', () => {\n      // arrange\n      const baseDir = join(process.cwd(), 'someDir', 'project');\n      const pagesDir = join(baseDir, 'src', 'pages');\n      const context = {\n        pagesDir: pagesDir\n      };\n      const request = {\n        type: Constants.PAGE,\n        name: 'settings view',\n        includeSpec: true,\n        includeNgModule: true\n      };\n\n      const templateDir = join(\n        baseDir,\n        'node_modules',\n        'ionic-angular',\n        'templates'\n      );\n      spyOn(helpers, helpers.getStringPropertyValue.name).and.returnValue(\n        templateDir\n      );\n\n      // act\n      const hydratedRequest = util.hydrateRequest(context, request);\n\n      // assert\n      expect(hydratedRequest).toEqual({\n        className: 'SettingsViewPage',\n        dirToRead: join(templateDir, 'page'),\n        dirToWrite: join(pagesDir, 'settings-view'),\n        fileName: 'settings-view',\n        importStatement: 'import { IonicPage, NavController, NavParams } from \\'ionic-angular\\';',\n        includeNgModule: true,\n        includeSpec: true,\n        ionicPage: '\\n@IonicPage()',\n        name: 'settings view',\n        type: 'page'\n      });\n      expect(hydratedRequest.type).toEqual(Constants.PAGE);\n      expect(hydratedRequest.name).toEqual(request.name);\n      expect(hydratedRequest.includeNgModule).toBeTruthy();\n      expect(hydratedRequest.includeSpec).toBeTruthy();\n      expect(hydratedRequest.className).toEqual('SettingsViewPage');\n      expect(hydratedRequest.fileName).toEqual('settings-view');\n      expect(hydratedRequest.dirToRead).toEqual(\n        join(templateDir, Constants.PAGE)\n      );\n      expect(hydratedRequest.dirToWrite).toEqual(\n        join(pagesDir, hydratedRequest.fileName)\n      );\n    });\n\n    it('should take a page with no module request and return a hydrated page request', () => {\n      // arrange\n      const baseDir = join(process.cwd(), 'someDir', 'project');\n      const pagesDir = join(baseDir, 'src', 'pages');\n      const context = {\n        pagesDir: pagesDir\n      };\n      const includeNgModule = false;\n      const request = {\n        type: Constants.PAGE,\n        name: 'about',\n        includeSpec: true,\n        includeNgModule: false\n      };\n\n      const templateDir = join(\n        baseDir,\n        'node_modules',\n        'ionic-angular',\n        'templates'\n      );\n      spyOn(helpers, helpers.getStringPropertyValue.name).and.returnValue(\n        templateDir\n      );\n\n      // act\n      const hydratedRequest = util.hydrateRequest(context, request);\n\n      // assert\n      expect(hydratedRequest).toEqual({\n        className: 'AboutPage',\n        dirToRead: join(templateDir, 'page'),\n        dirToWrite: join(pagesDir, 'about'),\n        fileName: 'about',\n        importStatement: 'import { NavController, NavParams } from \\'ionic-angular\\';',\n        includeNgModule: false,\n        includeSpec: true,\n        ionicPage: null,\n        name: 'about',\n        type: 'page'\n      });\n      expect(hydratedRequest.ionicPage).toEqual(null);\n      expect(hydratedRequest.importStatement).toEqual(\n        'import { NavController, NavParams } from \\'ionic-angular\\';'\n      );\n      expect(hydratedRequest.type).toEqual(Constants.PAGE);\n      expect(hydratedRequest.name).toEqual(request.name);\n      expect(hydratedRequest.includeNgModule).toBeFalsy();\n      expect(hydratedRequest.includeSpec).toBeTruthy();\n      expect(hydratedRequest.className).toEqual('AboutPage');\n      expect(hydratedRequest.fileName).toEqual('about');\n      expect(hydratedRequest.dirToRead).toEqual(\n        join(templateDir, Constants.PAGE)\n      );\n      expect(hydratedRequest.dirToWrite).toEqual(\n        join(pagesDir, hydratedRequest.fileName)\n      );\n    });\n  });\n\n  describe('hydrateTabRequest', () => {\n    it('should take a lazy loaded page set the tab root to a string', () => {\n      // arrange\n      const baseDir = join(process.cwd(), 'someDir', 'project');\n      const pagesDir = join(baseDir, 'src', 'pages');\n      const templateDir = join(\n        baseDir,\n        'node_modules',\n        'ionic-angular',\n        'templates'\n      );\n      const context: BuildContext = { pagesDir };\n      const request = {\n        type: 'tabs',\n        name: 'stooges',\n        includeNgModule: true,\n        tabs: [\n          {\n            includeNgModule: true,\n            type: 'page',\n            name: 'moe',\n            className: 'MoePage',\n            fileName: 'moe',\n            dirToRead: join(templateDir, 'page'),\n            dirToWrite: join(pagesDir, 'moe')\n          }\n        ]\n      };\n      spyOn(helpers, helpers.getStringPropertyValue.name).and.returnValue(\n        templateDir\n      );\n      // act\n      const hydatedTabRequest = util.hydrateTabRequest(context, request);\n      // assert\n      expect(hydatedTabRequest.tabVariables).toEqual(`  moeRoot = 'MoePage'\\n`);\n    });\n\n    it('should take a page set the tab root to a component ref', () => {\n      // arrange\n      const baseDir = join(process.cwd(), 'someDir', 'project');\n      const pagesDir = join(baseDir, 'src', 'pages');\n      const templateDir = join(\n        baseDir,\n        'node_modules',\n        'ionic-angular',\n        'templates'\n      );\n      const context: BuildContext = { pagesDir };\n      const request = {\n        type: 'tabs',\n        name: 'stooges',\n        includeNgModule: false,\n        tabs: [\n          {\n            includeNgModule: false,\n            type: 'page',\n            name: 'moe',\n            className: 'MoePage',\n            fileName: 'moe',\n            dirToRead: join(templateDir, 'page'),\n            dirToWrite: join(pagesDir, 'moe')\n          }\n        ]\n      };\n      spyOn(helpers, helpers.getStringPropertyValue.name).and.returnValue(\n        templateDir\n      );\n      // act\n      const hydatedTabRequest = util.hydrateTabRequest(context, request);\n      // assert\n      expect(hydatedTabRequest.tabVariables).toEqual('  moeRoot = MoePage\\n');\n    });\n  });\n\n  describe('readTemplates', () => {\n    it('should get a map of templates and their content back', () => {\n      // arrange\n      const templateDir =\n        '/Users/noone/project/node_modules/ionic-angular/templates/component';\n      const knownValues = [\n        'html.tmpl',\n        'scss.tmpl',\n        'spec.ts.tmpl',\n        'ts.tmpl',\n        'module.tmpl'\n      ];\n      const fileContent = 'SomeContent';\n      spyOn(fs, 'readdirSync').and.returnValue(knownValues);\n      spyOn(helpers, helpers.readFileAsync.name).and.returnValue(\n        Promise.resolve(fileContent)\n      );\n\n      // act\n      const promise = util.readTemplates(templateDir);\n\n      // assert\n      return promise.then((map: Map<string, string>) => {\n        expect(map.get(join(templateDir, knownValues[0]))).toEqual(fileContent);\n        expect(map.get(join(templateDir, knownValues[1]))).toEqual(fileContent);\n        expect(map.get(join(templateDir, knownValues[2]))).toEqual(fileContent);\n        expect(map.get(join(templateDir, knownValues[3]))).toEqual(fileContent);\n        expect(map.get(join(templateDir, knownValues[4]))).toEqual(fileContent);\n      });\n    });\n  });\n\n  describe('filterOutTemplates', () => {\n    it('should preserve all templates', () => {\n      const map = new Map<string, string>();\n      const templateDir =\n        '/Users/noone/project/node_modules/ionic-angular/templates/component';\n      const fileContent = 'SomeContent';\n      const knownValues = [\n        'html.tmpl',\n        'scss.tmpl',\n        'spec.ts.tmpl',\n        'ts.tmpl',\n        'module.tmpl'\n      ];\n      map.set(join(templateDir, knownValues[0]), fileContent);\n      map.set(join(templateDir, knownValues[1]), fileContent);\n      map.set(join(templateDir, knownValues[2]), fileContent);\n      map.set(join(templateDir, knownValues[3]), fileContent);\n      map.set(join(templateDir, knownValues[4]), fileContent);\n\n      const newMap = util.filterOutTemplates(\n        { includeNgModule: true, includeSpec: true },\n        map\n      );\n      expect(newMap.size).toEqual(knownValues.length);\n    });\n\n    it('should remove spec', () => {\n      const map = new Map<string, string>();\n      const templateDir =\n        '/Users/noone/project/node_modules/ionic-angular/templates/component';\n      const fileContent = 'SomeContent';\n      const knownValues = [\n        'html.tmpl',\n        'scss.tmpl',\n        'spec.ts.tmpl',\n        'ts.tmpl',\n        'module.tmpl'\n      ];\n      map.set(join(templateDir, knownValues[0]), fileContent);\n      map.set(join(templateDir, knownValues[1]), fileContent);\n      map.set(join(templateDir, knownValues[2]), fileContent);\n      map.set(join(templateDir, knownValues[3]), fileContent);\n      map.set(join(templateDir, knownValues[4]), fileContent);\n\n      const newMap = util.filterOutTemplates(\n        { includeNgModule: true, includeSpec: false },\n        map\n      );\n      expect(newMap.size).toEqual(4);\n      expect(newMap.get(join(templateDir, knownValues[0]))).toBeTruthy();\n      expect(newMap.get(join(templateDir, knownValues[1]))).toBeTruthy();\n      expect(newMap.get(join(templateDir, knownValues[2]))).toBeFalsy();\n      expect(newMap.get(join(templateDir, knownValues[3]))).toBeTruthy();\n      expect(newMap.get(join(templateDir, knownValues[4]))).toBeTruthy();\n    });\n\n    it('should remove spec and module', () => {\n      const map = new Map<string, string>();\n      const templateDir =\n        '/Users/noone/project/node_modules/ionic-angular/templates/component';\n      const fileContent = 'SomeContent';\n      const knownValues = [\n        'html.tmpl',\n        'scss.tmpl',\n        'spec.ts.tmpl',\n        'ts.tmpl',\n        'module.ts.tmpl'\n      ];\n      map.set(join(templateDir, knownValues[0]), fileContent);\n      map.set(join(templateDir, knownValues[1]), fileContent);\n      map.set(join(templateDir, knownValues[2]), fileContent);\n      map.set(join(templateDir, knownValues[3]), fileContent);\n      map.set(join(templateDir, knownValues[4]), fileContent);\n\n      const newMap = util.filterOutTemplates(\n        { includeNgModule: false, includeSpec: false },\n        map\n      );\n      expect(newMap.size).toEqual(3);\n      expect(newMap.get(join(templateDir, knownValues[0]))).toBeTruthy();\n      expect(newMap.get(join(templateDir, knownValues[1]))).toBeTruthy();\n      expect(newMap.get(join(templateDir, knownValues[2]))).toBeFalsy();\n      expect(newMap.get(join(templateDir, knownValues[3]))).toBeTruthy();\n      expect(newMap.get(join(templateDir, knownValues[4]))).toBeFalsy();\n    });\n  });\n\n  describe('applyTemplates', () => {\n    it('should replace the template content', () => {\n      const fileOne = '/Users/noone/fileOne';\n\n      const fileOneContent = `\n<!--\n  Generated template for the $CLASSNAME component.\n\n  See https://angular.io/docs/ts/latest/api/core/index/ComponentMetadata-class.html\n  for more info on Angular 2 Components.\n-->\n\n{{text}}\n\n      `;\n\n      const fileTwo = '/Users/noone/fileTwo';\n      const fileTwoContent = `\n$FILENAME {\n\n}\n      `;\n\n      const fileThree = '/Users/noone/fileThree';\n      const fileThreeContent = `\ndescribe('$CLASSNAME', () => {\n  it('should do something', () => {\n    expect(true).toEqual(true);\n  });\n});\n      `;\n\n      const fileFour = '/Users/noone/fileFour';\n      const fileFourContent = `\nimport { Component } from '@angular/core';\n\n/*\n  Generated class for the $CLASSNAME component.\n\n  See https://angular.io/docs/ts/latest/api/core/index/ComponentMetadata-class.html\n  for more info on Angular 2 Components.\n*/\n@Component({\n  selector: '$FILENAME',\n  templateUrl: '$FILENAME.html'\n})\nexport class $CLASSNAMEComponent {\n\n  text: string;\n\n  constructor() {\n    console.log('Hello $CLASSNAME Component');\n    this.text = 'Hello World';\n  }\n\n}\n\n      `;\n\n      const fileFive = '/Users/noone/fileFive';\n      const fileFiveContent = `\nimport { NgModule } from '@angular/core';\nimport { $CLASSNAME } from './$FILENAME';\nimport { IonicModule } from 'ionic-angular';\n\n@NgModule({\n  declarations: [\n    $CLASSNAME,\n  ],\n  imports: [\n    IonicModule.forChild($CLASSNAME)\n  ],\n  entryComponents: [\n    $CLASSNAME\n  ],\n  providers: []\n})\nexport class $CLASSNAMEModule {}\n      `;\n\n      const fileSix = '/Users/noone/fileSix';\n      const fileSixContent = `\n<!--\n  Generated template for the $CLASSNAME page.\n\n  See http://ionicframework.com/docs/v2/components/#navigation for more info on\n  Ionic pages and navigation.\n-->\n<ion-header>\n\n  <ion-navbar>\n    <ion-title>$SUPPLIEDNAME</ion-title>\n  </ion-navbar>\n\n</ion-header>\n\n\n<ion-content padding>\n\n</ion-content>\n      `;\n\n      const fileSeven = '/Users/noone/fileSeven';\n      const fileSevenContent = `\n<ion-tabs>\n$TAB_CONTENT\n</ion-tabs>\n      `;\n\n      const map = new Map<string, string>();\n      map.set(fileOne, fileOneContent);\n      map.set(fileTwo, fileTwoContent);\n      map.set(fileThree, fileThreeContent);\n      map.set(fileFour, fileFourContent);\n      map.set(fileFive, fileFiveContent);\n      map.set(fileSix, fileSixContent);\n      map.set(fileSeven, fileSevenContent);\n\n      const className = 'SettingsView';\n      const fileName = 'settings-view';\n      const suppliedName = 'settings view';\n\n      const results = util.applyTemplates(\n        { name: suppliedName, className: className, fileName: fileName },\n        map\n      );\n      const modifiedContentOne = results.get(fileOne);\n      const modifiedContentTwo = results.get(fileTwo);\n      const modifiedContentThree = results.get(fileThree);\n      const modifiedContentFour = results.get(fileFour);\n      const modifiedContentFive = results.get(fileFive);\n      const modifiedContentSix = results.get(fileSix);\n      const modifiedContentSeven = results.get(fileSeven);\n      const nonExistentVars = [\n        GeneratorConstants.CLASSNAME_VARIABLE,\n        GeneratorConstants.FILENAME_VARIABLE,\n        GeneratorConstants.SUPPLIEDNAME_VARIABLE,\n        GeneratorConstants.TAB_CONTENT_VARIABLE,\n        GeneratorConstants.TAB_VARIABLES_VARIABLE\n      ];\n\n      for (let v of nonExistentVars) {\n        expect(modifiedContentOne.indexOf(v)).toEqual(-1);\n        expect(modifiedContentTwo.indexOf(v)).toEqual(-1);\n        expect(modifiedContentThree.indexOf(v)).toEqual(-1);\n        expect(modifiedContentFour.indexOf(v)).toEqual(-1);\n        expect(modifiedContentFive.indexOf(v)).toEqual(-1);\n        expect(modifiedContentSix.indexOf(v)).toEqual(-1);\n        expect(modifiedContentSeven.indexOf(v)).toEqual(-1);\n      }\n    });\n  });\n\n  describe('writeGeneratedFiles', () => {\n    it('should return the list of files generated', () => {\n      const map = new Map<string, string>();\n      const templateDir =\n        '/Users/noone/project/node_modules/ionic-angular/templates/component';\n      const fileContent = 'SomeContent';\n      const knownValues = [\n        'html.tmpl',\n        'scss.tmpl',\n        'spec.ts.tmpl',\n        'ts.tmpl',\n        'module.tmpl'\n      ];\n      const fileName = 'settings-view';\n      const dirToWrite = join('/Users/noone/project/src/components', fileName);\n      map.set(join(templateDir, knownValues[0]), fileContent);\n      map.set(join(templateDir, knownValues[1]), fileContent);\n      map.set(join(templateDir, knownValues[2]), fileContent);\n      map.set(join(templateDir, knownValues[3]), fileContent);\n      map.set(join(templateDir, knownValues[4]), fileContent);\n\n      spyOn(helpers, helpers.mkDirpAsync.name).and.returnValue(\n        Promise.resolve()\n      );\n      spyOn(helpers, helpers.writeFileAsync.name).and.returnValue(\n        Promise.resolve()\n      );\n\n      const promise = util.writeGeneratedFiles(\n        { dirToWrite: dirToWrite, fileName: fileName },\n        map\n      );\n\n      return promise.then((filesCreated: string[]) => {\n        const fileExtensions = knownValues.map(knownValue =>\n          basename(knownValue, GeneratorConstants.KNOWN_FILE_EXTENSION)\n        );\n        expect(filesCreated[0]).toEqual(\n          join(dirToWrite, `${fileName}.${fileExtensions[0]}`)\n        );\n        expect(filesCreated[1]).toEqual(\n          join(dirToWrite, `${fileName}.${fileExtensions[1]}`)\n        );\n        expect(filesCreated[2]).toEqual(\n          join(dirToWrite, `${fileName}.${fileExtensions[2]}`)\n        );\n        expect(filesCreated[3]).toEqual(\n          join(dirToWrite, `${fileName}.${fileExtensions[3]}`)\n        );\n        expect(filesCreated[4]).toEqual(\n          join(dirToWrite, `${fileName}.${fileExtensions[4]}`)\n        );\n      });\n    });\n  });\n\n  describe('getDirToWriteToByType', () => {\n    let context: any;\n    const componentsDir = '/path/to/components';\n    const directivesDir = '/path/to/directives';\n    const pagesDir = '/path/to/pages';\n    const pipesDir = '/path/to/pipes';\n    const providersDir = '/path/to/providers';\n\n    beforeEach(() => {\n      context = {\n        componentsDir,\n        directivesDir,\n        pagesDir,\n        pipesDir,\n        providersDir\n      };\n    });\n\n    it('should return the appropriate components directory', () => {\n      expect(util.getDirToWriteToByType(context, 'component')).toEqual(\n        componentsDir\n      );\n    });\n\n    it('should return the appropriate directives directory', () => {\n      expect(util.getDirToWriteToByType(context, 'directive')).toEqual(\n        directivesDir\n      );\n    });\n\n    it('should return the appropriate pages directory', () => {\n      expect(util.getDirToWriteToByType(context, 'page')).toEqual(pagesDir);\n    });\n\n    it('should return the appropriate pipes directory', () => {\n      expect(util.getDirToWriteToByType(context, 'pipe')).toEqual(pipesDir);\n    });\n\n    it('should return the appropriate providers directory', () => {\n      expect(util.getDirToWriteToByType(context, 'provider')).toEqual(\n        providersDir\n      );\n    });\n\n    it('should throw error upon unknown generator type', () => {\n      expect(() => util.getDirToWriteToByType(context, 'dan')).toThrowError(\n        'Unknown Generator Type: dan'\n      );\n    });\n  });\n\n  describe('getNgModules', () => {\n    let context: any;\n    const componentsDir = join(process.cwd(), 'path', 'to', 'components');\n    const directivesDir = join(process.cwd(), 'path', 'to', 'directives');\n    const pagesDir = join(process.cwd(), 'path', 'to', 'pages');\n    const pipesDir = join(process.cwd(), 'path', 'to', 'pipes');\n    const providersDir = join(process.cwd(), 'path', 'to', 'providers');\n\n    beforeEach(() => {\n      context = {\n        componentsDir,\n        directivesDir,\n        pagesDir,\n        pipesDir,\n        providersDir\n      };\n    });\n\n    it('should return an empty list of glob results', () => {\n      const globAllSpy = spyOn(globUtils, globUtils.globAll.name);\n      util.getNgModules(context, []);\n      expect(globAllSpy).toHaveBeenCalledWith([]);\n    });\n\n    it('should return a list of glob results for components', () => {\n      const globAllSpy = spyOn(globUtils, globUtils.globAll.name);\n      spyOn(helpers, helpers.getStringPropertyValue.name).and.returnValue(\n        '.module.ts'\n      );\n      util.getNgModules(context, ['component']);\n      expect(globAllSpy).toHaveBeenCalledWith([\n        join(componentsDir, '**', '*.module.ts')\n      ]);\n    });\n\n    it('should return a list of glob results for pages and components', () => {\n      const globAllSpy = spyOn(globUtils, globUtils.globAll.name);\n      spyOn(helpers, helpers.getStringPropertyValue.name).and.returnValue(\n        '.module.ts'\n      );\n      util.getNgModules(context, ['page', 'component']);\n      expect(globAllSpy).toHaveBeenCalledWith([\n        join(pagesDir, '**', '*.module.ts'),\n        join(componentsDir, '**', '*.module.ts')\n      ]);\n    });\n  });\n\n});\n"
  },
  {
    "path": "src/generators/util.ts",
    "content": "import { basename, dirname, extname, join, relative, sep } from 'path';\nimport { readdirSync, existsSync, writeFileSync, openSync, closeSync } from 'fs';\nimport { Logger } from '../logger/logger';\nimport { toUnixPath } from '../util/helpers';\n\nimport * as Constants from '../util/constants';\nimport * as GeneratorConstants from './constants';\nimport { camelCase, constantCase, getStringPropertyValue, mkDirpAsync, paramCase, pascalCase, readFileAsync, replaceAll, sentenceCase, upperCaseFirst, writeFileAsync } from '../util/helpers';\nimport { BuildContext } from '../util/interfaces';\nimport { globAll, GlobResult } from '../util/glob-util';\nimport { changeExtension, ensureSuffix, removeSuffix } from '../util/helpers';\nimport { appendNgModuleDeclaration, appendNgModuleExports, appendNgModuleProvider, insertNamedImportIfNeeded } from '../util/typescript-utils';\n\nexport function hydrateRequest(context: BuildContext, request: GeneratorRequest) {\n  const hydrated = request as HydratedGeneratorRequest;\n  const suffix = getSuffixFromGeneratorType(context, request.type);\n\n  hydrated.className = ensureSuffix(pascalCase(request.name), upperCaseFirst(suffix));\n  hydrated.fileName = removeSuffix(paramCase(request.name), `-${paramCase(suffix)}`);\n\n  if (request.type === 'pipe') hydrated.pipeName = camelCase(request.name);\n\n  if (!!hydrated.includeNgModule) {\n    if (hydrated.type === 'tabs') {\n      hydrated.importStatement = `import { IonicPage, NavController } from 'ionic-angular';`;\n    } else {\n      hydrated.importStatement = `import { IonicPage, NavController, NavParams } from 'ionic-angular';`;\n    }\n    hydrated.ionicPage = '\\n@IonicPage()';\n  } else {\n\n    hydrated.ionicPage = null;\n    hydrated.importStatement = `import { NavController, NavParams } from 'ionic-angular';`;\n\n  }\n  hydrated.dirToRead = join(getStringPropertyValue(Constants.ENV_VAR_IONIC_ANGULAR_TEMPLATE_DIR), request.type);\n\n  const baseDir = getDirToWriteToByType(context, request.type);\n  hydrated.dirToWrite = join(baseDir, hydrated.fileName);\n  return hydrated;\n}\n\nexport function createCommonModule(envVar: string, requestType: string) {\n  let className = requestType.charAt(0).toUpperCase() + requestType.slice(1) + 's';\n  let tmplt = `import { NgModule } from '@angular/core';\\n@NgModule({\\n\\tdeclarations: [],\\n\\timports: [],\\n\\texports: []\\n})\\nexport class ${className}Module {}\\n`;\n  writeFileSync(envVar, tmplt);\n}\n\nexport function hydrateTabRequest(context: BuildContext, request: GeneratorTabRequest) {\n  const h = hydrateRequest(context, request);\n  const hydrated = Object.assign({\n    tabs: request.tabs,\n    tabContent: '',\n    tabVariables: '',\n    tabsImportStatement: '',\n  }, h) as HydratedGeneratorRequest;\n\n  if (hydrated.includeNgModule) {\n    hydrated.tabsImportStatement += `import { IonicPage, NavController } from 'ionic-angular';`;\n  } else {\n    hydrated.tabsImportStatement += `import { NavController } from 'ionic-angular';`;\n  }\n\n  for (let i = 0; i < request.tabs.length; i++) {\n    const tabVar = `${camelCase(request.tabs[i].name)}Root`;\n\n    if (hydrated.includeNgModule) {\n      hydrated.tabVariables += `  ${tabVar} = '${request.tabs[i].className}'\\n`;\n    } else {\n      hydrated.tabVariables += `  ${tabVar} = ${request.tabs[i].className}\\n`;\n    }\n\n    // If this is the last ion-tab to insert\n    // then we do not want a new line\n    if (i === request.tabs.length - 1) {\n      hydrated.tabContent += `    <ion-tab [root]=\"${tabVar}\" tabTitle=\"${sentenceCase(request.tabs[i].name)}\" tabIcon=\"information-circle\"></ion-tab>`;\n    } else {\n      hydrated.tabContent += `    <ion-tab [root]=\"${tabVar}\" tabTitle=\"${sentenceCase(request.tabs[i].name)}\" tabIcon=\"information-circle\"></ion-tab>\\n`;\n    }\n  }\n\n  return hydrated;\n}\n\nexport function readTemplates(pathToRead: string): Promise<Map<string, string>> {\n  const fileNames = readdirSync(pathToRead);\n  const absolutePaths = fileNames.map(fileName => {\n    return join(pathToRead, fileName);\n  });\n  const filePathToContent = new Map<string, string>();\n  const promises = absolutePaths.map(absolutePath => {\n    const promise = readFileAsync(absolutePath);\n    promise.then((fileContent: string) => {\n      filePathToContent.set(absolutePath, fileContent);\n    });\n    return promise;\n  });\n  return Promise.all(promises).then(() => {\n    return filePathToContent;\n  });\n}\n\nexport function filterOutTemplates(request: HydratedGeneratorRequest, templates: Map<string, string>) {\n  const templatesToUseMap = new Map<string, string>();\n  templates.forEach((fileContent: string, filePath: string) => {\n    const newFileExtension = basename(filePath, GeneratorConstants.KNOWN_FILE_EXTENSION);\n    const shouldSkip = (!request.includeNgModule && newFileExtension === GeneratorConstants.NG_MODULE_FILE_EXTENSION) || (!request.includeSpec && newFileExtension === GeneratorConstants.SPEC_FILE_EXTENSION);\n    if (!shouldSkip) {\n      templatesToUseMap.set(filePath, fileContent);\n    }\n  });\n  return templatesToUseMap;\n}\n\nexport function applyTemplates(request: HydratedGeneratorRequest, templates: Map<string, string>) {\n  const appliedTemplateMap = new Map<string, string>();\n  templates.forEach((fileContent: string, filePath: string) => {\n    fileContent = replaceAll(fileContent, GeneratorConstants.CLASSNAME_VARIABLE, request.className);\n    fileContent = replaceAll(fileContent, GeneratorConstants.PIPENAME_VARIABLE, request.pipeName);\n    fileContent = replaceAll(fileContent, GeneratorConstants.IMPORTSTATEMENT_VARIABLE, request.importStatement);\n    fileContent = replaceAll(fileContent, GeneratorConstants.IONICPAGE_VARIABLE, request.ionicPage);\n    fileContent = replaceAll(fileContent, GeneratorConstants.FILENAME_VARIABLE, request.fileName);\n    fileContent = replaceAll(fileContent, GeneratorConstants.SUPPLIEDNAME_VARIABLE, request.name);\n    fileContent = replaceAll(fileContent, GeneratorConstants.TAB_CONTENT_VARIABLE, request.tabContent);\n    fileContent = replaceAll(fileContent, GeneratorConstants.TAB_VARIABLES_VARIABLE, request.tabVariables);\n    fileContent = replaceAll(fileContent, GeneratorConstants.TABS_IMPORTSTATEMENT_VARIABLE, request.tabsImportStatement);\n    appliedTemplateMap.set(filePath, fileContent);\n  });\n  return appliedTemplateMap;\n}\n\nexport function writeGeneratedFiles(request: HydratedGeneratorRequest, processedTemplates: Map<string, string>): Promise<string[]> {\n  const promises: Promise<any>[] = [];\n  const createdFileList: string[] = [];\n  processedTemplates.forEach((fileContent: string, filePath: string) => {\n    const newFileExtension = basename(filePath, GeneratorConstants.KNOWN_FILE_EXTENSION);\n    const newFileName = `${request.fileName}.${newFileExtension}`;\n    const fileToWrite = join(request.dirToWrite, newFileName);\n    createdFileList.push(fileToWrite);\n    promises.push(createDirAndWriteFile(fileToWrite, fileContent));\n  });\n\n  return Promise.all(promises).then(() => {\n    return createdFileList;\n  });\n}\n\nfunction createDirAndWriteFile(filePath: string, fileContent: string) {\n  const directory = dirname(filePath);\n  return mkDirpAsync(directory).then(() => {\n    return writeFileAsync(filePath, fileContent);\n  });\n}\n\nexport function getNgModules(context: BuildContext, types: string[]): Promise<GlobResult[]> {\n  const ngModuleSuffix = getStringPropertyValue(Constants.ENV_NG_MODULE_FILE_NAME_SUFFIX);\n  const patterns = types.map((type) => join(getDirToWriteToByType(context, type), '**', `*${ngModuleSuffix}`));\n  return globAll(patterns);\n}\n\nfunction getSuffixFromGeneratorType(context: BuildContext, type: string) {\n  if (type === Constants.COMPONENT) {\n    return 'Component';\n  } else if (type === Constants.DIRECTIVE) {\n    return 'Directive';\n  } else if (type === Constants.PAGE || type === Constants.TABS) {\n    return 'Page';\n  } else if (type === Constants.PIPE) {\n    return 'Pipe';\n  } else if (type === Constants.PROVIDER) {\n    return 'Provider';\n  }\n  throw new Error(`Unknown Generator Type: ${type}`);\n}\n\nexport function getDirToWriteToByType(context: BuildContext, type: string) {\n  if (type === Constants.COMPONENT) {\n    return context.componentsDir;\n  } else if (type === Constants.DIRECTIVE) {\n    return context.directivesDir;\n  } else if (type === Constants.PAGE || type === Constants.TABS) {\n    return context.pagesDir;\n  } else if (type === Constants.PIPE) {\n    return context.pipesDir;\n  } else if (type === Constants.PROVIDER) {\n    return context.providersDir;\n  }\n  throw new Error(`Unknown Generator Type: ${type}`);\n}\n\nexport async function nonPageFileManipulation(context: BuildContext, name: string, ngModulePath: string, type: string) {\n  const hydratedRequest = hydrateRequest(context, { type, name });\n  const envVar = getStringPropertyValue(`IONIC_${hydratedRequest.type.toUpperCase()}S_NG_MODULE_PATH`);\n  let importPath;\n  let fileContent: string;\n  let templatesArray: string[] = await generateTemplates(context, hydratedRequest, false);\n  if (hydratedRequest.type === 'pipe' || hydratedRequest.type === 'component' || hydratedRequest.type === 'directive') {\n    if (!existsSync(envVar)) createCommonModule(envVar, hydratedRequest.type);\n  }\n  const typescriptFilePath = changeExtension(templatesArray.filter(path => extname(path) === '.ts')[0], '');\n\n\n  readFileAsync(ngModulePath).then((content) => {\n    importPath = type === 'pipe' || type === 'component' || type === 'directive'\n      // Insert `./` if it's a pipe component or directive\n      // Since these will go in a common module.\n      ? toUnixPath(`./${relative(dirname(ngModulePath), hydratedRequest.dirToWrite)}${sep}${hydratedRequest.fileName}`)\n      : toUnixPath(`${relative(dirname(ngModulePath), hydratedRequest.dirToWrite)}${sep}${hydratedRequest.fileName}`);\n\n    content = insertNamedImportIfNeeded(ngModulePath, content, hydratedRequest.className, importPath);\n    if (type === 'pipe' || type === 'component' || type === 'directive') {\n      content = appendNgModuleDeclaration(ngModulePath, content, hydratedRequest.className);\n      content = appendNgModuleExports(ngModulePath, content, hydratedRequest.className);\n    }\n    if (type === 'provider') {\n      content = appendNgModuleProvider(ngModulePath, content, hydratedRequest.className);\n    }\n    return writeFileAsync(ngModulePath, content);\n  });\n}\n\nexport function tabsModuleManipulation(tabs: string[][], hydratedRequest: HydratedGeneratorRequest, tabHydratedRequests: HydratedGeneratorRequest[]): Promise<any> {\n\n  tabHydratedRequests.forEach((tabRequest, index) => {\n    tabRequest.generatedFileNames = tabs[index];\n  });\n  const ngModulePath = tabs[0].find((element: any): boolean => element.indexOf('module') !== -1);\n\n  if (!ngModulePath) {\n    // Static imports\n    const tabsPath = join(hydratedRequest.dirToWrite, `${hydratedRequest.fileName}.ts`);\n\n    let modifiedContent: string = null;\n    return readFileAsync(tabsPath).then(content => {\n      tabHydratedRequests.forEach((tabRequest) => {\n        const typescriptFilePath = changeExtension(tabRequest.generatedFileNames.filter(path => extname(path) === '.ts')[0], '');\n        const importPath = toUnixPath(relative(dirname(tabsPath), typescriptFilePath));\n        modifiedContent = insertNamedImportIfNeeded(tabsPath, content, tabRequest.className, importPath);\n        content = modifiedContent;\n      });\n      return writeFileAsync(tabsPath, modifiedContent);\n    });\n  }\n\n}\n\nexport function generateTemplates(context: BuildContext, request: HydratedGeneratorRequest, includePageConstants?: boolean): Promise<string[]> {\n  Logger.debug('[Generators] generateTemplates: Reading templates ...');\n\n  let pageConstantFile = join(context.pagesDir, 'pages.constants.ts');\n  if (includePageConstants && !existsSync(pageConstantFile)) createPageConstants(context);\n\n  return readTemplates(request.dirToRead).then((map: Map<string, string>) => {\n\n    Logger.debug('[Generators] generateTemplates: Filtering out NgModule and Specs if needed ...');\n    return filterOutTemplates(request, map);\n\n  }).then((filteredMap: Map<string, string>) => {\n\n    Logger.debug('[Generators] generateTemplates: Applying templates ...');\n    const appliedTemplateMap = applyTemplates(request, filteredMap);\n\n    Logger.debug('[Generators] generateTemplates: Writing generated files to disk ...');\n\n    // Adding const to gets some type completion\n    if (includePageConstants) createConstStatments(pageConstantFile, request);\n\n    return writeGeneratedFiles(request, appliedTemplateMap);\n  });\n}\n\nexport function createConstStatments(pageConstantFile: string, request: HydratedGeneratorRequest) {\n  readFileAsync(pageConstantFile).then((content) => {\n    content += `\\nexport const ${constantCase(request.className)} = '${request.className}';`;\n    writeFileAsync(pageConstantFile, content);\n  });\n}\n\nexport function createPageConstants(context: BuildContext) {\n  let pageConstantFile = join(context.pagesDir, 'pages.constants.ts');\n  writeFileAsync(pageConstantFile, '//Constants for getting type references');\n}\n\nexport interface GeneratorOption {\n  type: string;\n  multiple: boolean;\n}\n\nexport interface GeneratorRequest {\n  type?: string;\n  name?: string;\n  includeSpec?: boolean;\n  includeNgModule?: boolean;\n}\n\nexport interface GeneratorTabRequest extends GeneratorRequest {\n  tabs?: HydratedGeneratorRequest[];\n}\n\nexport interface HydratedGeneratorRequest extends GeneratorRequest {\n  fileName?: string;\n  importStatement?: string;\n  ionicPage?: string;\n  className?: string;\n  tabContent?: string;\n  tabVariables?: string;\n  tabsImportStatement?: string;\n  dirToRead?: string;\n  dirToWrite?: string;\n  generatedFileNames?: string[];\n  pipeName?: string;\n}\n"
  },
  {
    "path": "src/generators.ts",
    "content": "import * as Constants from './util/constants';\nimport { BuildContext } from './util/interfaces';\nimport { hydrateRequest, hydrateTabRequest, getNgModules, GeneratorOption, GeneratorRequest, nonPageFileManipulation, generateTemplates, tabsModuleManipulation } from './generators/util';\n\nexport { getNgModules, GeneratorOption, GeneratorRequest };\n\nexport function processPageRequest(context: BuildContext, name: string, commandOptions?: { module?: boolean; constants?: boolean; }) {\n  if (commandOptions) {\n    const hydratedRequest = hydrateRequest(context, { type: 'page', name, includeNgModule: commandOptions.module });\n    return generateTemplates(context, hydratedRequest, commandOptions.constants);\n  }else {\n    const hydratedRequest = hydrateRequest(context, { type: 'page', name, includeNgModule: false });\n    return generateTemplates(context, hydratedRequest);\n  }\n}\n\nexport function processPipeRequest(context: BuildContext, name: string, ngModulePath: string) {\n  return nonPageFileManipulation(context, name, ngModulePath, 'pipe');\n}\n\nexport function processDirectiveRequest(context: BuildContext, name: string, ngModulePath: string) {\n  return nonPageFileManipulation(context, name, ngModulePath, 'directive');\n}\n\nexport function processComponentRequest(context: BuildContext, name: string, ngModulePath: string) {\n  return nonPageFileManipulation(context, name, ngModulePath, 'component');\n}\n\nexport function processProviderRequest(context: BuildContext, name: string, ngModulePath: string) {\n  return nonPageFileManipulation(context, name, ngModulePath, 'provider');\n}\n\nexport function processTabsRequest(context: BuildContext, name: string, tabs: any[],  commandOptions?: { module?: boolean; constants?: boolean; }) {\n\n  const includePageConstants = commandOptions ? commandOptions.constants : false;\n  const includeNgModule = commandOptions ? commandOptions.module : false;\n\n  const tabHydratedRequests = tabs.map((tab) => hydrateRequest(context, { type: 'page', name: tab, includeNgModule}));\n  const hydratedRequest = hydrateTabRequest(context, { type: 'tabs', name, includeNgModule, tabs: tabHydratedRequests });\n\n  return generateTemplates(context, hydratedRequest, includePageConstants).then(() => {\n    const promises = tabHydratedRequests.map((hydratedRequest) => {\n      return generateTemplates(context, hydratedRequest, includePageConstants);\n    });\n\n    return Promise.all(promises);\n  }).then((tabs) => {\n    tabsModuleManipulation(tabs, hydratedRequest, tabHydratedRequests);\n  });\n}\n\nexport function listOptions() {\n  const list: GeneratorOption[] = [];\n  list.push({type: Constants.COMPONENT, multiple: false});\n  list.push({type: Constants.DIRECTIVE, multiple: false});\n  list.push({type: Constants.PAGE, multiple: false});\n  list.push({type: Constants.PIPE, multiple: false});\n  list.push({type: Constants.PROVIDER, multiple: false});\n  list.push({type: Constants.TABS, multiple: true});\n  return list;\n}\n\n\n\n"
  },
  {
    "path": "src/highlight/github-gist.scss",
    "content": "/**\n * GitHub Gist Theme\n * Author : Louis Barranqueiro - https://github.com/LouisBarranqueiro\n * https://highlightjs.org/\n */\n\n.hljs-comment,\n.hljs-meta {\n  color: #969896;\n}\n\n.hljs-string,\n.hljs-variable,\n.hljs-template-variable,\n.hljs-strong,\n.hljs-emphasis,\n.hljs-quote {\n  color: #df5000;\n}\n\n.hljs-keyword,\n.hljs-selector-tag,\n.hljs-type {\n  color: #a71d5d;\n}\n\n.hljs-literal,\n.hljs-symbol,\n.hljs-bullet,\n.hljs-attribute {\n  color: #0086b3;\n}\n\n.hljs-section,\n.hljs-name {\n  color: #63a35c;\n}\n\n.hljs-tag {\n  color: #333333;\n}\n\n.hljs-title,\n.hljs-attr,\n.hljs-selector-id,\n.hljs-selector-class,\n.hljs-selector-attr,\n.hljs-selector-pseudo {\n  color: #795da3;\n}\n\n.hljs-addition {\n  color: #55a532;\n  background-color: #eaffea;\n}\n\n.hljs-deletion {\n  color: #bd2c00;\n  background-color: #ffecec;\n}\n\n.hljs-link {\n  text-decoration: underline;\n}\n"
  },
  {
    "path": "src/highlight/highlight.spec.ts",
    "content": "import { highlight, highlightError } from './highlight';\n\n\ndescribe('highlight.js', () => {\n\n  describe('highlightError', () => {\n\n    it('should error highlight unescaped', () => {\n      const htmlInput = `x & y`;\n      const errorCharStart = 2;\n      const errorLength = 1;\n      const v = highlightError(htmlInput, errorCharStart, errorLength);\n      expect(v).toEqual(`x <span class=\"ion-diagnostics-error-chr\">&</span> y`);\n    });\n\n    it('should error highlight escaped >', () => {\n      const sourceText = `x > y`;\n      const htmlInput = highlight('typescript', sourceText, true).value;\n      const errorCharStart = 2;\n      const errorLength = 1;\n      const v = highlightError(htmlInput, errorCharStart, errorLength);\n      expect(v).toEqual(`x <span class=\"ion-diagnostics-error-chr\">&gt;</span> y`);\n    });\n\n    it('should error highlight before escaped >', () => {\n      const sourceText = `if (x > y) return;`;\n      const htmlInput = highlight('typescript', sourceText, true).value;\n      const errorCharStart = 4;\n      const errorLength = 1;\n      const v = highlightError(htmlInput, errorCharStart, errorLength);\n      expect(v).toEqual(`<span class=\"hljs-keyword\">if</span> (<span class=\"ion-diagnostics-error-chr\">x</span> &gt; y) <span class=\"hljs-keyword\">return</span>;`);\n    });\n\n    it('should error highlight after escaped <', () => {\n      const sourceText = `if (x < y) return;`;\n      const htmlInput = highlight('typescript', sourceText, true).value;\n      const errorCharStart = 8;\n      const errorLength = 1;\n      const v = highlightError(htmlInput, errorCharStart, errorLength);\n      expect(v).toEqual(`<span class=\"hljs-keyword\">if</span> (x &lt; <span class=\"ion-diagnostics-error-chr\">y</span>) <span class=\"hljs-keyword\">return</span>;`);\n    });\n\n    it('should error highlight first 3 chars', () => {\n      // var name: string = 'Ellie';\n      const htmlInput = `<span class=\"hljs-keyword\">var</span> name: <span class=\"hljs-built_in\">string</span> = <span class=\"hljs-string\">'Ellie'</span>;`;\n      const errorCharStart = 0;\n      const errorLength = 3;\n      const v = highlightError(htmlInput, errorCharStart, errorLength);\n      expect(v).toEqual(`<span class=\"hljs-keyword\"><span class=\"ion-diagnostics-error-chr\">v</span><span class=\"ion-diagnostics-error-chr\">a</span><span class=\"ion-diagnostics-error-chr\">r</span></span> name: <span class=\"hljs-built_in\">string</span> = <span class=\"hljs-string\">'Ellie'</span>;`);\n    });\n\n    it('should error highlight second char', () => {\n      // var name: string = 'Ellie';\n      const htmlInput = `<span class=\"hljs-keyword\">var</span> name: <span class=\"hljs-built_in\">string</span> = <span class=\"hljs-string\">'Ellie'</span>;`;\n      const errorCharStart = 1;\n      const errorLength = 1;\n      const v = highlightError(htmlInput, errorCharStart, errorLength);\n      expect(v).toEqual(`<span class=\"hljs-keyword\">v<span class=\"ion-diagnostics-error-chr\">a</span>r</span> name: <span class=\"hljs-built_in\">string</span> = <span class=\"hljs-string\">'Ellie'</span>;`);\n    });\n\n    it('should error highlight first char', () => {\n      // var name: string = 'Ellie';\n      const htmlInput = `<span class=\"hljs-keyword\">var</span> name: <span class=\"hljs-built_in\">string</span> = <span class=\"hljs-string\">'Ellie'</span>;`;\n      const errorCharStart = 0;\n      const errorLength = 1;\n      const v = highlightError(htmlInput, errorCharStart, errorLength);\n      expect(v).toEqual(`<span class=\"hljs-keyword\"><span class=\"ion-diagnostics-error-chr\">v</span>ar</span> name: <span class=\"hljs-built_in\">string</span> = <span class=\"hljs-string\">'Ellie'</span>;`);\n    });\n\n    it('should return the same if there are is no errorLength', () => {\n      // textInput = `var name: string = 'Ellie';`;\n      const htmlInput = `<span class=\"hljs-keyword\">var</span> name: <span class=\"hljs-built_in\">string</span> = <span class=\"hljs-string\">'Ellie'</span>;`;\n      const errorCharStart = 10;\n      const errorLength = 0;\n      const v = highlightError(htmlInput, errorCharStart, errorLength);\n      expect(v).toEqual(htmlInput);\n    });\n\n    it('should return the same if there are is no errorCharStart', () => {\n      // textInput = `var name: string = 'Ellie';`;\n      const htmlInput = `<span class=\"hljs-keyword\">var</span> name: <span class=\"hljs-built_in\">string</span> = <span class=\"hljs-string\">'Ellie'</span>;`;\n      const errorCharStart = -1;\n      const errorLength = 10;\n      const v = highlightError(htmlInput, errorCharStart, errorLength);\n      expect(v).toEqual(htmlInput);\n    });\n\n  });\n\n  describe('typescript', () => {\n\n    it('should replace typescript with <', () => {\n      const sourceText = `if (x < y) return;`;\n      const v = highlight('typescript', sourceText, true).value;\n      expect(v).toEqual(`<span class=\"hljs-keyword\">if</span> (x &lt; y) <span class=\"hljs-keyword\">return</span>;`);\n    });\n\n    it('should replace typescript', () => {\n      const sourceText = `var name: string = 'Ellie';`;\n      const v = highlight('typescript', sourceText, true).value;\n      expect(v).toEqual(`<span class=\"hljs-keyword\">var</span> name: <span class=\"hljs-built_in\">string</span> = <span class=\"hljs-string\">'Ellie'</span>;`);\n    });\n\n  });\n\n  describe('html', () => {\n\n    it('should replace html', () => {\n      const sourceText = `<div key=\"value\">Text</div>`;\n      const v = highlight('html', sourceText, true).value;\n      expect(v).toEqual(`<span class=\"hljs-tag\">&lt;<span class=\"hljs-name\">div</span> <span class=\"hljs-attr\">key</span>=<span class=\"hljs-string\">\"value\"</span>&gt;</span>Text<span class=\"hljs-tag\">&lt;/<span class=\"hljs-name\">div</span>&gt;</span>`);\n    });\n\n  });\n\n  describe('scss', () => {\n\n    it('should replace scss', () => {\n      const sourceText = `.className { color: $red; }`;\n      const v = highlight('scss', sourceText, true).value;\n      expect(v).toEqual(`<span class=\"hljs-selector-class\">.className</span> { <span class=\"hljs-attribute\">color</span>: <span class=\"hljs-variable\">$red</span>; }`);\n    });\n\n  });\n\n});\n"
  },
  {
    "path": "src/highlight/highlight.ts",
    "content": "/**\n * Ported from highlight.js\n * Syntax highlighting with language autodetection.\n * https://highlightjs.org/\n * Copyright (c) 2006, Ivan Sagalaev\n * https://github.com/isagalaev/highlight.js/blob/master/LICENSE\n */\n\nvar hljs: any = {};\n// Convenience variables for build-in objects\nvar objectKeys: any = Object.keys;\n\n// Global internal variables used within the highlight.js library.\nvar languages: any = {},\n    aliases: any   = {};\n\nvar spanEndTag = '</span>';\n\n// Global options used when within external APIs. This is modified when\n// calling the `hljs.configure` function.\nvar options: any = {\n  classPrefix: 'hljs-',\n  tabReplace: null,\n  useBR: false,\n  languages: undefined\n};\n\n// Object map that is used to escape some common HTML characters.\nvar escapeRegexMap: any = {\n  '&': '&amp;',\n  '<': '&lt;',\n  '>': '&gt;'\n};\n\n/* Utility functions */\n\nfunction escape(value: string) {\n  return value.replace(/[&<>]/gm, function(character: any) {\n    return escapeRegexMap[character];\n  });\n}\n\nfunction testRe(re: any, lexeme: any) {\n  var match = re && re.exec(lexeme);\n  return match && match.index === 0;\n}\n\nfunction inherit(parent: any, obj: any) {\n  var key: any;\n  var result: any = {};\n\n  for (key in parent)\n    result[key] = parent[key];\n  if (obj)\n    for (key in obj)\n      result[key] = obj[key];\n  return result;\n}\n\n/* Initialization */\n\nfunction compileLanguage(language: any) {\n\n  function reStr(re: any) {\n      return (re && re.source) || re;\n  }\n\n  function langRe(value: any, global?: any) {\n    return new RegExp(\n      reStr(value),\n      'm' + (language.case_insensitive ? 'i' : '') + (global ? 'g' : '')\n    );\n  }\n\n  function compileMode(mode: any, parent?: any): any {\n    if (mode.compiled)\n      return;\n    mode.compiled = true;\n\n    mode.keywords = mode.keywords || mode.beginKeywords;\n    if (mode.keywords) {\n      var compiled_keywords: any = {};\n\n      var flatten = function(className: any, str: any) {\n        if (language.case_insensitive) {\n          str = str.toLowerCase();\n        }\n        str.split(' ').forEach(function(kw: any) {\n          var pair = kw.split('|');\n          compiled_keywords[pair[0]] = [className, pair[1] ? Number(pair[1]) : 1];\n        });\n      };\n\n      if (typeof mode.keywords === 'string') { // string\n        flatten('keyword', mode.keywords);\n      } else {\n        objectKeys(mode.keywords).forEach(function (className: any) {\n          flatten(className, mode.keywords[className]);\n        });\n      }\n      mode.keywords = compiled_keywords;\n    }\n    mode.lexemesRe = langRe(mode.lexemes || /\\w+/, true);\n\n    if (parent) {\n      if (mode.beginKeywords) {\n        mode.begin = '\\\\b(' + mode.beginKeywords.split(' ').join('|') + ')\\\\b';\n      }\n      if (!mode.begin)\n        mode.begin = /\\B|\\b/;\n      mode.beginRe = langRe(mode.begin);\n      if (!mode.end && !mode.endsWithParent)\n        mode.end = /\\B|\\b/;\n      if (mode.end)\n        mode.endRe = langRe(mode.end);\n      mode.terminator_end = reStr(mode.end) || '';\n      if (mode.endsWithParent && parent.terminator_end)\n        mode.terminator_end += (mode.end ? '|' : '') + parent.terminator_end;\n    }\n    if (mode.illegal)\n      mode.illegalRe = langRe(mode.illegal);\n    if (mode.relevance == null)\n      mode.relevance = 1;\n    if (!mode.contains) {\n      mode.contains = [];\n    }\n    var expanded_contains: any = [];\n    mode.contains.forEach(function(c: any) {\n      if (c.variants) {\n        c.variants.forEach(function(v: any) {expanded_contains.push(inherit(c, v)); });\n      } else {\n        expanded_contains.push(c === 'self' ? mode : c);\n      }\n    });\n    mode.contains = expanded_contains;\n    mode.contains.forEach(function(c: any) {compileMode(c, mode); });\n\n    if (mode.starts) {\n      compileMode(mode.starts, parent);\n    }\n\n    var terminators =\n      mode.contains.map(function(c: any) {\n        return c.beginKeywords ? '\\\\.?(' + c.begin + ')\\\\.?' : c.begin;\n      })\n      .concat([mode.terminator_end, mode.illegal])\n      .map(reStr)\n      .filter(Boolean);\n    mode.terminators = terminators.length ? langRe(terminators.join('|'), true) : {exec: function(/*s*/): any {return null; }};\n  }\n\n  compileMode(language);\n}\n\n\nexport function highlightError(htmlInput: string, errorCharStart: number, errorLength: number) {\n  if (errorCharStart < 0 || errorLength < 1 || !htmlInput) return htmlInput;\n\n  const chars = htmlInput.split('');\n  let inTag = false;\n  let textIndex = -1;\n  for (var htmlIndex = 0; htmlIndex < chars.length; htmlIndex++) {\n    if (chars[htmlIndex] === '<') {\n      inTag = true;\n      continue;\n\n    } else if (chars[htmlIndex] === '>') {\n      inTag = false;\n      continue;\n\n    } else if (inTag) {\n      continue;\n\n    } else if (chars[htmlIndex] === '&') {\n\n      var isValidEscape = true;\n      var escapeChars = '&';\n      for (var i = htmlIndex + 1; i < chars.length; i++) {\n        if (!chars[i] || chars[i] === ' ') {\n          isValidEscape = false;\n          break;\n        } else if (chars[i] === ';') {\n          escapeChars += ';';\n          break;\n        } else {\n          escapeChars += chars[i];\n        }\n      }\n      isValidEscape = (isValidEscape && escapeChars.length > 1 && escapeChars.length < 9 && escapeChars[escapeChars.length - 1] === ';');\n\n      if (isValidEscape) {\n        chars[htmlIndex] = escapeChars;\n        for (let i = 0; i < escapeChars.length - 1; i++) {\n          chars.splice(htmlIndex + 1, 1);\n        }\n      }\n    }\n\n    textIndex++;\n\n    if (textIndex < errorCharStart || textIndex >= errorCharStart + errorLength) {\n      continue;\n    }\n\n    chars[htmlIndex] = `<span class=\"ion-diagnostics-error-chr\">${chars[htmlIndex]}</span>`;\n  }\n\n  return chars.join('');\n}\n\n/*\nCore highlighting function. Accepts a language name, or an alias, and a\nstring with the code to highlight. Returns an object with the following\nproperties:\n\n- relevance (int)\n- value (an HTML string with highlighting markup)\n\n*/\nexport function highlight(name: string, value: string, ignore_illegals?: boolean, continuation?: any): {value: string, relevance: number, language?: string, top?: any} {\n\n  function subMode(lexeme: any, mode: any) {\n    var i: any, length: any;\n\n    for (i = 0, length = mode.contains.length; i < length; i++) {\n      if (testRe(mode.contains[i].beginRe, lexeme)) {\n        return mode.contains[i];\n      }\n    }\n  }\n\n  function endOfMode(mode: any, lexeme: any): any {\n    if (testRe(mode.endRe, lexeme)) {\n      while (mode.endsParent && mode.parent) {\n        mode = mode.parent;\n      }\n      return mode;\n    }\n    if (mode.endsWithParent) {\n      return endOfMode(mode.parent, lexeme);\n    }\n  }\n\n  function isIllegal(lexeme: any, mode: any) {\n    return !ignore_illegals && testRe(mode.illegalRe, lexeme);\n  }\n\n  function keywordMatch(mode: any, match: any) {\n    var match_str = language.case_insensitive ? match[0].toLowerCase() : match[0];\n    return mode.keywords.hasOwnProperty(match_str) && mode.keywords[match_str];\n  }\n\n  function buildSpan(classname: any, insideSpan: any, leaveOpen?: any, noPrefix?: any): any {\n    var classPrefix = noPrefix ? '' : options.classPrefix,\n        openSpan    = '<span class=\"' + classPrefix,\n        closeSpan   = leaveOpen ? '' : spanEndTag;\n\n    openSpan += classname + '\">';\n\n    return openSpan + insideSpan + closeSpan;\n  }\n\n  function processKeywords() {\n    var keyword_match: any, last_index: any, match: any, result: any;\n\n    if (!top.keywords)\n      return escape(mode_buffer);\n\n    result = '';\n    last_index = 0;\n    top.lexemesRe.lastIndex = 0;\n    match = top.lexemesRe.exec(mode_buffer);\n\n    while (match) {\n      result += escape(mode_buffer.substr(last_index, match.index - last_index));\n      keyword_match = keywordMatch(top, match);\n      if (keyword_match) {\n        relevance += keyword_match[1];\n        result += buildSpan(keyword_match[0], escape(match[0]));\n      } else {\n        result += escape(match[0]);\n      }\n      last_index = top.lexemesRe.lastIndex;\n      match = top.lexemesRe.exec(mode_buffer);\n    }\n    return result + escape(mode_buffer.substr(last_index));\n  }\n\n  function processSubLanguage() {\n    var explicit = typeof top.subLanguage === 'string';\n    if (explicit && !languages[top.subLanguage]) {\n      return escape(mode_buffer);\n    }\n\n    var result = explicit ?\n                  highlight(top.subLanguage, mode_buffer, true, continuations[top.subLanguage]) :\n                  highlightAuto(mode_buffer, top.subLanguage.length ? top.subLanguage : undefined);\n\n    // Counting embedded language score towards the host language may be disabled\n    // with zeroing the containing mode relevance. Usecase in point is Markdown that\n    // allows XML everywhere and makes every XML snippet to have a much larger Markdown\n    // score.\n    if (top.relevance > 0) {\n      relevance += result.relevance;\n    }\n    if (explicit) {\n      continuations[top.subLanguage] = result.top;\n    }\n    return buildSpan(result.language, result.value, false, true);\n  }\n\n  function processBuffer() {\n    result += (top.subLanguage != null ? processSubLanguage() : processKeywords());\n    mode_buffer = '';\n  }\n\n  function startNewMode(mode: any, asdf?: any) {\n    result += mode.className ? buildSpan(mode.className, '', true) : '';\n    top = Object.create(mode, {parent: {value: top}});\n  }\n\n  function processLexeme(buffer: any, lexeme?: any): any {\n\n    mode_buffer += buffer;\n\n    if (lexeme == null) {\n      processBuffer();\n      return 0;\n    }\n\n    var new_mode = subMode(lexeme, top);\n    if (new_mode) {\n      if (new_mode.skip) {\n        mode_buffer += lexeme;\n      } else {\n        if (new_mode.excludeBegin) {\n          mode_buffer += lexeme;\n        }\n        processBuffer();\n        if (!new_mode.returnBegin && !new_mode.excludeBegin) {\n          mode_buffer = lexeme;\n        }\n      }\n      startNewMode(new_mode, lexeme);\n      return new_mode.returnBegin ? 0 : lexeme.length;\n    }\n\n    var end_mode = endOfMode(top, lexeme);\n    if (end_mode) {\n      var origin = top;\n      if (origin.skip) {\n        mode_buffer += lexeme;\n      } else {\n        if (!(origin.returnEnd || origin.excludeEnd)) {\n          mode_buffer += lexeme;\n        }\n        processBuffer();\n        if (origin.excludeEnd) {\n          mode_buffer = lexeme;\n        }\n      }\n      do {\n        if (top.className) {\n          result += spanEndTag;\n        }\n        if (!top.skip) {\n          relevance += top.relevance;\n        }\n        top = top.parent;\n      } while (top !== end_mode.parent);\n      if (end_mode.starts) {\n        startNewMode(end_mode.starts, '');\n      }\n      return origin.returnEnd ? 0 : lexeme.length;\n    }\n\n    if (isIllegal(lexeme, top))\n      throw new Error('Illegal lexeme \"' + lexeme + '\" for mode \"' + (top.className || '<unnamed>') + '\"');\n\n    /*\n    Parser should not reach this point as all types of lexemes should be caught\n    earlier, but if it does due to some bug make sure it advances at least one\n    character forward to prevent infinite looping.\n    */\n    mode_buffer += lexeme;\n    return lexeme.length || 1;\n  }\n\n  var language = getLanguage(name);\n  if (!language) {\n    throw new Error('Unknown language: \"' + name + '\"');\n  }\n\n  compileLanguage(language);\n  var top: any = continuation || language;\n  var continuations: any = {}; // keep continuations for sub-languages\n  var result = '', current: any;\n  for (current = top; current !== language; current = current.parent) {\n    if (current.className) {\n      result = buildSpan(current.className, '', true) + result;\n    }\n  }\n  var mode_buffer = '';\n  var relevance = 0;\n  try {\n    var match: any, count: any, index = 0;\n    while (true) {\n      top.terminators.lastIndex = index;\n      match = top.terminators.exec(value);\n      if (!match)\n        break;\n      count = processLexeme(value.substr(index, match.index - index), match[0]);\n      index = match.index + count;\n    }\n    processLexeme(value.substr(index));\n    for (current = top; current.parent; current = current.parent) { // close dangling modes\n      if (current.className) {\n        result += spanEndTag;\n      }\n    }\n    return {\n      relevance: relevance,\n      value: result,\n      language: name,\n      top: top\n    };\n  } catch (e) {\n    if (e.message && e.message.indexOf('Illegal') !== -1) {\n      return {\n        relevance: 0,\n        value: escape(value)\n      };\n    } else {\n      throw e;\n    }\n  }\n}\n\n/*\nHighlighting with language detection. Accepts a string with the code to\nhighlight. Returns an object with the following properties:\n\n- language (detected language)\n- relevance (int)\n- value (an HTML string with highlighting markup)\n- second_best (object with the same structure for second-best heuristically\n  detected language, may be absent)\n\n*/\nfunction highlightAuto(text: any, languageSubset?: any) {\n  languageSubset = languageSubset || options.languages || objectKeys(languages);\n  var result: any = {\n    relevance: 0,\n    value: escape(text)\n  };\n  var second_best = result;\n  languageSubset.filter(getLanguage).forEach(function(name: any) {\n    var current = highlight(name, text, false);\n    current.language = name;\n    if (current.relevance > second_best.relevance) {\n      second_best = current;\n    }\n    if (current.relevance > result.relevance) {\n      second_best = result;\n      result = current;\n    }\n  });\n  if (second_best.language) {\n    result.second_best = second_best;\n  }\n  return result;\n}\n\n/*\nUpdates highlight.js global options with values passed in the form of an object.\n*/\nfunction configure(user_options: any) {\n  options = inherit(options, user_options);\n}\n\nfunction registerLanguage(name: any, language: any) {\n  var lang = languages[name] = language(hljs);\n  if (lang.aliases) {\n    lang.aliases.forEach(function(alias: any) {aliases[alias] = name; });\n  }\n}\n\nfunction listLanguages() {\n  return objectKeys(languages);\n}\n\nfunction getLanguage(name: any) {\n  name = (name || '').toLowerCase();\n  return languages[name] || languages[aliases[name]];\n}\n\n/* Interface definition */\n\nhljs.highlight = highlight;\nhljs.highlightAuto = highlightAuto;\nhljs.configure = configure;\nhljs.registerLanguage = registerLanguage;\nhljs.listLanguages = listLanguages;\nhljs.getLanguage = getLanguage;\nhljs.inherit = inherit;\n\n// Common regexps\nhljs.IDENT_RE = '[a-zA-Z]\\\\w*';\nhljs.UNDERSCORE_IDENT_RE = '[a-zA-Z_]\\\\w*';\nhljs.NUMBER_RE = '\\\\b\\\\d+(\\\\.\\\\d+)?';\nhljs.C_NUMBER_RE = '(-?)(\\\\b0[xX][a-fA-F0-9]+|(\\\\b\\\\d+(\\\\.\\\\d*)?|\\\\.\\\\d+)([eE][-+]?\\\\d+)?)'; // 0x..., 0..., decimal, float\nhljs.BINARY_NUMBER_RE = '\\\\b(0b[01]+)'; // 0b...\nhljs.RE_STARTERS_RE = '!|!=|!==|%|%=|&|&&|&=|\\\\*|\\\\*=|\\\\+|\\\\+=|,|-|-=|/=|/|:|;|<<|<<=|<=|<|===|==|=|>>>=|>>=|>=|>>>|>>|>|\\\\?|\\\\[|\\\\{|\\\\(|\\\\^|\\\\^=|\\\\||\\\\|=|\\\\|\\\\||~';\n\n// Common modes\nhljs.BACKSLASH_ESCAPE = {\n  begin: '\\\\\\\\[\\\\s\\\\S]', relevance: 0\n};\nhljs.APOS_STRING_MODE = {\n  className: 'string',\n  begin: '\\'', end: '\\'',\n  illegal: '\\\\n',\n  contains: [hljs.BACKSLASH_ESCAPE]\n};\nhljs.QUOTE_STRING_MODE = {\n  className: 'string',\n  begin: '\"', end: '\"',\n  illegal: '\\\\n',\n  contains: [hljs.BACKSLASH_ESCAPE]\n};\nhljs.PHRASAL_WORDS_MODE = {\n  begin: /\\b(a|an|the|are|I'm|isn't|don't|doesn't|won't|but|just|should|pretty|simply|enough|gonna|going|wtf|so|such|will|you|your|like)\\b/\n};\nhljs.COMMENT = function (begin: any, end: any, inherits: any) {\n  var mode = hljs.inherit(\n    {\n      className: 'comment',\n      begin: begin, end: end,\n      contains: []\n    },\n    inherits || {}\n  );\n  mode.contains.push(hljs.PHRASAL_WORDS_MODE);\n  mode.contains.push({\n    className: 'doctag',\n    begin: '(?:TODO|FIXME|NOTE|BUG|XXX):',\n    relevance: 0\n  });\n  return mode;\n};\nhljs.C_LINE_COMMENT_MODE = hljs.COMMENT('//', '$');\nhljs.C_BLOCK_COMMENT_MODE = hljs.COMMENT('/\\\\*', '\\\\*/');\nhljs.HASH_COMMENT_MODE = hljs.COMMENT('#', '$');\nhljs.NUMBER_MODE = {\n  className: 'number',\n  begin: hljs.NUMBER_RE,\n  relevance: 0\n};\nhljs.C_NUMBER_MODE = {\n  className: 'number',\n  begin: hljs.C_NUMBER_RE,\n  relevance: 0\n};\nhljs.BINARY_NUMBER_MODE = {\n  className: 'number',\n  begin: hljs.BINARY_NUMBER_RE,\n  relevance: 0\n};\nhljs.CSS_NUMBER_MODE = {\n  className: 'number',\n  begin: hljs.NUMBER_RE + '(' +\n    '%|em|ex|ch|rem'  +\n    '|vw|vh|vmin|vmax' +\n    '|cm|mm|in|pt|pc|px' +\n    '|deg|grad|rad|turn' +\n    '|s|ms' +\n    '|Hz|kHz' +\n    '|dpi|dpcm|dppx' +\n    ')?',\n  relevance: 0\n};\nhljs.REGEXP_MODE = {\n  className: 'regexp',\n  begin: /\\//, end: /\\/[gimuy]*/,\n  illegal: /\\n/,\n  contains: [\n    hljs.BACKSLASH_ESCAPE,\n    {\n      begin: /\\[/, end: /\\]/,\n      relevance: 0,\n      contains: [hljs.BACKSLASH_ESCAPE]\n    }\n  ]\n};\nhljs.TITLE_MODE = {\n  className: 'title',\n  begin: hljs.IDENT_RE,\n  relevance: 0\n};\nhljs.UNDERSCORE_TITLE_MODE = {\n  className: 'title',\n  begin: hljs.UNDERSCORE_IDENT_RE,\n  relevance: 0\n};\nhljs.METHOD_GUARD = {\n  // excludes method names from keyword processing\n  begin: '\\\\.\\\\s*' + hljs.UNDERSCORE_IDENT_RE,\n  relevance: 0\n};\n\nhljs.registerLanguage('typescript', typescript);\n\nfunction typescript(hljs: any) {\n  var KEYWORDS = {\n    keyword:\n      'in if for while finally var new function do return void else break catch ' +\n      'instanceof with throw case default try this switch continue typeof delete ' +\n      'let yield const class public private protected get set super ' +\n      'static implements enum export import declare type namespace abstract',\n    literal:\n      'true false null undefined NaN Infinity',\n    built_in:\n      'eval isFinite isNaN parseFloat parseInt decodeURI decodeURIComponent ' +\n      'encodeURI encodeURIComponent escape unescape Object Function Boolean Error ' +\n      'EvalError InternalError RangeError ReferenceError StopIteration SyntaxError ' +\n      'TypeError URIError Number Math Date String RegExp Array Float32Array ' +\n      'Float64Array Int16Array Int32Array Int8Array Uint16Array Uint32Array ' +\n      'Uint8Array Uint8ClampedArray ArrayBuffer DataView JSON Intl arguments require ' +\n      'module console window document any number boolean string void'\n  };\n\n  return {\n    aliases: ['ts'],\n    keywords: KEYWORDS,\n    contains: [\n      {\n        className: 'meta',\n        begin: /^\\s*['\"]use strict['\"]/\n      },\n      hljs.APOS_STRING_MODE,\n      hljs.QUOTE_STRING_MODE,\n      { // template string\n        className: 'string',\n        begin: '`', end: '`',\n        contains: [\n          hljs.BACKSLASH_ESCAPE,\n          {\n            className: 'subst',\n            begin: '\\\\$\\\\{', end: '\\\\}'\n          }\n        ]\n      },\n      hljs.C_LINE_COMMENT_MODE,\n      hljs.C_BLOCK_COMMENT_MODE,\n      {\n        className: 'number',\n        variants: [\n          { begin: '\\\\b(0[bB][01]+)' },\n          { begin: '\\\\b(0[oO][0-7]+)' },\n          { begin: hljs.C_NUMBER_RE }\n        ],\n        relevance: 0\n      },\n      { // \"value\" container\n        begin: '(' + hljs.RE_STARTERS_RE + '|\\\\b(case|return|throw)\\\\b)\\\\s*',\n        keywords: 'return throw case',\n        contains: [\n          hljs.C_LINE_COMMENT_MODE,\n          hljs.C_BLOCK_COMMENT_MODE,\n          hljs.REGEXP_MODE\n        ],\n        relevance: 0\n      },\n      {\n        className: 'function',\n        begin: 'function', end: /[\\{;]/, excludeEnd: true,\n        keywords: KEYWORDS,\n        contains: [\n          'self',\n          hljs.inherit(hljs.TITLE_MODE, {begin: /[A-Za-z$_][0-9A-Za-z$_]*/}),\n          {\n            className: 'params',\n            begin: /\\(/, end: /\\)/,\n            excludeBegin: true,\n            excludeEnd: true,\n            keywords: KEYWORDS,\n            contains: [\n              hljs.C_LINE_COMMENT_MODE,\n              hljs.C_BLOCK_COMMENT_MODE\n            ],\n            illegal: /[\"'\\(]/\n          }\n        ],\n        illegal: /%/,\n        relevance: 0 // () => {} is more typical in TypeScript\n      },\n      {\n        beginKeywords: 'constructor', end: /\\{/, excludeEnd: true\n      },\n      { // prevent references like module.id from being higlighted as module definitions\n        begin: /module\\./,\n        keywords: {built_in: 'module'},\n        relevance: 0\n      },\n      {\n        beginKeywords: 'module', end: /\\{/, excludeEnd: true\n      },\n      {\n        beginKeywords: 'interface', end: /\\{/, excludeEnd: true,\n        keywords: 'interface extends'\n      },\n      {\n        begin: /\\$[(.]/ // relevance booster for a pattern common to JS libs: `$(something)` and `$.something`\n      },\n      {\n        begin: '\\\\.' + hljs.IDENT_RE, relevance: 0 // hack: prevents detection of keywords after dots\n      }\n    ]\n  };\n}\n\n\nhljs.registerLanguage('scss', scss);\n\nfunction scss(hljs: any) {\n  var IDENT_RE = '[a-zA-Z-][a-zA-Z0-9_-]*';\n  var VARIABLE = {\n    className: 'variable',\n    begin: '(\\\\$' + IDENT_RE + ')\\\\b'\n  };\n  var HEXCOLOR = {\n    className: 'number', begin: '#[0-9A-Fa-f]+'\n  };\n  // var DEF_INTERNALS = {\n  //   className: 'attribute',\n  //   begin: '[A-Z\\\\_\\\\.\\\\-]+', end: ':',\n  //   excludeEnd: true,\n  //   illegal: '[^\\\\s]',\n  //   starts: {\n  //     endsWithParent: true, excludeEnd: true,\n  //     contains: [\n  //       HEXCOLOR,\n  //       hljs.CSS_NUMBER_MODE,\n  //       hljs.QUOTE_STRING_MODE,\n  //       hljs.APOS_STRING_MODE,\n  //       hljs.C_BLOCK_COMMENT_MODE,\n  //       {\n  //         className: 'meta', begin: '!important'\n  //       }\n  //     ]\n  //   }\n  // };\n  return {\n    case_insensitive: true,\n    illegal: '[=/|\\']',\n    contains: [\n      hljs.C_LINE_COMMENT_MODE,\n      hljs.C_BLOCK_COMMENT_MODE,\n      {\n        className: 'selector-id', begin: '\\\\#[A-Za-z0-9_-]+',\n        relevance: 0\n      },\n      {\n        className: 'selector-class', begin: '\\\\.[A-Za-z0-9_-]+',\n        relevance: 0\n      },\n      {\n        className: 'selector-attr', begin: '\\\\[', end: '\\\\]',\n        illegal: '$'\n      },\n      {\n        className: 'selector-tag', // begin: IDENT_RE, end: '[,|\\\\s]'\n        begin: '\\\\b(a|abbr|acronym|address|area|article|aside|audio|b|base|big|blockquote|body|br|button|canvas|caption|cite|code|col|colgroup|command|datalist|dd|del|details|dfn|div|dl|dt|em|embed|fieldset|figcaption|figure|footer|form|frame|frameset|(h[1-6])|head|header|hgroup|hr|html|i|iframe|img|input|ins|kbd|keygen|label|legend|li|link|map|mark|meta|meter|nav|noframes|noscript|object|ol|optgroup|option|output|p|param|pre|progress|q|rp|rt|ruby|samp|script|section|select|small|span|strike|strong|style|sub|sup|table|tbody|td|textarea|tfoot|th|thead|time|title|tr|tt|ul|var|video)\\\\b',\n        relevance: 0\n      },\n      {\n        begin: ':(visited|valid|root|right|required|read-write|read-only|out-range|optional|only-of-type|only-child|nth-of-type|nth-last-of-type|nth-last-child|nth-child|not|link|left|last-of-type|last-child|lang|invalid|indeterminate|in-range|hover|focus|first-of-type|first-line|first-letter|first-child|first|enabled|empty|disabled|default|checked|before|after|active)'\n      },\n      {\n        begin: '::(after|before|choices|first-letter|first-line|repeat-index|repeat-item|selection|value)'\n      },\n      VARIABLE,\n      {\n        className: 'attribute',\n        begin: '\\\\b(z-index|word-wrap|word-spacing|word-break|width|widows|white-space|visibility|vertical-align|unicode-bidi|transition-timing-function|transition-property|transition-duration|transition-delay|transition|transform-style|transform-origin|transform|top|text-underline-position|text-transform|text-shadow|text-rendering|text-overflow|text-indent|text-decoration-style|text-decoration-line|text-decoration-color|text-decoration|text-align-last|text-align|tab-size|table-layout|right|resize|quotes|position|pointer-events|perspective-origin|perspective|page-break-inside|page-break-before|page-break-after|padding-top|padding-right|padding-left|padding-bottom|padding|overflow-y|overflow-x|overflow-wrap|overflow|outline-width|outline-style|outline-offset|outline-color|outline|orphans|order|opacity|object-position|object-fit|normal|none|nav-up|nav-right|nav-left|nav-index|nav-down|min-width|min-height|max-width|max-height|mask|marks|margin-top|margin-right|margin-left|margin-bottom|margin|list-style-type|list-style-position|list-style-image|list-style|line-height|letter-spacing|left|justify-content|initial|inherit|ime-mode|image-orientation|image-resolution|image-rendering|icon|hyphens|height|font-weight|font-variant-ligatures|font-variant|font-style|font-stretch|font-size-adjust|font-size|font-language-override|font-kerning|font-feature-settings|font-family|font|float|flex-wrap|flex-shrink|flex-grow|flex-flow|flex-direction|flex-basis|flex|filter|empty-cells|display|direction|cursor|counter-reset|counter-increment|content|column-width|column-span|column-rule-width|column-rule-style|column-rule-color|column-rule|column-gap|column-fill|column-count|columns|color|clip-path|clip|clear|caption-side|break-inside|break-before|break-after|box-sizing|box-shadow|box-decoration-break|bottom|border-width|border-top-width|border-top-style|border-top-right-radius|border-top-left-radius|border-top-color|border-top|border-style|border-spacing|border-right-width|border-right-style|border-right-color|border-right|border-radius|border-left-width|border-left-style|border-left-color|border-left|border-image-width|border-image-source|border-image-slice|border-image-repeat|border-image-outset|border-image|border-color|border-collapse|border-bottom-width|border-bottom-style|border-bottom-right-radius|border-bottom-left-radius|border-bottom-color|border-bottom|border|background-size|background-repeat|background-position|background-origin|background-image|background-color|background-clip|background-attachment|background-blend-mode|background|backface-visibility|auto|animation-timing-function|animation-play-state|animation-name|animation-iteration-count|animation-fill-mode|animation-duration|animation-direction|animation-delay|animation|align-self|align-items|align-content)\\\\b',\n        illegal: '[^\\\\s]'\n      },\n      {\n        begin: '\\\\b(whitespace|wait|w-resize|visible|vertical-text|vertical-ideographic|uppercase|upper-roman|upper-alpha|underline|transparent|top|thin|thick|text|text-top|text-bottom|tb-rl|table-header-group|table-footer-group|sw-resize|super|strict|static|square|solid|small-caps|separate|se-resize|scroll|s-resize|rtl|row-resize|ridge|right|repeat|repeat-y|repeat-x|relative|progress|pointer|overline|outside|outset|oblique|nowrap|not-allowed|normal|none|nw-resize|no-repeat|no-drop|newspaper|ne-resize|n-resize|move|middle|medium|ltr|lr-tb|lowercase|lower-roman|lower-alpha|loose|list-item|line|line-through|line-edge|lighter|left|keep-all|justify|italic|inter-word|inter-ideograph|inside|inset|inline|inline-block|inherit|inactive|ideograph-space|ideograph-parenthesis|ideograph-numeric|ideograph-alpha|horizontal|hidden|help|hand|groove|fixed|ellipsis|e-resize|double|dotted|distribute|distribute-space|distribute-letter|distribute-all-lines|disc|disabled|default|decimal|dashed|crosshair|collapse|col-resize|circle|char|center|capitalize|break-word|break-all|bottom|both|bolder|bold|block|bidi-override|below|baseline|auto|always|all-scroll|absolute|table|table-cell)\\\\b'\n      },\n      {\n        begin: ':', end: ';',\n        contains: [\n          VARIABLE,\n          HEXCOLOR,\n          hljs.CSS_NUMBER_MODE,\n          hljs.QUOTE_STRING_MODE,\n          hljs.APOS_STRING_MODE,\n          {\n            className: 'meta', begin: '!important'\n          }\n        ]\n      },\n      {\n        begin: '@', end: '[{;]',\n        keywords: 'mixin include extend for if else each while charset import debug media page content font-face namespace warn',\n        contains: [\n          VARIABLE,\n          hljs.QUOTE_STRING_MODE,\n          hljs.APOS_STRING_MODE,\n          HEXCOLOR,\n          hljs.CSS_NUMBER_MODE,\n          {\n            begin: '\\\\s[A-Za-z0-9_.-]+',\n            relevance: 0\n          }\n        ]\n      }\n    ]\n  };\n}\n\n\nhljs.registerLanguage('xml', xml);\n\nfunction xml(hljs: any) {\n  var XML_IDENT_RE = '[A-Za-z0-9\\\\._:-]+';\n  var TAG_INTERNALS = {\n    endsWithParent: true,\n    illegal: /</,\n    relevance: 0,\n    contains: [\n      {\n        className: 'attr',\n        begin: XML_IDENT_RE,\n        relevance: 0\n      },\n      {\n        begin: /=\\s*/,\n        relevance: 0,\n        contains: [\n          {\n            className: 'string',\n            endsParent: true,\n            variants: [\n              {begin: /\"/, end: /\"/},\n              {begin: /'/, end: /'/},\n              {begin: /[^\\s\"'=<>`]+/}\n            ]\n          }\n        ]\n      }\n    ]\n  };\n  return {\n    aliases: ['html', 'xhtml', 'rss', 'atom', 'xjb', 'xsd', 'xsl', 'plist'],\n    case_insensitive: true,\n    contains: [\n      {\n        className: 'meta',\n        begin: '<!DOCTYPE', end: '>',\n        relevance: 10,\n        contains: [{begin: '\\\\[', end: '\\\\]'}]\n      },\n      hljs.COMMENT(\n        '<!--',\n        '-->',\n        {\n          relevance: 10\n        }\n      ),\n      {\n        begin: '<\\\\!\\\\[CDATA\\\\[', end: '\\\\]\\\\]>',\n        relevance: 10\n      },\n      {\n        begin: /<\\?(php)?/, end: /\\?>/,\n        subLanguage: 'php',\n        contains: [{begin: '/\\\\*', end: '\\\\*/', skip: true}]\n      },\n      {\n        className: 'tag',\n        /*\n        The lookahead pattern (?=...) ensures that 'begin' only matches\n        '<style' as a single word, followed by a whitespace or an\n        ending braket. The '$' is needed for the lexeme to be recognized\n        by hljs.subMode() that tests lexemes outside the stream.\n        */\n        begin: '<style(?=\\\\s|>|$)', end: '>',\n        keywords: {name: 'style'},\n        contains: [TAG_INTERNALS],\n        starts: {\n          end: '</style>', returnEnd: true,\n          subLanguage: ['css', 'xml']\n        }\n      },\n      {\n        className: 'tag',\n        // See the comment in the <style tag about the lookahead pattern\n        begin: '<script(?=\\\\s|>|$)', end: '>',\n        keywords: {name: 'script'},\n        contains: [TAG_INTERNALS],\n        starts: {\n          end: '\\<\\/script\\>', returnEnd: true,\n          subLanguage: ['actionscript', 'javascript', 'handlebars', 'xml']\n        }\n      },\n      {\n        className: 'meta',\n        variants: [\n          {begin: /<\\?xml/, end: /\\?>/, relevance: 10},\n          {begin: /<\\?\\w+/, end: /\\?>/}\n        ]\n      },\n      {\n        className: 'tag',\n        begin: '</?', end: '/?>',\n        contains: [\n          {\n            className: 'name', begin: /[^\\/><\\s]+/, relevance: 0\n          },\n          TAG_INTERNALS\n        ]\n      }\n    ]\n  };\n}\n"
  },
  {
    "path": "src/index.ts",
    "content": "export { build } from './build';\nexport { bundle, bundleUpdate } from './bundle';\nexport { clean } from './clean';\nexport { cleancss } from './cleancss';\nexport { copy, copyUpdate } from './copy';\nexport { lint } from './lint';\nexport { minify } from './minify';\nexport { ngc } from './ngc';\nexport { sass, sassUpdate } from './sass';\nexport { serve } from './serve';\nexport { transpile } from './transpile';\nexport { uglifyjs } from './uglifyjs';\nexport { watch, buildUpdate } from './watch';\nexport * from './util/config';\nexport * from './util/helpers';\nexport * from './util/interfaces';\nexport * from './util/constants';\nexport * from './generators';\n\nexport { getDeepLinkData } from './deep-linking/util';\n\nimport { generateContext } from './util/config';\nimport { getAppScriptsVersion, setContext } from './util/helpers';\nimport { Logger } from './logger/logger';\n\nexport function run(task: string) {\n  try {\n    Logger.info(`ionic-app-scripts ${getAppScriptsVersion()}`, 'cyan');\n  } catch (e) {}\n\n  try {\n    const context = generateContext(null);\n    setContext(context);\n    require(`../dist/${task}`)[task](context).catch((err: any) => {\n      errorLog(task, err);\n    });\n  } catch (e) {\n    errorLog(task, e);\n  }\n}\n\nfunction errorLog(task: string, e: any) {\n  Logger.error(`ionic-app-script task: \"${task}\"`);\n  if (e && e.toString() !== 'Error') {\n    Logger.error(`${e}`);\n  }\n  if (e.stack) {\n    Logger.unformattedError(e.stack);\n  }\n  process.exit(1);\n}\n"
  },
  {
    "path": "src/lint/lint-factory.spec.ts",
    "content": "import { Configuration, Linter } from 'tslint';\nimport { DiagnosticCategory } from 'typescript';\nimport * as ts from 'typescript';\nimport { isObject } from 'util';\nimport {\n  createLinter,\n  createProgram,\n  getFileNames,\n  getLintResult,\n  getTsLintConfig,\n  lint,\n  typeCheck\n} from './lint-factory';\n\n\ndescribe('lint factory', () => {\n  describe('createProgram()', () => {\n    it('should create a TS Program', () => {\n      const context: any = {rootDir: ''};\n      const program: any = createProgram(context, '');\n      const fns = [\n        'getSourceFiles',\n        'getTypeChecker'\n      ];\n\n      expect(isObject(program)).toBeTruthy();\n      for (const fn of fns) {\n        expect(typeof program[fn]).toEqual('function');\n      }\n    });\n  });\n\n  describe('getTsLintConfig()', () => {\n    it('should fetch the TSLint configuration from file path', () => {\n      const tsConfigFilePath = 'tsconfig.json';\n      const mockConfig = {rulesDirectory: ['node_modules/@ionic']};\n      spyOn(Configuration, Configuration.loadConfigurationFromPath.name).and.returnValue(mockConfig);\n\n      const config = getTsLintConfig(tsConfigFilePath);\n\n      expect(isObject(config)).toBeTruthy();\n      expect(Configuration.loadConfigurationFromPath).toHaveBeenLastCalledWith(tsConfigFilePath);\n      expect(config).toEqual(mockConfig);\n    });\n\n    it('should extend configuration with {linterOptions} if provided', () => {\n      const tsConfigFilePath = 'tsconfig.json';\n      const mockConfig = {rulesDirectory: ['node_modules/@ionic']};\n      spyOn(Configuration, Configuration.loadConfigurationFromPath.name).and.returnValue(mockConfig);\n      const config = getTsLintConfig(tsConfigFilePath, {\n        typeCheck: true\n      });\n\n      expect(config.linterOptions).toEqual({\n        typeCheck: true\n      });\n    });\n  });\n\n  describe('createLinter()', () => {\n    it('should create a Linter', () => {\n      const context: any = {rootDir: ''};\n      const program = createProgram(context, '');\n      const linter = createLinter(context, program);\n\n      expect(linter instanceof Linter).toBeTruthy();\n    });\n  });\n\n  describe('getFileNames()', () => {\n    it('should get the file names referenced in the tsconfig.json', () => {\n      const context: any = {rootDir: ''};\n      const program = createProgram(context, '');\n      const mockFiles = ['test.ts'];\n      spyOn(Linter, 'getFileNames').and.returnValue(mockFiles);\n      const files = getFileNames(context, program);\n\n      expect(Array.isArray(files)).toBeTruthy();\n      expect(files).toEqual(mockFiles);\n    });\n  });\n\n  describe('typeCheck()', () => {\n    it('should not be called if {typeCheck} is false', done => {\n      const context: any = {rootDir: ''};\n      const program = createProgram(context, '');\n\n      spyOn(ts, ts.getPreEmitDiagnostics.name).and.returnValue([]);\n\n      typeCheck(context, program, {typeCheck: false})\n        .then((result) => {\n          expect(ts.getPreEmitDiagnostics).toHaveBeenCalledTimes(0);\n          expect(result).toEqual([]);\n          done();\n        });\n    });\n\n    it('should type check if {typeCheck} is true', done => {\n      const context: any = {rootDir: ''};\n      const program = createProgram(context, '');\n\n      const diagnostics: any = [{\n        file: {},\n        start: 2,\n        length: 10,\n        messageText: 'Oops',\n        category: DiagnosticCategory.Warning,\n        code: 120\n      }];\n\n      spyOn(ts, ts.getPreEmitDiagnostics.name).and.returnValue(diagnostics);\n\n      typeCheck(context, program, {typeCheck: true})\n        .then((result) => {\n          expect(ts.getPreEmitDiagnostics).toHaveBeenCalledWith(program);\n          expect(result).toEqual(diagnostics);\n          done();\n        });\n    });\n  });\n\n  describe('lint()', () => {\n    it('should lint a file', () => {\n      const context: any = {rootDir: ''};\n      const program = createProgram(context, '');\n      const linter = createLinter(context, program);\n      spyOn(linter, 'lint').and.returnValue(undefined);\n      const config = {};\n      const filePath = 'test.ts';\n      const fileContents = 'const test = true;';\n\n      lint(linter, config, filePath, fileContents);\n\n      expect(linter.lint).toHaveBeenCalledWith(filePath, fileContents, config);\n    });\n  });\n  describe('getLintResult()', () => {\n    it('should get the lint results after linting a file', () => {\n      const context: any = {rootDir: ''};\n      const program = createProgram(context, '');\n      const linter = createLinter(context, program);\n      spyOn(linter, 'lint').and.returnValue(undefined);\n      const mockResult: any = {};\n      spyOn(linter, 'getResult').and.returnValue(mockResult);\n      const config = {\n        jsRules: new Map(),\n        rules: new Map()\n      };\n      const filePath = 'test.ts';\n      const fileContents = 'const test = true;';\n\n      lint(linter, config, filePath, fileContents);\n\n      const result = getLintResult(linter);\n      expect(isObject(result)).toBeTruthy();\n      expect(result).toEqual(mockResult);\n    });\n  });\n});\n"
  },
  {
    "path": "src/lint/lint-factory.ts",
    "content": "import { Configuration, Linter, LintResult } from 'tslint';\nimport { Program, getPreEmitDiagnostics, Diagnostic } from 'typescript';\nimport { BuildContext } from '../util/interfaces';\nimport { isObject } from 'util';\n\n\nexport interface LinterOptions {\n  typeCheck?: boolean;\n}\n\nexport interface LinterConfig {\n  [key: string]: any;\n}\n\n\n/**\n * Lint a file according to config\n * @param {Linter} linter\n * @param {LinterConfig} config\n * @param {string} filePath\n * @param {string} fileContents\n */\nexport function lint(linter: Linter, config: LinterConfig, filePath: string, fileContents: string): void {\n  linter.lint(filePath, fileContents, config as any);\n}\n\n/**\n * Get the linter result\n * @param {Linter} linter\n * @return {LintResult}\n */\nexport function getLintResult(linter: Linter): LintResult {\n  return linter.getResult();\n}\n\n\n/**\n * Type check a TS program\n * @param {BuildContext} context\n * @param {Program} program\n * @param {LinterOptions} linterOptions\n * @return {Promise<Diagnostic[]>}\n */\nexport function typeCheck(context: BuildContext, program: Program, linterOptions?: LinterOptions): Promise<Diagnostic[]> {\n  if (isObject(linterOptions) && linterOptions.typeCheck) {\n    return Promise.resolve(getPreEmitDiagnostics(program));\n  }\n  return Promise.resolve([]);\n}\n\n\n/**\n * Create a TS program based on the BuildContext {rootDir} or TS config file path (if provided)\n * @param {BuildContext} context\n * @param {string} tsConfig\n * @return {Program}\n */\nexport function createProgram(context: BuildContext, tsConfig: string): Program {\n  return Linter.createProgram(tsConfig, context.rootDir);\n}\n\n\n/**\n * Get all files that are sourced in TS config\n * @param {BuildContext} context\n * @param {Program} program\n * @return {Array<string>}\n */\nexport function getFileNames(context: BuildContext, program: Program): string[] {\n  return Linter.getFileNames(program);\n}\n\n\n/**\n * Get lint configuration\n * @param {string} tsLintConfig\n * @param {LinterOptions} linterOptions\n * @return {Linter}\n */\nexport function getTsLintConfig(tsLintConfig: string, linterOptions?: LinterOptions): LinterConfig {\n  const config = Configuration.loadConfigurationFromPath(tsLintConfig);\n  Object.assign(config, isObject(linterOptions) ? {linterOptions} : {});\n  return config;\n}\n\n/**\n * Create a TS linter\n * @param {BuildContext} context\n * @param {Program} program\n * @return {Linter}\n */\nexport function createLinter(context: BuildContext, program: Program): Linter {\n  return new Linter({\n    fix: false\n  }, program);\n}\n"
  },
  {
    "path": "src/lint/lint-utils.spec.ts",
    "content": "import * as fs from 'fs';\nimport { DiagnosticCategory } from 'typescript';\nimport * as helpers from '../util/helpers';\nimport * as loggerDiagnostics from '../logger/logger-diagnostics';\nimport * as tsLogger from '../logger/logger-typescript';\nimport * as tsLintLogger from '../logger/logger-tslint';\nimport * as linter from './lint-factory';\nimport * as utils from './lint-utils';\n\n\ndescribe('lint utils', () => {\n  describe('lintFile()', () => {\n    it('should return lint details', () => {\n      const filePath = 'test.ts';\n      const fileContent = `\n        export const foo = 'bar';\n      `;\n      const context: any = {\n        rootDir: ''\n      };\n      const mockLintResult: any = {\n        errorCount: 0,\n        warningCount: 0,\n        failures: [],\n        fixes: [],\n        format: '',\n        output: ''\n      };\n\n      spyOn(linter, linter.lint.name).and.returnValue(mockLintResult);\n      spyOn(linter, linter.createProgram.name).and.returnValue({});\n      spyOn(linter, linter.createLinter.name).and.returnValue({});\n\n      // Mock the file read\n      spyOn(helpers, helpers.readFileAsync.name).and.returnValue(Promise.resolve(fileContent));\n      spyOn(fs, 'openSync').and.returnValue(null);\n      spyOn(fs, 'readSync').and.returnValue(null);\n      spyOn(fs, 'closeSync').and.returnValue(null);\n\n      const mockProgram = linter.createProgram(context, '');\n      const mockLinter = linter.createLinter(context, mockProgram);\n      const mockConfig = {};\n\n      return utils.lintFile(mockLinter, mockConfig, filePath)\n        .then(() => {\n          expect(linter.lint)\n            .toHaveBeenCalledWith(mockLinter, mockConfig, filePath, fileContent);\n        });\n    });\n  });\n\n  describe('processTypeCheckDiagnostics()', () => {\n    it('should not throw an error when there are no files with errors or warnings', () => {\n      utils.processTypeCheckDiagnostics({}, []);\n    });\n\n    it('should throw an error when one or more file has failures', () => {\n      const knownError = new Error('Should never get here');\n      const results: any[] = [\n        {\n          file: {},\n          start: 0,\n          length: 10,\n          messageText: 'Something failed',\n          category: DiagnosticCategory.Warning,\n          code: 100\n        }\n      ];\n\n      spyOn(tsLogger, tsLogger.runTypeScriptDiagnostics.name).and.returnValue(null);\n      spyOn(loggerDiagnostics, loggerDiagnostics.printDiagnostics.name).and.returnValue(null);\n\n      try {\n        utils.processTypeCheckDiagnostics({}, results);\n        throw knownError;\n      } catch (e) {\n        expect(loggerDiagnostics.printDiagnostics).toHaveBeenCalledTimes(1);\n        expect(e).not.toEqual(knownError);\n      }\n    });\n  });\n\n  describe('processLintResult()', () => {\n    it('should not throw an error when there are no files with errors or warnings', () => {\n      utils.processLintResult({}, {\n          errorCount: 0,\n          warningCount: 0,\n          failures: [],\n          fixes: [],\n          format: '',\n          output: ''\n        });\n    });\n\n    it('should throw an error when one or more file has failures', () => {\n      const knownError = new Error('Should never get here');\n      const result: any = {\n        errorCount: 1,\n        warningCount: 0,\n        failures: [\n          {\n            getFileName() {\n              return 'test.ts';\n            }\n          }\n        ],\n        fixes: [],\n        format: '',\n        output: ''\n      };\n\n      spyOn(tsLintLogger, tsLintLogger.runTsLintDiagnostics.name).and.returnValue(null);\n      spyOn(loggerDiagnostics, loggerDiagnostics.printDiagnostics.name).and.returnValue(null);\n\n      try {\n        utils.processLintResult({}, result);\n        throw knownError;\n      } catch (ex) {\n        expect(loggerDiagnostics.printDiagnostics).toHaveBeenCalledTimes(1);\n        expect(ex).not.toEqual(knownError);\n      }\n    });\n  });\n\n  describe('generateErrorMessageForFiles()', () => {\n    it('should generate a string from an array of files', () => {\n      expect(utils.generateErrorMessageForFiles(['test_one.ts', 'test_two.ts'], 'Just testing:'))\n        .toEqual('Just testing:\\ntest_one.ts\\ntest_two.ts');\n    });\n  });\n\n  describe('getFileNames()', () => {\n    it('should retrieve file names from an array of RuleFailure objects', () => {\n      const ruleFailures: any[] = [\n          {\n            getFileName() {\n              return '/User/john/test.ts';\n            }\n          }\n      ];\n      const fileNames = utils.getFileNames({rootDir: '/User/john'}, ruleFailures);\n\n      expect(fileNames)\n        .toEqual(['test.ts']);\n    });\n  });\n\n  describe('removeDuplicateFileNames()', () => {\n    it('should remove duplicate string entries in arrays', () => {\n      expect(utils.removeDuplicateFileNames(['test.ts', 'test.ts']))\n        .toEqual(['test.ts']);\n    });\n  });\n});\n"
  },
  {
    "path": "src/lint/lint-utils.ts",
    "content": "import * as fs from 'fs';\nimport { Linter, LintResult, RuleFailure } from 'tslint';\nimport { Diagnostic, Program } from 'typescript';\nimport { BuildError } from '../util/errors';\nimport {\n  createLinter,\n  getLintResult,\n  getTsLintConfig,\n  lint,\n  LinterOptions,\n  typeCheck\n} from './lint-factory';\nimport { readFileAsync } from '../util/helpers';\nimport { BuildContext } from '../util/interfaces';\nimport { Logger } from '../logger/logger';\nimport { printDiagnostics, DiagnosticsType } from '../logger/logger-diagnostics';\nimport { runTypeScriptDiagnostics } from '../logger/logger-typescript';\nimport { runTsLintDiagnostics } from '../logger/logger-tslint';\n\n\n/**\n * Lint files\n * @param {BuildContext} context\n * @param {Program} program\n * @param {string} tsLintConfig - TSLint config file path\n * @param {Array<string>} filePaths\n * @param {LinterOptions} linterOptions\n */\nexport function lintFiles(context: BuildContext, program: Program, tsLintConfig: string, filePaths: string[], linterOptions?: LinterOptions): Promise<void> {\n  const linter = createLinter(context, program);\n  const config = getTsLintConfig(tsLintConfig, linterOptions);\n\n  return typeCheck(context, program, linterOptions)\n    .then(diagnostics => processTypeCheckDiagnostics(context, diagnostics))\n    .then(() => Promise.all(filePaths.map(filePath => lintFile(linter, config, filePath)))\n    .then(() => getLintResult(linter))\n    // NOTE: We only need to process the lint result after we ran the linter on all the files,\n    // otherwise we'll end up with duplicated messages if we process the result after each file gets linted.\n    .then((result: LintResult) => processLintResult(context, result)));\n}\n\nexport function lintFile(linter: Linter, config: any, filePath: string): Promise<void> {\n  if (isMpegFile(filePath)) {\n    return Promise.reject(`${filePath} is not a valid TypeScript file`);\n  }\n  return readFileAsync(filePath)\n    .then((fileContents: string) => lint(linter, config, filePath, fileContents));\n}\n\n\n/**\n * Process typescript diagnostics after type checking\n * NOTE: This will throw a BuildError if there were any type errors.\n * @param {BuildContext} context\n * @param {Array<Diagnostic>} tsDiagnostics\n */\nexport function processTypeCheckDiagnostics(context: BuildContext, tsDiagnostics: Diagnostic[]) {\n  if (tsDiagnostics.length > 0) {\n    const diagnostics = runTypeScriptDiagnostics(context, tsDiagnostics);\n    printDiagnostics(context, DiagnosticsType.TypeScript, diagnostics, true, false);\n    const files = removeDuplicateFileNames(diagnostics.map(diagnostic => diagnostic.relFileName));\n    const errorMessage = generateErrorMessageForFiles(files, 'The following files failed type checking:');\n    throw new BuildError(errorMessage);\n  }\n}\n\n\n/**\n * Process lint results\n * NOTE: This will throw a BuildError if there were any warnings or errors in any of the lint results.\n * @param {BuildContext} context\n * @param {LintResult} result\n */\nexport function processLintResult(context: BuildContext, result: LintResult) {\n  const files: string[] = [];\n\n  // Only process the lint result if there are errors or warnings (there's no point otherwise)\n  if (result.errorCount !== 0 || result.warningCount !== 0) {\n    const diagnostics = runTsLintDiagnostics(context, result.failures);\n    printDiagnostics(context, DiagnosticsType.TsLint, diagnostics, true, false);\n    files.push(...getFileNames(context, result.failures));\n  }\n\n  if (files.length > 0) {\n    const errorMessage = generateErrorMessageForFiles(files);\n    throw new BuildError(errorMessage);\n  }\n}\n\n\nexport function generateErrorMessageForFiles(failingFiles: string[], message?: string) {\n  return `${message || 'The following files did not pass tslint:'}\\n${failingFiles.join('\\n')}`;\n}\n\nexport function getFileNames(context: BuildContext, failures: RuleFailure[]): string[] {\n  return failures.map(failure => failure.getFileName()\n    .replace(context.rootDir, '')\n    .replace(/^\\//g, ''));\n}\n\nexport function removeDuplicateFileNames(fileNames: string[]) {\n  return Array.from(new Set(fileNames));\n}\n\nfunction isMpegFile(file: string) {\n  const buffer = new Buffer(256);\n  buffer.fill(0);\n\n  const fd = fs.openSync(file, 'r');\n  try {\n    fs.readSync(fd, buffer, 0, 256, null);\n    if (buffer.readInt8(0) === 0x47 && buffer.readInt8(188) === 0x47) {\n      Logger.debug(`tslint: ${file}: ignoring MPEG transport stream`);\n      return true;\n    }\n  } finally {\n    fs.closeSync(fd);\n  }\n  return false;\n}\n"
  },
  {
    "path": "src/lint.spec.ts",
    "content": "import * as Constants from './util/constants';\nimport * as workerClient from './worker-client';\nimport { lint } from './lint';\n\nlet originalEnv = process.env;\n\ndescribe('lint task', () => {\n  describe('lint', () => {\n    beforeEach(() => {\n      originalEnv = process.env;\n      process.env = {};\n    });\n\n    afterEach(() => {\n      process.env = originalEnv;\n    });\n\n    it('should return a resolved promise', (done: Function) => {\n      spyOn(workerClient, workerClient.runWorker.name).and.returnValue(Promise.resolve());\n\n      lint(null).then(() => {\n        done();\n      });\n    });\n\n    it('should return resolved promise when bailOnLintError is not set', (done: Function) => {\n      spyOn(workerClient, workerClient.runWorker.name).and.returnValue(Promise.reject(new Error('Simulating an error')));\n\n      lint(null).then(() => {\n        done();\n      });\n    });\n\n    it('should return rejected promise when bailOnLintError is set', (done: Function) => {\n      spyOn(workerClient, workerClient.runWorker.name).and.returnValue(Promise.reject(new Error('Simulating an error')));\n      process.env[Constants.ENV_BAIL_ON_LINT_ERROR] = 'true';\n\n      lint(null).catch(() => {\n        done();\n      });\n    });\n  });\n});\n"
  },
  {
    "path": "src/lint.ts",
    "content": "import { access } from 'fs';\nimport { join } from 'path';\n\nimport { lintFiles } from './lint/lint-utils';\nimport { createProgram, getFileNames } from './lint/lint-factory';\nimport { Logger } from './logger/logger';\nimport { getUserConfigFile } from './util/config';\nimport { ENV_BAIL_ON_LINT_ERROR, ENV_TYPE_CHECK_ON_LINT } from './util/constants';\nimport { getBooleanPropertyValue } from './util/helpers';\nimport { getTsConfigPath } from './transpile';\nimport { BuildContext, ChangedFile, TaskInfo } from './util/interfaces';\nimport { runWorker } from './worker-client';\n\n\nexport interface LintWorkerConfig {\n  tsConfig: string;\n  tsLintConfig: string | null;\n  filePaths?: string[];\n  typeCheck?: boolean;\n}\n\n\nconst taskInfo: TaskInfo = {\n  fullArg: '--tslint',\n  shortArg: '-i',\n  envVar: 'ionic_tslint',\n  packageConfig: 'IONIC_TSLINT',\n  defaultConfigFile: '../tslint'\n};\n\n\nexport function lint(context: BuildContext, tsLintConfig?: string | null, typeCheck?: boolean) {\n  const logger = new Logger('lint');\n  return runWorker('lint', 'lintWorker', context, {tsLintConfig, tsConfig: getTsConfigPath(context), typeCheck: typeCheck || getBooleanPropertyValue(ENV_TYPE_CHECK_ON_LINT)})\n    .then(() => {\n      logger.finish();\n    })\n    .catch((err: Error) => {\n      if (getBooleanPropertyValue(ENV_BAIL_ON_LINT_ERROR)) {\n        throw logger.fail(err);\n      }\n      logger.finish();\n    });\n}\n\nexport function lintWorker(context: BuildContext, {tsConfig, tsLintConfig, typeCheck}: LintWorkerConfig) {\n  return getLintConfig(context, tsLintConfig)\n    .then(tsLintConfig => lintApp(context, {\n      tsConfig,\n      tsLintConfig,\n      typeCheck\n    }));\n}\n\n\nexport function lintUpdate(changedFiles: ChangedFile[], context: BuildContext, typeCheck?: boolean) {\n  const changedTypescriptFiles = changedFiles.filter(changedFile => changedFile.ext === '.ts');\n  return runWorker('lint', 'lintUpdateWorker', context, {\n    typeCheck,\n    tsConfig: getTsConfigPath(context),\n    tsLintConfig: getUserConfigFile(context, taskInfo, null),\n    filePaths: changedTypescriptFiles.map(changedTypescriptFile => changedTypescriptFile.filePath)\n  });\n}\n\nexport function lintUpdateWorker(context: BuildContext, {tsConfig, tsLintConfig, filePaths, typeCheck}: LintWorkerConfig) {\n  const program = createProgram(context, tsConfig);\n  return getLintConfig(context, tsLintConfig)\n    .then(tsLintConfig => lintFiles(context, program, tsLintConfig, filePaths, {typeCheck}))\n    // Don't throw if linting failed\n    .catch(() => {});\n}\n\n\nfunction lintApp(context: BuildContext, {tsConfig, tsLintConfig, typeCheck}: LintWorkerConfig) {\n  const program = createProgram(context, tsConfig);\n  const files = getFileNames(context, program);\n  return lintFiles(context, program, tsLintConfig, files, {typeCheck});\n}\n\n\nfunction getLintConfig(context: BuildContext, tsLintConfig: string | null): Promise<string> {\n  return new Promise((resolve, reject) => {\n    tsLintConfig = getUserConfigFile(context, taskInfo, tsLintConfig);\n    if (!tsLintConfig) {\n      tsLintConfig = join(context.rootDir, 'tslint.json');\n    }\n\n    Logger.debug(`tslint config: ${tsLintConfig}`);\n\n    access(tsLintConfig, (err: Error) => {\n      if (err) {\n        // if the tslint.json file cannot be found that's fine, the\n        // dev may not want to run tslint at all and to do that they\n        // just don't have the file\n        reject(err);\n        return;\n      }\n      resolve(tsLintConfig);\n    });\n  });\n}\n"
  },
  {
    "path": "src/logger/logger-diagnostics.ts",
    "content": "import { BuildContext, Diagnostic, PrintLine } from '../util/interfaces';\nimport { escapeHtml, titleCase } from '../util/helpers';\nimport { highlightError } from '../highlight/highlight';\nimport { join } from 'path';\nimport { Logger } from './logger';\nimport { readFileSync, unlinkSync, writeFileSync } from 'fs';\nimport * as chalk from 'chalk';\n\n\nexport function printDiagnostics(context: BuildContext, diagnosticsType: string, diagnostics: Diagnostic[], consoleLogDiagnostics: boolean, writeHtmlDiagnostics: boolean) {\n  if (diagnostics && diagnostics.length) {\n\n    if (consoleLogDiagnostics) {\n      diagnostics.forEach(consoleLogDiagnostic);\n    }\n\n    if (writeHtmlDiagnostics) {\n      const content = diagnostics.map(generateDiagnosticHtml);\n      const fileName = getDiagnosticsFileName(context.buildDir, diagnosticsType);\n      writeFileSync(fileName, content.join('\\n'), { encoding: 'utf8' });\n    }\n  }\n}\n\n\nfunction consoleLogDiagnostic(d: Diagnostic) {\n  if (d.level === 'warn') {\n    Logger.warn(d.header);\n  } else {\n    Logger.error(d.header);\n  }\n\n  Logger.wordWrap([d.messageText]).forEach(m => {\n    console.log(m);\n  });\n  console.log('');\n\n  if (d.lines && d.lines.length) {\n    const lines = prepareLines(d.lines, 'text');\n\n    lines.forEach(l => {\n      if (!isMeaningfulLine(l.text)) {\n        return;\n      }\n\n      let msg = `L${l.lineNumber}:  `;\n      while (msg.length < Logger.INDENT.length) {\n        msg = ' ' + msg;\n      }\n\n      let text = l.text;\n      if (l.errorCharStart > -1) {\n        text = consoleHighlightError(text, l.errorCharStart, l.errorLength);\n      }\n\n      msg = chalk.dim(msg);\n\n      if (d.language === 'javascript') {\n        msg += jsConsoleSyntaxHighlight(text);\n      } else if (d.language === 'scss') {\n        msg += cssConsoleSyntaxHighlight(text, l.errorCharStart);\n      } else {\n        msg += text;\n      }\n\n      console.log(msg);\n    });\n\n    console.log('');\n  }\n}\n\nfunction consoleHighlightError(errorLine: string, errorCharStart: number, errorLength: number) {\n  let rightSideChars = errorLine.length - errorCharStart + errorLength - 1;\n  while (errorLine.length + Logger.INDENT.length > Logger.MAX_LEN) {\n    if (errorCharStart > (errorLine.length - errorCharStart + errorLength) && errorCharStart > 5) {\n      // larger on left side\n      errorLine = errorLine.substr(1);\n      errorCharStart--;\n\n    } else if (rightSideChars > 1) {\n      // larger on right side\n      errorLine = errorLine.substr(0, errorLine.length - 1);\n      rightSideChars--;\n\n    } else {\n      break;\n    }\n  }\n\n  const lineChars: string[] = [];\n  const lineLength = Math.max(errorLine.length, errorCharStart + errorLength);\n  for (var i = 0; i < lineLength; i++) {\n    var chr = errorLine.charAt(i);\n    if (i >= errorCharStart && i < errorCharStart + errorLength) {\n      chr = chalk.bgRed(chr === '' ? ' ' : chr);\n    }\n    lineChars.push(chr);\n  }\n\n  return lineChars.join('');\n}\n\n\nlet diagnosticsHtmlCache: {[key: string]: any} = {};\n\nexport function clearDiagnosticsCache() {\n  diagnosticsHtmlCache = {};\n}\n\nexport function clearDiagnostics(context: BuildContext, type: string) {\n  try {\n    delete diagnosticsHtmlCache[type];\n    unlinkSync(getDiagnosticsFileName(context.buildDir, type));\n  } catch (e) {}\n}\n\n\nexport function hasDiagnostics(buildDir: string) {\n  loadBuildDiagnosticsHtml(buildDir);\n\n  const keys = Object.keys(diagnosticsHtmlCache);\n  for (var i = 0; i < keys.length; i++) {\n    if (typeof diagnosticsHtmlCache[keys[i]] === 'string') {\n      return true;\n    }\n  }\n\n  return false;\n}\n\n\nfunction loadBuildDiagnosticsHtml(buildDir: string) {\n  try {\n    if (diagnosticsHtmlCache[DiagnosticsType.TypeScript] === undefined) {\n      diagnosticsHtmlCache[DiagnosticsType.TypeScript] = readFileSync(getDiagnosticsFileName(buildDir, DiagnosticsType.TypeScript), 'utf8');\n    }\n  } catch (e) {\n    diagnosticsHtmlCache[DiagnosticsType.TypeScript] = false;\n  }\n\n  try {\n    if (diagnosticsHtmlCache[DiagnosticsType.Sass] === undefined) {\n      diagnosticsHtmlCache[DiagnosticsType.Sass] = readFileSync(getDiagnosticsFileName(buildDir, DiagnosticsType.Sass), 'utf8');\n    }\n  } catch (e) {\n    diagnosticsHtmlCache[DiagnosticsType.Sass] = false;\n  }\n}\n\n\nexport function injectDiagnosticsHtml(buildDir: string, content: any) {\n  if (!hasDiagnostics(buildDir)) {\n    return content;\n  }\n\n  let contentStr = content.toString();\n\n  const c: string[] = [];\n  c.push(`<div id=\"ion-diagnostics\">`);\n\n  // diagnostics content\n  c.push(getDiagnosticsHtmlContent(buildDir));\n\n  c.push(`</div>`); // #ion-diagnostics\n\n  let match = contentStr.match(/<body>(?![\\s\\S]*<body>)/i);\n  if (match) {\n    contentStr = contentStr.replace(match[0], match[0] + '\\n' + c.join('\\n'));\n  } else {\n    contentStr = c.join('\\n') + contentStr;\n  }\n\n  return contentStr;\n}\n\n\nexport function getDiagnosticsHtmlContent(buildDir: string, includeDiagnosticsHtml?: string) {\n  const c: string[] = [];\n\n  // diagnostics header\n  c.push(`\n    <div class=\"ion-diagnostics-header\">\n      <div class=\"ion-diagnostics-header-content\">\n        <div class=\"ion-diagnostics-header-inner\">Error</div>\n        <div class=\"ion-diagnostics-buttons\">\n          <button id=\"ion-diagnostic-close\">Close</button>\n        </div>\n      </div>\n    </div>\n  `);\n\n  c.push(`<div class=\"ion-diagnostics-content\">`);\n\n  if (includeDiagnosticsHtml) {\n    c.push(includeDiagnosticsHtml);\n  }\n\n  loadBuildDiagnosticsHtml(buildDir);\n\n  const keys = Object.keys(diagnosticsHtmlCache);\n  for (var i = 0; i < keys.length; i++) {\n    if (typeof diagnosticsHtmlCache[keys[i]] === 'string') {\n      c.push(diagnosticsHtmlCache[keys[i]]);\n    }\n  }\n\n  c.push(`</div>`);\n\n  return c.join('\\n');\n}\n\n\nexport function generateDiagnosticHtml(d: Diagnostic) {\n  const c: string[] = [];\n\n  c.push(`<div class=\"ion-diagnostic\">`);\n\n  c.push(`<div class=\"ion-diagnostic-masthead\" title=\"${escapeHtml(d.type)} error: ${escapeHtml(d.code)}\">`);\n\n  const title = `${titleCase(d.type)} ${titleCase(d.level)}`;\n  c.push(`<div class=\"ion-diagnostic-title\">${escapeHtml(title)}</div>`);\n\n  c.push(`<div class=\"ion-diagnostic-message\" data-error-code=\"${escapeHtml(d.type)}-${escapeHtml(d.code)}\">${escapeHtml(d.messageText)}</div>`);\n\n  c.push(`</div>`); // .ion-diagnostic-masthead\n\n  c.push(generateCodeBlock(d));\n\n  c.push(`</div>`); // .ion-diagnostic\n\n  return c.join('\\n');\n}\n\n\nexport function generateCodeBlock(d: Diagnostic) {\n  const c: string[] = [];\n\n  c.push(`<div class=\"ion-diagnostic-file\">`);\n\n  c.push(`<div class=\"ion-diagnostic-file-header\" title=\"${escapeHtml(d.absFileName)}\">${escapeHtml(d.relFileName)}</div>`);\n\n  if (d.lines && d.lines.length) {\n    c.push(`<div class=\"ion-diagnostic-blob\">`);\n\n    c.push(`<table class=\"ion-diagnostic-table\">`);\n\n    prepareLines(d.lines, 'html').forEach(l => {\n      c.push(`<tr${(l.errorCharStart > -1) ? ' class=\"ion-diagnostic-error-line\"' : ''}>`);\n\n      c.push(`<td class=\"ion-diagnostic-blob-num\" data-line-number=\"${l.lineNumber}\"></td>`);\n\n      c.push(`<td class=\"ion-diagnostic-blob-code\">${highlightError(l.html, l.errorCharStart, l.errorLength)}</td>`);\n\n      c.push(`</tr>`);\n    });\n\n    c.push(`</table>`);\n\n    c.push(`</div>`); // .ion-diagnostic-blob\n  }\n\n  c.push(`</div>`); // .ion-diagnostic-file\n\n  return c.join('\\n');\n}\n\n\nfunction jsConsoleSyntaxHighlight(text: string) {\n  if (text.trim().startsWith('//')) {\n    return chalk.dim(text);\n  }\n\n  const words = text.split(' ').map(word => {\n    if (JS_KEYWORDS.indexOf(word) > -1) {\n      return chalk.cyan(word);\n    }\n    return word;\n  });\n\n  return words.join(' ');\n}\n\n\nfunction cssConsoleSyntaxHighlight(text: string, errorCharStart: number) {\n  let cssProp = true;\n  const safeChars = 'abcdefghijklmnopqrstuvwxyz-_';\n  const notProp = '.#,:}@$[]/*';\n\n  const chars: string[] = [];\n\n  for (var i = 0; i < text.length; i++) {\n    var c = text.charAt(i);\n\n    if (c === ';' || c === '{') {\n      cssProp = true;\n    } else if (notProp.indexOf(c) > -1) {\n      cssProp = false;\n    }\n    if (cssProp && safeChars.indexOf(c.toLowerCase()) > -1) {\n      chars.push(chalk.cyan(c));\n      continue;\n    }\n\n    chars.push(c);\n  }\n\n  return chars.join('');\n}\n\n\nfunction prepareLines(orgLines: PrintLine[], code: 'text'|'html') {\n  const lines: PrintLine[] = JSON.parse(JSON.stringify(orgLines));\n\n  for (let i = 0; i < 100; i++) {\n    if (!eachLineHasLeadingWhitespace(lines, code)) {\n      return lines;\n    }\n    for (let i = 0; i < lines.length; i++) {\n      (<any>lines[i])[code] = (<any>lines[i])[code].substr(1);\n      lines[i].errorCharStart--;\n      if (!(<any>lines[i])[code].length) {\n        return lines;\n      }\n    }\n  }\n\n  return lines;\n}\n\n\nfunction eachLineHasLeadingWhitespace(lines: PrintLine[], code: 'text'|'html') {\n  if (!lines.length) {\n    return false;\n  }\n  for (var i = 0; i < lines.length; i++) {\n    if ( !(<any>lines[i])[code] || (<any>lines[i])[code].length < 1) {\n      return false;\n    }\n    var firstChar = (<any>lines[i])[code].charAt(0);\n    if (firstChar !== ' ' && firstChar !== '\\t') {\n      return false;\n    }\n  }\n  return true;\n}\n\n\nconst JS_KEYWORDS = [\n  'abstract', 'any', 'as', 'break', 'boolean', 'case', 'catch', 'class',\n  'console', 'const', 'continue', 'debugger', 'declare', 'default', 'delete',\n  'do', 'else', 'enum', 'export', 'extends', 'false', 'finally', 'for', 'from',\n  'function', 'get', 'if', 'import', 'in', 'implements', 'Infinity',\n  'instanceof', 'let', 'module', 'namespace', 'NaN', 'new', 'number', 'null',\n  'public', 'private', 'protected', 'require', 'return', 'static', 'set',\n  'string', 'super', 'switch', 'this', 'throw', 'try', 'true', 'type',\n  'typeof', 'undefined', 'var', 'void', 'with', 'while', 'yield',\n];\n\n\nfunction getDiagnosticsFileName(buildDir: string, type: string) {\n  return join(buildDir, `.ion-diagnostic-${type}.html`);\n}\n\n\nfunction isMeaningfulLine(line: string) {\n  if (line) {\n    line = line.trim();\n    if (line.length) {\n      return (MEH_LINES.indexOf(line) < 0);\n    }\n  }\n  return false;\n}\n\nconst MEH_LINES = [';', ':', '{', '}', '(', ')', '/**', '/*', '*/', '*', '({', '})'];\n\nexport const DiagnosticsType = {\n  TypeScript: 'typescript',\n  Sass: 'sass',\n  TsLint: 'tslint'\n};\n"
  },
  {
    "path": "src/logger/logger-runtime.ts",
    "content": "import { Diagnostic, PrintLine } from '../util/interfaces';\nimport { escapeHtml, splitLineBreaks } from '../util/helpers';\nimport { generateCodeBlock, getDiagnosticsHtmlContent } from './logger-diagnostics';\nimport { highlight } from '../highlight/highlight';\nimport { readFileSync } from 'fs';\nimport { resolve , normalize} from 'path';\n\n\nexport function generateRuntimeDiagnosticContent(rootDir: string, buildDir: string, runtimeErrorMessage: string, runtimeErrorStack: string) {\n  let c: string[] = [];\n\n  c.push(`<div class=\"ion-diagnostic\">`);\n  c.push(`<div class=\"ion-diagnostic-masthead\">`);\n  c.push(`<div class=\"ion-diagnostic-header\">Runtime Error</div>`);\n\n  if (runtimeErrorMessage) {\n    runtimeErrorMessage = runtimeErrorMessage.replace(/inline template:\\d+:\\d+/g, '');\n    runtimeErrorMessage = runtimeErrorMessage.replace('inline template', '');\n\n    c.push(`<div class=\"ion-diagnostic-message\">${escapeHtml(runtimeErrorMessage)}</div>`);\n  }\n  c.push(`</div>`); // .ion-diagnostic-masthead\n\n  const diagnosticsHtmlCache = generateRuntimeStackDiagnostics(rootDir, runtimeErrorStack);\n  diagnosticsHtmlCache.forEach(d => {\n    c.push(generateCodeBlock(d));\n  });\n\n  if (runtimeErrorStack) {\n    c.push(`<div class=\"ion-diagnostic-stack-header\">Stack</div>`);\n    c.push(`<div class=\"ion-diagnostic-stack\">${escapeHtml(runtimeErrorStack)}</div>`);\n  }\n\n  c.push(`</div>`); // .ion-diagnostic\n\n  return getDiagnosticsHtmlContent(buildDir, c.join('\\n'));\n}\n\n\nexport function generateRuntimeStackDiagnostics(rootDir: string, stack: string) {\n  const diagnostics: Diagnostic[] = [];\n\n  if (stack) {\n    splitLineBreaks(stack).forEach(stackLine => {\n      try {\n        const match = WEBPACK_FILE_REGEX.exec(stackLine);\n        if (!match) return;\n\n        const fileSplit = match[1].split('?');\n        if (fileSplit.length !== 2) return;\n\n        const linesSplit = fileSplit[1].split(':');\n        if (linesSplit.length !== 3) return;\n\n        const fileName = fileSplit[0];\n        if (fileName.indexOf('~') > -1) return;\n\n        const errorLineNumber = parseInt(linesSplit[1], 10);\n        const errorCharNumber = parseInt(linesSplit[2], 10);\n\n        const d: Diagnostic = {\n          level: 'error',\n          language: 'typescript',\n          type: 'runtime',\n          header: '',\n          code: 'runtime',\n          messageText: '',\n          absFileName: resolve(rootDir, fileName),\n          relFileName: normalize(fileName),\n          lines: []\n        };\n\n        const sourceText = readFileSync(d.absFileName, 'utf8');\n        const srcLines = splitLineBreaks(sourceText);\n        if (!srcLines.length || errorLineNumber >= srcLines.length) return;\n\n        let htmlLines = srcLines;\n\n        try {\n          htmlLines = splitLineBreaks(highlight(d.language, sourceText, true).value);\n        } catch (e) {}\n\n        const errorLine: PrintLine = {\n          lineIndex: errorLineNumber - 1,\n          lineNumber: errorLineNumber,\n          text: srcLines[errorLineNumber - 1],\n          html: htmlLines[errorLineNumber - 1],\n          errorCharStart: errorCharNumber + 1,\n          errorLength: 1\n        };\n\n        if (errorLine.html.indexOf('class=\"hljs') === -1) {\n          try {\n            errorLine.html = highlight(d.language, errorLine.text, true).value;\n          } catch (e) {}\n        }\n\n        d.lines.push(errorLine);\n\n        if (errorLine.lineIndex > 0) {\n          const previousLine: PrintLine = {\n            lineIndex: errorLine.lineIndex - 1,\n            lineNumber: errorLine.lineNumber - 1,\n            text: srcLines[errorLine.lineIndex - 1],\n            html: htmlLines[errorLine.lineIndex - 1],\n            errorCharStart: -1,\n            errorLength: -1\n          };\n\n          if (previousLine.html.indexOf('class=\"hljs') === -1) {\n            try {\n              previousLine.html = highlight(d.language, previousLine.text, true).value;\n            } catch (e) {}\n          }\n\n          d.lines.unshift(previousLine);\n        }\n\n        if (errorLine.lineIndex < srcLines.length) {\n          const nextLine: PrintLine = {\n            lineIndex: errorLine.lineIndex + 1,\n            lineNumber: errorLine.lineNumber + 1,\n            text: srcLines[errorLine.lineIndex + 1],\n            html: htmlLines[errorLine.lineIndex + 1],\n            errorCharStart: -1,\n            errorLength: -1\n          };\n\n          if (nextLine.html.indexOf('class=\"hljs') === -1) {\n            try {\n              nextLine.html = highlight(d.language, nextLine.text, true).value;\n            } catch (e) {}\n          }\n\n          d.lines.push(nextLine);\n        }\n\n        diagnostics.push(d);\n\n      } catch (e) {}\n    });\n  }\n\n  return diagnostics;\n}\n\nconst WEBPACK_FILE_REGEX = /\\(webpack:\\/\\/\\/(.*?)\\)/;\n"
  },
  {
    "path": "src/logger/logger-sass.ts",
    "content": "import { BuildContext, Diagnostic, PrintLine } from '../util/interfaces';\nimport { highlight } from '../highlight/highlight';\nimport { Logger } from './logger';\nimport { readFileSync } from 'fs';\nimport { SassError } from 'node-sass';\nimport { splitLineBreaks } from '../util/helpers';\n\n\nexport function runSassDiagnostics(context: BuildContext, sassError: SassError) {\n  if (!sassError) {\n    return [];\n  }\n\n  const d: Diagnostic = {\n    level: 'error',\n    type: 'sass',\n    language: 'scss',\n    header: 'sass error',\n    code: sassError.status && sassError.status.toString(),\n    relFileName: null,\n    absFileName: null,\n    messageText: sassError.message,\n    lines: []\n  };\n\n  if (sassError.file) {\n    d.absFileName = sassError.file;\n    d.relFileName = Logger.formatFileName(context.rootDir, d.absFileName);\n    d.header = Logger.formatHeader('sass', d.absFileName, context.rootDir, sassError.line);\n\n    if (sassError.line > -1) {\n      try {\n        const sourceText = readFileSync(d.absFileName, 'utf8');\n        const srcLines = splitLineBreaks(sourceText);\n        let htmlLines = srcLines;\n\n        try {\n          htmlLines = splitLineBreaks(highlight(d.language, sourceText, true).value);\n        } catch (e) {}\n\n        const errorLine: PrintLine = {\n          lineIndex: sassError.line - 1,\n          lineNumber: sassError.line,\n          text: srcLines[sassError.line - 1],\n          html: htmlLines[sassError.line - 1],\n          errorCharStart: sassError.column,\n          errorLength: 0\n        };\n\n        if (errorLine.html.indexOf('class=\"hljs') === -1) {\n          try {\n            errorLine.html = highlight(d.language, errorLine.text, true).value;\n          } catch (e) {}\n        }\n\n        for (let i = errorLine.errorCharStart; i >= 0; i--) {\n          if (STOP_CHARS.indexOf(errorLine.text.charAt(i)) > -1) {\n            break;\n          }\n          errorLine.errorCharStart = i;\n        }\n\n        for (let j = errorLine.errorCharStart; j <= errorLine.text.length; j++) {\n          if (STOP_CHARS.indexOf(errorLine.text.charAt(j)) > -1) {\n            break;\n          }\n          errorLine.errorLength++;\n        }\n\n        if (errorLine.errorLength === 0 && errorLine.errorCharStart > 0) {\n          errorLine.errorLength = 1;\n          errorLine.errorCharStart--;\n        }\n\n        d.lines.push(errorLine);\n\n        if (errorLine.lineIndex > 0) {\n          const previousLine: PrintLine = {\n            lineIndex: errorLine.lineIndex - 1,\n            lineNumber: errorLine.lineNumber - 1,\n            text: srcLines[errorLine.lineIndex - 1],\n            html: htmlLines[errorLine.lineIndex - 1],\n            errorCharStart: -1,\n            errorLength: -1\n          };\n\n          if (previousLine.html.indexOf('class=\"hljs') === -1) {\n            try {\n              previousLine.html = highlight(d.language, previousLine.text, true).value;\n            } catch (e) {}\n          }\n\n          d.lines.unshift(previousLine);\n        }\n\n        if (errorLine.lineIndex + 1 < srcLines.length) {\n          const nextLine: PrintLine = {\n            lineIndex: errorLine.lineIndex + 1,\n            lineNumber: errorLine.lineNumber + 1,\n            text: srcLines[errorLine.lineIndex + 1],\n            html: htmlLines[errorLine.lineIndex + 1],\n            errorCharStart: -1,\n            errorLength: -1\n          };\n\n          if (nextLine.html.indexOf('class=\"hljs') === -1) {\n            try {\n              nextLine.html = highlight(d.language, nextLine.text, true).value;\n            } catch (e) {}\n          }\n\n          d.lines.push(nextLine);\n        }\n\n      } catch (e) {\n        Logger.debug(`sass loadDiagnostic, ${e}`);\n      }\n    }\n\n  }\n\n  return [d];\n}\n\nconst STOP_CHARS = ['', '\\n', '\\r', '\\t', ' ', ':', ';', ',', '{', '}', '.', '#', '@', '!', '[', ']', '(', ')', '&', '+', '~', '^', '*', '$'];\n"
  },
  {
    "path": "src/logger/logger-tslint.ts",
    "content": "import { IRuleFailurePositionJson, RuleFailure } from 'tslint';\nimport { splitLineBreaks } from '../util/helpers';\nimport { BuildContext, Diagnostic, PrintLine } from '../util/interfaces';\nimport { Logger } from './logger';\n\n\nconst STOP_CHARS = [' ', '=', ',', '.', '\\t', '{', '}', '(', ')', '\"', '\\'', '`', '?', ':', ';', '+', '-', '*', '/', '<', '>', '&', '[', ']', '|'];\n\n\nexport function runTsLintDiagnostics(context: BuildContext, failures: RuleFailure[]) {\n  return failures.map(failure => loadDiagnostic(context, failure));\n}\n\n\nexport function loadDiagnostic(context: BuildContext, failure: RuleFailure) {\n  const start: IRuleFailurePositionJson = failure.getStartPosition()\n    .toJson();\n  const end: IRuleFailurePositionJson = failure.getEndPosition()\n    .toJson();\n  const fileName = failure.getFileName();\n  const sourceFile = failure.getRawLines();\n\n  const d: Diagnostic = {\n    level: 'warn',\n    type: 'tslint',\n    language: 'typescript',\n    absFileName: fileName,\n    relFileName: Logger.formatFileName(context.rootDir, fileName),\n    header: Logger.formatHeader('tslint', fileName, context.rootDir, start.line + 1, end.line + 1),\n    code: failure.getRuleName(),\n    messageText: failure.getFailure(),\n    lines: []\n  };\n\n  if (sourceFile) {\n    const srcLines = splitLineBreaks(sourceFile);\n\n    for (let i = start.line; i <= end.line; i++) {\n      if (srcLines[i].trim().length) {\n        const errorLine: PrintLine = {\n          lineIndex: i,\n          lineNumber: i + 1,\n          text: srcLines[i],\n          html: srcLines[i],\n          errorCharStart: (i === start.line) ? start.character : (i === end.line) ? end.character : -1,\n          errorLength: 0,\n        };\n        for (let j = errorLine.errorCharStart; j < errorLine.text.length; j++) {\n          if (STOP_CHARS.indexOf(errorLine.text.charAt(j)) > -1) {\n            break;\n          }\n          errorLine.errorLength++;\n        }\n\n        if (errorLine.errorLength === 0 && errorLine.errorCharStart > 0) {\n          errorLine.errorLength = 1;\n          errorLine.errorCharStart--;\n        }\n\n        d.lines.push(errorLine);\n      }\n    }\n\n    if (start.line > 0) {\n      const beforeLine: PrintLine = {\n        lineIndex: start.line - 1,\n        lineNumber: start.line,\n        text: srcLines[start.line - 1],\n        html: srcLines[start.line - 1],\n        errorCharStart: -1,\n        errorLength: -1\n      };\n      d.lines.unshift(beforeLine);\n    }\n\n    if (end.line < srcLines.length) {\n      const afterLine: PrintLine = {\n        lineIndex: end.line + 1,\n        lineNumber: end.line + 2,\n        text: srcLines[end.line + 1],\n        html: srcLines[end.line + 1],\n        errorCharStart: -1,\n        errorLength: -1\n      };\n      d.lines.push(afterLine);\n    }\n  }\n\n  return d;\n}\n"
  },
  {
    "path": "src/logger/logger-typescript.ts",
    "content": "import { BuildContext, Diagnostic, PrintLine } from '../util/interfaces';\nimport { Logger } from './logger';\nimport { highlight } from '../highlight/highlight';\nimport { splitLineBreaks } from '../util/helpers';\nimport * as ts from 'typescript';\n\n\n/**\n * Ok, so formatting overkill, we know. But whatever, it makes for great\n * error reporting within a terminal. So, yeah, let's code it up, shall we?\n */\n\nexport function runTypeScriptDiagnostics(context: BuildContext, tsDiagnostics: ts.Diagnostic[]) {\n  return tsDiagnostics.map(tsDiagnostic => {\n    return loadDiagnostic(context, tsDiagnostic);\n  });\n}\n\n\nfunction loadDiagnostic(context: BuildContext, tsDiagnostic: ts.Diagnostic) {\n  const d: Diagnostic = {\n    level: 'error',\n    type: 'typescript',\n    language: 'typescript',\n    header: 'typescript error',\n    code: tsDiagnostic.code.toString(),\n    messageText: ts.flattenDiagnosticMessageText(tsDiagnostic.messageText, '\\n'),\n    relFileName: null,\n    absFileName: null,\n    lines: []\n  };\n\n  if (tsDiagnostic.file && tsDiagnostic.file.getText) {\n    d.absFileName = tsDiagnostic.file.fileName;\n    d.relFileName = Logger.formatFileName(context.rootDir, d.absFileName);\n\n    let sourceText = tsDiagnostic.file.getText();\n    let srcLines = splitLineBreaks(sourceText);\n    let htmlLines = srcLines;\n\n    try {\n      htmlLines = splitLineBreaks(highlight(d.language, sourceText, true).value);\n    } catch (e) {}\n\n    const posData = tsDiagnostic.file.getLineAndCharacterOfPosition(tsDiagnostic.start);\n\n    const errorLine: PrintLine = {\n      lineIndex: posData.line,\n      lineNumber: posData.line + 1,\n      text: srcLines[posData.line],\n      html: htmlLines[posData.line],\n      errorCharStart: posData.character,\n      errorLength: Math.max(tsDiagnostic.length, 1)\n    };\n\n    if (errorLine.html && errorLine.html.indexOf('class=\"hljs') === -1) {\n      try {\n        errorLine.html = highlight(d.language, errorLine.text, true).value;\n      } catch (e) {}\n    }\n\n    d.lines.push(errorLine);\n\n    if (errorLine.errorLength === 0 && errorLine.errorCharStart > 0) {\n      errorLine.errorLength = 1;\n      errorLine.errorCharStart--;\n    }\n\n    d.header =  Logger.formatHeader('typescript', tsDiagnostic.file.fileName, context.rootDir, errorLine.lineNumber);\n\n    if (errorLine.lineIndex > 0) {\n      const previousLine: PrintLine = {\n        lineIndex: errorLine.lineIndex - 1,\n        lineNumber: errorLine.lineNumber - 1,\n        text: srcLines[errorLine.lineIndex - 1],\n        html: htmlLines[errorLine.lineIndex - 1],\n        errorCharStart: -1,\n        errorLength: -1\n      };\n\n      if (previousLine.html && previousLine.html.indexOf('class=\"hljs') === -1) {\n        try {\n          previousLine.html = highlight(d.language, previousLine.text, true).value;\n        } catch (e) {}\n      }\n\n      d.lines.unshift(previousLine);\n    }\n\n    if (errorLine.lineIndex + 1 < srcLines.length) {\n      const nextLine: PrintLine = {\n        lineIndex: errorLine.lineIndex + 1,\n        lineNumber: errorLine.lineNumber + 1,\n        text: srcLines[errorLine.lineIndex + 1],\n        html: htmlLines[errorLine.lineIndex + 1],\n        errorCharStart: -1,\n        errorLength: -1\n      };\n\n      if (nextLine.html && nextLine.html.indexOf('class=\"hljs') === -1) {\n        try {\n          nextLine.html = highlight(d.language, nextLine.text, true).value;\n        } catch (e) {}\n      }\n\n      d.lines.push(nextLine);\n    }\n  }\n\n  return d;\n}\n\n"
  },
  {
    "path": "src/logger/logger.ts",
    "content": "import { BuildError, IgnorableError } from '../util/errors';\nimport { isDebugMode } from '../util/config';\nimport * as chalk from 'chalk';\n\n\nexport class Logger {\n  private start: number;\n  private scope: string;\n\n  constructor(scope: string) {\n    this.start = Date.now();\n    this.scope = scope;\n    let msg = `${scope} started ${chalk.dim('...')}`;\n    if (isDebugMode()) {\n      msg += memoryUsage();\n    }\n    Logger.info(msg);\n  }\n\n  ready(color?: string, bold?: boolean) {\n    this.completed('ready', color, bold);\n  }\n\n  finish(color?: string, bold?: boolean) {\n    this.completed('finished', color, bold);\n  }\n\n  private completed(type: string, color: string, bold: boolean) {\n    const duration = Date.now() - this.start;\n    let time: string;\n\n    if (duration > 1000) {\n      time = 'in ' + (duration / 1000).toFixed(2) + ' s';\n\n    } else {\n      let ms = parseFloat((duration).toFixed(3));\n      if (ms > 0) {\n        time = 'in ' + duration + ' ms';\n      } else {\n        time = 'in less than 1 ms';\n      }\n    }\n\n    let msg = `${this.scope} ${type}`;\n    if (color) {\n      msg = (<any>chalk)[color](msg);\n    }\n    if (bold) {\n      msg = chalk.bold(msg);\n    }\n\n    msg += ' ' + chalk.dim(time);\n\n    if (isDebugMode()) {\n      msg += memoryUsage();\n    }\n\n    Logger.info(msg);\n  }\n\n  fail(err: Error) {\n    if (err) {\n      if (err instanceof IgnorableError) {\n        return;\n      }\n\n      if (err instanceof BuildError) {\n        let failedMsg = `${this.scope} failed`;\n        if (err.message) {\n          failedMsg += `: ${err.message}`;\n        }\n\n        if (!err.hasBeenLogged) {\n          Logger.error(`${failedMsg}`);\n\n          err.hasBeenLogged = true;\n\n          if (err.stack && isDebugMode()) {\n            Logger.debug(err.stack);\n          }\n\n        } else if (isDebugMode()) {\n          Logger.debug(`${failedMsg}`);\n        }\n        return err;\n      }\n    }\n\n    return err;\n  }\n\n  setStartTime(startTime: number) {\n    this.start = startTime;\n  }\n\n  /**\n   * Does not print out a time prefix or color any text. Only prefix\n   * with whitespace so the message is lined up with timestamped logs.\n   */\n  static log(...msg: any[]) {\n    Logger.wordWrap(msg).forEach(line => {\n      console.log(line);\n    });\n  }\n\n  /**\n   * Prints out a dim colored timestamp prefix, with optional color\n   * and bold message.\n   */\n  static info(msg: string, color?: string, bold?: boolean) {\n    const lines = Logger.wordWrap([msg]);\n    if (lines.length) {\n      let prefix = timePrefix();\n      let lineOneMsg = lines[0].substr(prefix.length);\n      if (color) {\n        lineOneMsg = (<any>chalk)[color](lineOneMsg);\n      }\n      if (bold) {\n        lineOneMsg = chalk.bold(lineOneMsg);\n      }\n      lines[0] = chalk.dim(prefix) + lineOneMsg;\n    }\n    lines.forEach((line, lineIndex) => {\n      if (lineIndex > 0) {\n        if (color) {\n          line = (<any>chalk)[color](line);\n        }\n        if (bold) {\n          line = chalk.bold(line);\n        }\n      }\n      console.log(line);\n    });\n  }\n\n  /**\n   * Prints out a yellow colored timestamp prefix.\n   */\n  static warn(...msg: any[]) {\n    const lines = Logger.wordWrap(msg);\n    if (lines.length) {\n      let prefix = timePrefix();\n      lines[0] = prefix + lines[0].substr(prefix.length);\n    }\n    lines.forEach(line => {\n      console.warn(chalk.yellow(line));\n    });\n  }\n\n  /**\n   * Prints out a error colored timestamp prefix.\n   */\n  static error(...msg: any[]) {\n    const lines = Logger.wordWrap(msg);\n    if (lines.length) {\n      let prefix = timePrefix();\n      lines[0] = prefix + lines[0].substr(prefix.length);\n      if (isDebugMode()) {\n        lines[0] += memoryUsage();\n      }\n    }\n    lines.forEach(line => {\n      console.error(chalk.red(line));\n    });\n  }\n\n  static unformattedError(msg: string) {\n    console.error(chalk.red(msg));\n  }\n\n  static unformattedDebug(msg: string) {\n    console.log(chalk.cyan(msg));\n  }\n\n  /**\n   * Prints out a blue colored DEBUG prefix. Only prints out when debug mode.\n   */\n  static debug(...msg: any[]) {\n    if (isDebugMode()) {\n      msg.push(memoryUsage());\n\n      const lines = Logger.wordWrap(msg);\n      if (lines.length) {\n        let prefix = '[ DEBUG! ]';\n        lines[0] = prefix + lines[0].substr(prefix.length);\n      }\n      lines.forEach(line => {\n        console.log(chalk.cyan(line));\n      });\n    }\n  }\n\n  static wordWrap(msg: any[]) {\n    const output: string[] = [];\n\n    const words: any[] = [];\n    msg.forEach(m => {\n      if (m === null) {\n        words.push('null');\n\n      } else if (typeof m === 'undefined') {\n        words.push('undefined');\n\n      } else if (typeof m === 'string') {\n        m.replace(/\\s/gm, ' ').split(' ').forEach(strWord => {\n          if (strWord.trim().length) {\n            words.push(strWord.trim());\n          }\n        });\n\n      } else if (typeof m === 'number' || typeof m === 'boolean') {\n        words.push(m.toString());\n\n      } else if (typeof m === 'function') {\n        words.push(m.toString());\n\n      } else if (Array.isArray(m)) {\n        words.push(() => {\n          return m.toString();\n        });\n\n      } else if (Object(m) === m) {\n        words.push(() => {\n          return m.toString();\n        });\n\n      } else {\n        words.push(m.toString());\n      }\n    });\n\n    let line = Logger.INDENT;\n    words.forEach(word => {\n      if (typeof word === 'function') {\n        if (line.trim().length) {\n          output.push(line);\n        }\n        output.push(word());\n        line = Logger.INDENT;\n\n      } else if (Logger.INDENT.length + word.length > Logger.MAX_LEN) {\n        // word is too long to play nice, just give it its own line\n        if (line.trim().length) {\n          output.push(line);\n        }\n        output.push(Logger.INDENT + word);\n        line = Logger.INDENT;\n\n      } else if ((word.length + line.length) > Logger.MAX_LEN) {\n        // this word would make the line too long\n        // print the line now, then start a new one\n        output.push(line);\n        line = Logger.INDENT + word + ' ';\n\n      } else {\n        line += word + ' ';\n      }\n    });\n    if (line.trim().length) {\n      output.push(line);\n    }\n    return output;\n  }\n\n\n  static formatFileName(rootDir: string, fileName: string) {\n    fileName = fileName.replace(rootDir, '');\n    if (/\\/|\\\\/.test(fileName.charAt(0))) {\n      fileName = fileName.substr(1);\n    }\n    if (fileName.length > 80) {\n      fileName = '...' + fileName.substr(fileName.length - 80);\n    }\n    return fileName;\n  }\n\n\n  static formatHeader(type: string, fileName: string, rootDir: string, startLineNumber: number = null, endLineNumber: number = null) {\n    let header = `${type}: ${Logger.formatFileName(rootDir, fileName)}`;\n\n    if (startLineNumber !== null && startLineNumber > 0) {\n      if (endLineNumber !== null && endLineNumber > startLineNumber) {\n        header += `, lines: ${startLineNumber} - ${endLineNumber}`;\n      } else {\n        header += `, line: ${startLineNumber}`;\n      }\n    }\n\n    return header;\n  }\n\n\n  static newLine() {\n    console.log('');\n  }\n\n  static INDENT = '            ';\n  static MAX_LEN = 120;\n\n}\n\n\nfunction timePrefix() {\n  const date = new Date();\n  return '[' + ('0' + date.getHours()).slice(-2) + ':' + ('0' + date.getMinutes()).slice(-2) + ':' + ('0' + date.getSeconds()).slice(-2) + ']';\n}\n\n\nfunction memoryUsage() {\n  return chalk.dim(` MEM: ${(process.memoryUsage().rss / 1000000).toFixed(1)}MB`);\n}\n"
  },
  {
    "path": "src/minify.ts",
    "content": "import * as Constants from './util/constants';\nimport { getBooleanPropertyValue } from './util/helpers';\nimport { BuildContext } from './util/interfaces';\nimport { cleancss } from './cleancss';\nimport { Logger } from './logger/logger';\nimport { uglifyjs } from './uglifyjs';\n\n\nexport function minify(context: BuildContext) {\n\n  const logger = new Logger('minify');\n\n  return minifyWorker(context)\n    .then(() => {\n      logger.finish();\n    })\n    .catch(err => {\n      throw logger.fail(err);\n    });\n}\n\n\nfunction minifyWorker(context: BuildContext) {\n  // both css and js minify can run at the same time\n  return Promise.all([\n    minifyJs(context),\n    minifyCss(context)\n  ]);\n}\n\nexport function minifyJs(context: BuildContext): Promise<any> {\n  return runUglify(context);\n}\n\nfunction runUglify(context: BuildContext) {\n  // uglify cannot handle ES2015, so convert it to ES5 before minifying (if needed)\n  return uglifyjs(context);\n}\n\nexport function minifyCss(context: BuildContext) {\n  return cleancss(context);\n}\n"
  },
  {
    "path": "src/mocks/mock-helpers.ts",
    "content": "import { BuildContext } from '../util/interfaces';\n\nexport function setContext(context: BuildContext) {\n}\n\nexport function readFileAsync(filePath: string) {\n  return Promise.resolve();\n}\n\n"
  },
  {
    "path": "src/ngc.ts",
    "content": "import { RawSourceMap, SourceMapConsumer, SourceMapGenerator } from 'source-map';\n\nimport { runAot } from './aot/aot-compiler';\nimport {\n  convertDeepLinkConfigEntriesToString,\n  getUpdatedAppNgModuleContentWithDeepLinkConfig,\n  filterTypescriptFilesForDeepLinks,\n  hasExistingDeepLinkConfig,\n  purgeDeepLinkDecorator\n} from './deep-linking/util';\nimport { Logger } from './logger/logger';\nimport { getUserConfigFile} from './util/config';\nimport * as Constants from './util/constants';\nimport { changeExtension, getBooleanPropertyValue, getParsedDeepLinkConfig, getStringPropertyValue } from './util/helpers';\nimport { BuildContext, TaskInfo } from './util/interfaces';\n\nexport function ngc(context: BuildContext, configFile?: string) {\n  configFile = getUserConfigFile(context, taskInfo, configFile);\n\n  const logger = new Logger('ngc');\n\n  return ngcWorker(context, configFile)\n    .then(() => {\n      logger.finish();\n    })\n    .catch(err => {\n      throw logger.fail(err);\n    });\n}\n\nexport function ngcWorker(context: BuildContext, configFile: string): Promise<any> {\n  return transformTsForDeepLinking(context).then(() => {\n    return runNgc(context, configFile);\n  });\n}\n\nexport function runNgc(context: BuildContext, configFile: string): Promise<any> {\n  return runAot(context, { entryPoint: process.env[Constants.ENV_APP_ENTRY_POINT],\n    rootDir: context.rootDir,\n    tsConfigPath: process.env[Constants.ENV_TS_CONFIG],\n    appNgModuleClass: process.env[Constants.ENV_APP_NG_MODULE_CLASS],\n    appNgModulePath: process.env[Constants.ENV_APP_NG_MODULE_PATH]\n  });\n}\n\nexport function transformTsForDeepLinking(context: BuildContext) {\n  if (getBooleanPropertyValue(Constants.ENV_PARSE_DEEPLINKS)) {\n    const tsFiles = filterTypescriptFilesForDeepLinks(context.fileCache);\n    tsFiles.forEach(tsFile => {\n      tsFile.content = purgeDeepLinkDecorator(tsFile.content);\n    });\n    const tsFile = context.fileCache.get(getStringPropertyValue(Constants.ENV_APP_NG_MODULE_PATH));\n    if (!hasExistingDeepLinkConfig(tsFile.path, tsFile.content)) {\n      const deepLinkString = convertDeepLinkConfigEntriesToString(getParsedDeepLinkConfig());\n      tsFile.content = getUpdatedAppNgModuleContentWithDeepLinkConfig(tsFile.path, tsFile.content, deepLinkString);\n    }\n  }\n  return Promise.resolve();\n}\n\nconst taskInfo: TaskInfo = {\n  fullArg: '--ngc',\n  shortArg: '-n',\n  envVar: 'IONIC_NGC',\n  packageConfig: 'ionic_ngc',\n  defaultConfigFile: null\n};\n"
  },
  {
    "path": "src/optimization/remove-unused-fonts.spec.ts",
    "content": "import { join } from 'path';\n\nimport { removeUnusedFonts } from './remove-unused-fonts';\nimport * as helpers from '../util/helpers';\n\ndescribe('Remove Fonts', () => {\n  describe('removeUnusedFonts', () => {\n    it('should not purge any fonts when target is not cordova', () => {\n      const fakeFontDirPath = join(process.cwd(), 'www', 'assets', 'fonts');\n      spyOn(helpers, helpers.getStringPropertyValue.name).and.returnValue(fakeFontDirPath);\n      spyOn(helpers, helpers.readDirAsync.name).and.returnValue(Promise.resolve(getMockFontDirData()));\n      spyOn(helpers, helpers.unlinkAsync.name).and.returnValue(Promise.resolve());\n\n      return removeUnusedFonts({ target: 'notCordova', platform: 'web' }).then(() => {\n        expect(helpers.getStringPropertyValue).toHaveBeenCalled();\n        expect(helpers.readDirAsync).toHaveBeenCalledWith(fakeFontDirPath);\n        expect(helpers.unlinkAsync).not.toHaveBeenCalled();\n      });\n    });\n\n    it('should purge all non-woffs for ionicons and roboto, and then all of noto-sans for ios', () => {\n      const fakeFontDirPath = join(process.cwd(), 'www', 'assets', 'fonts');\n      spyOn(helpers, helpers.getStringPropertyValue.name).and.returnValue(fakeFontDirPath);\n      spyOn(helpers, helpers.readDirAsync.name).and.returnValue(Promise.resolve(getMockFontDirData()));\n      const unlinkSpy = spyOn(helpers, helpers.unlinkAsync.name).and.returnValue(Promise.resolve());\n\n      return removeUnusedFonts({ target: 'cordova', platform: 'ios' }).then(() => {\n        expect(helpers.readDirAsync).toHaveBeenCalledWith(fakeFontDirPath);\n        expect(unlinkSpy.calls.all()[0].args[0]).toEqual(join(fakeFontDirPath, 'ionicons.eot'));\n        expect(unlinkSpy.calls.all()[1].args[0]).toEqual(join(fakeFontDirPath, 'ionicons.scss'));\n        expect(unlinkSpy.calls.all()[2].args[0]).toEqual(join(fakeFontDirPath, 'ionicons.svg'));\n        expect(unlinkSpy.calls.all()[3].args[0]).toEqual(join(fakeFontDirPath, 'ionicons.ttf'));\n        expect(unlinkSpy.calls.all()[4].args[0]).toEqual(join(fakeFontDirPath, 'noto-sans-bold.ttf'));\n        expect(unlinkSpy.calls.all()[5].args[0]).toEqual(join(fakeFontDirPath, 'noto-sans-bold.woff'));\n        expect(unlinkSpy.calls.all()[6].args[0]).toEqual(join(fakeFontDirPath, 'noto-sans-regular.ttf'));\n        expect(unlinkSpy.calls.all()[7].args[0]).toEqual(join(fakeFontDirPath, 'noto-sans-regular.woff'));\n        expect(unlinkSpy.calls.all()[8].args[0]).toEqual(join(fakeFontDirPath, 'noto-sans.scss'));\n\n        expect(unlinkSpy.calls.all()[9].args[0]).toEqual(join(fakeFontDirPath, 'roboto-bold.ttf'));\n        expect(unlinkSpy.calls.all()[10].args[0]).toEqual(join(fakeFontDirPath, 'roboto-light.ttf'));\n        expect(unlinkSpy.calls.all()[11].args[0]).toEqual(join(fakeFontDirPath, 'roboto-medium.ttf'));\n        expect(unlinkSpy.calls.all()[12].args[0]).toEqual(join(fakeFontDirPath, 'roboto-regular.ttf'));\n        expect(unlinkSpy.calls.all()[13].args[0]).toEqual(join(fakeFontDirPath, 'roboto.scss'));\n      });\n    });\n\n    it('should purge all non-woffs for ionicons, all of roboto and noto-sans for android', () => {\n      const fakeFontDirPath = join(process.cwd(), 'www', 'assets', 'fonts');\n      spyOn(helpers, helpers.getStringPropertyValue.name).and.returnValue(fakeFontDirPath);\n      spyOn(helpers, helpers.readDirAsync.name).and.returnValue(Promise.resolve(getMockFontDirData()));\n      const unlinkSpy = spyOn(helpers, helpers.unlinkAsync.name).and.returnValue(Promise.resolve());\n\n      return removeUnusedFonts({ target: 'cordova', platform: 'android' }).then(() => {\n        expect(helpers.readDirAsync).toHaveBeenCalledWith(fakeFontDirPath);\n        expect(unlinkSpy.calls.all()[0].args[0]).toEqual(join(fakeFontDirPath, 'ionicons.eot'));\n        expect(unlinkSpy.calls.all()[1].args[0]).toEqual(join(fakeFontDirPath, 'ionicons.scss'));\n        expect(unlinkSpy.calls.all()[2].args[0]).toEqual(join(fakeFontDirPath, 'ionicons.svg'));\n        expect(unlinkSpy.calls.all()[3].args[0]).toEqual(join(fakeFontDirPath, 'ionicons.ttf'));\n        expect(unlinkSpy.calls.all()[4].args[0]).toEqual(join(fakeFontDirPath, 'noto-sans-bold.ttf'));\n        expect(unlinkSpy.calls.all()[5].args[0]).toEqual(join(fakeFontDirPath, 'noto-sans-bold.woff'));\n        expect(unlinkSpy.calls.all()[6].args[0]).toEqual(join(fakeFontDirPath, 'noto-sans-regular.ttf'));\n        expect(unlinkSpy.calls.all()[7].args[0]).toEqual(join(fakeFontDirPath, 'noto-sans-regular.woff'));\n        expect(unlinkSpy.calls.all()[8].args[0]).toEqual(join(fakeFontDirPath, 'noto-sans.scss'));\n\n        expect(unlinkSpy.calls.all()[9].args[0]).toEqual(join(fakeFontDirPath, 'roboto-bold.ttf'));\n        expect(unlinkSpy.calls.all()[10].args[0]).toEqual(join(fakeFontDirPath, 'roboto-bold.woff'));\n        expect(unlinkSpy.calls.all()[11].args[0]).toEqual(join(fakeFontDirPath, 'roboto-bold.woff2'));\n        expect(unlinkSpy.calls.all()[12].args[0]).toEqual(join(fakeFontDirPath, 'roboto-light.ttf'));\n        expect(unlinkSpy.calls.all()[13].args[0]).toEqual(join(fakeFontDirPath, 'roboto-light.woff'));\n        expect(unlinkSpy.calls.all()[14].args[0]).toEqual(join(fakeFontDirPath, 'roboto-light.woff2'));\n        expect(unlinkSpy.calls.all()[15].args[0]).toEqual(join(fakeFontDirPath, 'roboto-medium.ttf'));\n        expect(unlinkSpy.calls.all()[16].args[0]).toEqual(join(fakeFontDirPath, 'roboto-medium.woff'));\n        expect(unlinkSpy.calls.all()[17].args[0]).toEqual(join(fakeFontDirPath, 'roboto-medium.woff2'));\n        expect(unlinkSpy.calls.all()[18].args[0]).toEqual(join(fakeFontDirPath, 'roboto-regular.ttf'));\n        expect(unlinkSpy.calls.all()[19].args[0]).toEqual(join(fakeFontDirPath, 'roboto-regular.woff'));\n        expect(unlinkSpy.calls.all()[20].args[0]).toEqual(join(fakeFontDirPath, 'roboto-regular.woff2'));\n        expect(unlinkSpy.calls.all()[21].args[0]).toEqual(join(fakeFontDirPath, 'roboto.scss'));\n\n      });\n    });\n\n    it('should purge all non-woffs for ionicons, all of roboto and noto-sans for windows', () => {\n      const fakeFontDirPath = join(process.cwd(), 'www', 'assets', 'fonts');\n      spyOn(helpers, helpers.getStringPropertyValue.name).and.returnValue(fakeFontDirPath);\n      spyOn(helpers, helpers.readDirAsync.name).and.returnValue(Promise.resolve(getMockFontDirData()));\n      const unlinkSpy = spyOn(helpers, helpers.unlinkAsync.name).and.returnValue(Promise.resolve());\n\n      return removeUnusedFonts({ target: 'cordova', platform: 'windows' }).then(() => {\n        expect(helpers.readDirAsync).toHaveBeenCalledWith(fakeFontDirPath);\n        expect(helpers.unlinkAsync).not.toHaveBeenCalled();\n      });\n    });\n  });\n});\n\nfunction getMockFontDirData() {\n  return [\n    'ionicons.eot',\n    'ionicons.scss',\n    'ionicons.svg',\n    'ionicons.ttf',\n    'ionicons.woff',\n    'ionicons.woff2',\n    'noto-sans-bold.ttf',\n    'noto-sans-bold.woff',\n    'noto-sans-regular.ttf',\n    'noto-sans-regular.woff',\n    'noto-sans.scss',\n    'roboto-bold.ttf',\n    'roboto-bold.woff',\n    'roboto-bold.woff2',\n    'roboto-light.ttf',\n    'roboto-light.woff',\n    'roboto-light.woff2',\n    'roboto-medium.ttf',\n    'roboto-medium.woff',\n    'roboto-medium.woff2',\n    'roboto-regular.ttf',\n    'roboto-regular.woff',\n    'roboto-regular.woff2',\n    'roboto.scss',\n    'my-custom-font.eot',\n    'my-custom-font.scss',\n    'my-custom-font.svg',\n    'my-custom-font.ttf',\n    'my-custom-font.woff',\n    'my-custom-font.woff2'\n  ];\n}\n"
  },
  {
    "path": "src/optimization/remove-unused-fonts.ts",
    "content": "import { extname, join } from 'path';\n\nimport { Logger } from '../logger/logger';\nimport * as Constants from '../util/constants';\nimport { getStringPropertyValue, readDirAsync, unlinkAsync } from '../util/helpers';\nimport { BuildContext } from '../util/interfaces';\n\n\n// For webapps, we pretty much need all fonts to be available because\n// the web server deployment never knows which browser/platform is\n// opening the app. Additionally, webapps will request fonts on-demand,\n// so having them all sit in the www/assets/fonts directory doesn’t\n// hurt anything if it’s never being requested.\n\n// However, with Cordova, the entire directory gets bundled and\n// shipped in the ipa/apk, but we also know exactly which platform\n// is opening the webapp. For this reason we can safely delete font\n// files we know would never be opened by the platform. So app-scripts\n// will continue to copy all font files over, but the cordova build\n// process would delete those we know are useless and just taking up\n// space. End goal is that the Cordova ipa/apk filesize is smaller.\n\n// Font Format Support:\n// ttf: http://caniuse.com/#feat=ttf\n// woff: http://caniuse.com/#feat=woff\n// woff2: http://caniuse.com/#feat=woff2\nexport function removeUnusedFonts(context: BuildContext): Promise<any> {\n  const fontDir = getStringPropertyValue(Constants.ENV_VAR_FONTS_DIR);\n  return readDirAsync(fontDir).then((fileNames: string[]) => {\n    fileNames = fileNames.sort();\n    const toPurge = getFontFileNamesToPurge(context.target, context.platform, fileNames);\n    const fullPaths = toPurge.map(fileName => join(fontDir, fileName));\n    const promises = fullPaths.map(fullPath => unlinkAsync(fullPath));\n    return Promise.all(promises);\n  });\n}\n\nexport function getFontFileNamesToPurge(target: string, platform: string, fileNames: string[]): string[] {\n  if (target !== Constants.CORDOVA) {\n    return [];\n  }\n  const filesToDelete = new Set<string>();\n  for (const fileName of fileNames) {\n    if (platform === 'android') {\n      // remove noto-sans, roboto, and non-woff ionicons\n      if (fileName.startsWith('noto-sans') || fileName.startsWith('roboto') || (isIonicons(fileName) && !isWoof(fileName))) {\n        filesToDelete.add(fileName);\n      }\n    } else if (platform === 'ios') {\n      // remove noto-sans, non-woff ionicons\n      if (fileName.startsWith('noto-sans') || (fileName.startsWith('roboto') && !isWoof(fileName)) || (isIonicons(fileName) && !isWoof(fileName))) {\n        filesToDelete.add(fileName);\n      }\n    }\n    // for now don't bother deleting anything for windows, need to get some info first\n\n  }\n  return Array.from(filesToDelete);\n}\n\nfunction isIonicons(fileName: string) {\n  return fileName.startsWith('ionicons');\n}\n\n// woof woof\nfunction isWoof(fileName: string) {\n  return extname(fileName) === '.woff' || extname(fileName) === '.woff2';\n}\n"
  },
  {
    "path": "src/postprocess.ts",
    "content": "import { basename, dirname, join, relative } from 'path';\nimport { emptyDirSync, mkdirpSync, writeFileSync } from 'fs-extra';\n\nimport { Logger } from './logger/logger';\nimport * as Constants from './util/constants';\nimport { getBooleanPropertyValue } from './util/helpers';\nimport { BuildContext } from './util/interfaces';\nimport { updateIndexHtml } from './core/inject-scripts';\nimport { purgeSourceMapsIfNeeded } from './util/source-maps';\nimport { removeUnusedFonts } from './optimization/remove-unused-fonts';\n\n\nexport function postprocess(context: BuildContext) {\n  const logger = new Logger(`postprocess`);\n  return postprocessWorker(context).then(() => {\n      logger.finish();\n    })\n    .catch((err: Error) => {\n      throw logger.fail(err);\n    });\n}\n\n\nfunction postprocessWorker(context: BuildContext) {\n  const promises: Promise<any>[] = [];\n  promises.push(purgeSourceMapsIfNeeded(context));\n  promises.push(updateIndexHtml(context));\n\n  if (getBooleanPropertyValue(Constants.ENV_AOT_WRITE_TO_DISK)) {\n    promises.push(writeFilesToDisk(context));\n  }\n\n  if (context.optimizeJs && getBooleanPropertyValue(Constants.ENV_PURGE_UNUSED_FONTS)) {\n    promises.push(removeUnusedFonts(context));\n  }\n\n  return Promise.all(promises);\n}\n\nexport function writeFilesToDisk(context: BuildContext) {\n  emptyDirSync(context.tmpDir);\n  const files = context.fileCache.getAll();\n  files.forEach(file => {\n    const dirName = dirname(file.path);\n    const relativePath = relative(process.cwd(), dirName);\n    const tmpPath = join(context.tmpDir, relativePath);\n    const fileName = basename(file.path);\n    const fileToWrite = join(tmpPath, fileName);\n    mkdirpSync(tmpPath);\n    writeFileSync(fileToWrite, file.content);\n  });\n  return Promise.resolve();\n}\n"
  },
  {
    "path": "src/preprocess.spec.ts",
    "content": "import { join } from 'path';\nimport * as preprocess from './preprocess';\nimport * as deeplink from './deep-linking';\nimport * as helpers from './util/helpers';\nimport * as globUtil from './util/glob-util';\n\ndescribe('Preprocess Task', () => {\n  describe('preprocess', () => {\n    it('should call deepLink but not write files to disk', () => {\n      // arrange\n      const context = {\n        optimizeJs: false\n      };\n\n      const mockDirName = join('some', 'fake', 'dir');\n      const mockGlobResults = [];\n      mockGlobResults.push({ absolutePath: mockDirName});\n      mockGlobResults.push({ absolutePath: mockDirName + '2'});\n      spyOn(deeplink, deeplink.deepLinking.name).and.returnValue(Promise.resolve());\n      spyOn(helpers, helpers.getBooleanPropertyValue.name).and.returnValue(false);\n      spyOn(helpers, helpers.getStringPropertyValue.name).and.returnValue(mockDirName);\n      spyOn(globUtil, globUtil.globAll.name).and.returnValue(Promise.resolve(mockGlobResults));\n\n      // act\n      return preprocess.preprocess(context);\n    });\n  });\n});\n"
  },
  {
    "path": "src/preprocess.ts",
    "content": "import { join } from 'path';\n\nimport { Logger } from './logger/logger';\nimport * as Constants from './util/constants';\nimport { BuildError } from './util/errors';\nimport { GlobResult, globAll } from './util/glob-util';\nimport { getBooleanPropertyValue, getStringPropertyValue } from './util/helpers';\nimport { BuildContext, ChangedFile } from './util/interfaces';\nimport { bundleCoreComponents } from './core/bundle-components';\n\n\nexport function preprocess(context: BuildContext) {\n  const logger = new Logger(`preprocess`);\n  return preprocessWorker(context).then(() => {\n      logger.finish();\n    })\n    .catch((err: Error) => {\n      const error = new BuildError(err.message);\n      error.isFatal = true;\n      throw logger.fail(error);\n    });\n}\n\nfunction preprocessWorker(context: BuildContext) {\n  const bundlePromise = bundleCoreComponents(context);\n\n  return Promise.all([bundlePromise]);\n}\n\nexport function preprocessUpdate(changedFiles: ChangedFile[], context: BuildContext) {\n  const promises: Promise<any>[] = [];\n\n  if (changedFiles.some(cf => cf.ext === '.scss')) {\n    promises.push(bundleCoreComponents(context));\n  }\n\n  return Promise.all(promises);\n}\n"
  },
  {
    "path": "src/sass.ts",
    "content": "import { basename, dirname, join, sep } from 'path';\nimport { BuildContext, BuildState, ChangedFile, TaskInfo } from './util/interfaces';\nimport { BuildError } from './util/errors';\nimport { bundle } from './bundle';\nimport { ensureDirSync, readdirSync, writeFile } from 'fs-extra';\nimport { fillConfigDefaults, getUserConfigFile, replacePathVars } from './util/config';\nimport { Logger } from './logger/logger';\nimport { runSassDiagnostics } from './logger/logger-sass';\nimport { printDiagnostics, clearDiagnostics, DiagnosticsType } from './logger/logger-diagnostics';\nimport { SassError, render as nodeSassRender, Result } from 'node-sass';\nimport * as postcss from 'postcss';\nimport * as autoprefixer from 'autoprefixer';\n\n\nexport function sass(context: BuildContext, configFile?: string) {\n  configFile = getUserConfigFile(context, taskInfo, configFile);\n\n  const logger = new Logger('sass');\n\n  return sassWorker(context, configFile)\n    .then(outFile => {\n      context.sassState = BuildState.SuccessfulBuild;\n      logger.finish();\n      return outFile;\n    })\n    .catch(err => {\n      context.sassState = BuildState.RequiresBuild;\n      throw logger.fail(err);\n    });\n}\n\n\nexport function sassUpdate(changedFiles: ChangedFile[], context: BuildContext) {\n  const configFile = getUserConfigFile(context, taskInfo, null);\n\n  const logger = new Logger('sass update');\n\n  return sassWorker(context, configFile)\n    .then(outFile => {\n      context.sassState = BuildState.SuccessfulBuild;\n      logger.finish();\n      return outFile;\n    })\n    .catch(err => {\n      context.sassState = BuildState.RequiresBuild;\n      throw logger.fail(err);\n    });\n}\n\n\nexport function sassWorker(context: BuildContext, configFile: string) {\n  const sassConfig: SassConfig = getSassConfig(context, configFile);\n\n  const bundlePromise: Promise<any>[] = [];\n  if (!context.moduleFiles && !sassConfig.file) {\n    // sass must always have a list of all the used module files\n    // so ensure we bundle if moduleFiles are currently unknown\n    bundlePromise.push(bundle(context));\n  }\n\n  return Promise.all(bundlePromise).then(() => {\n    clearDiagnostics(context, DiagnosticsType.Sass);\n\n    // where the final css output file is saved\n    if (!sassConfig.outFile) {\n      sassConfig.outFile = join(context.buildDir, sassConfig.outputFilename);\n    }\n    Logger.debug(`sass outFile: ${sassConfig.outFile}`);\n\n    // import paths where the sass compiler will look for imports\n    sassConfig.includePaths.unshift(join(context.srcDir));\n    Logger.debug(`sass includePaths: ${sassConfig.includePaths}`);\n\n    // sass import sorting algorithms incase there was something to tweak\n    sassConfig.sortComponentPathsFn = (sassConfig.sortComponentPathsFn || defaultSortComponentPathsFn);\n    sassConfig.sortComponentFilesFn = (sassConfig.sortComponentFilesFn || defaultSortComponentFilesFn);\n\n    if (!sassConfig.file) {\n      // if the sass config was not given an input file, then\n      // we're going to dynamically generate the sass data by\n      // scanning through all the components included in the bundle\n      // and generate the sass on the fly\n      generateSassData(context, sassConfig);\n    } else {\n      sassConfig.file = replacePathVars(context, sassConfig.file);\n    }\n\n    return render(context, sassConfig);\n  });\n}\n\nexport function getSassConfig(context: BuildContext, configFile: string): SassConfig {\n  configFile = getUserConfigFile(context, taskInfo, configFile);\n  return fillConfigDefaults(configFile, taskInfo.defaultConfigFile);\n}\n\nfunction generateSassData(context: BuildContext, sassConfig: SassConfig) {\n  /**\n   * 1) Import user sass variables first since user variables\n   *    should have precedence over default library variables.\n   * 2) Import all library sass files next since library css should\n   *    be before user css, and potentially have library css easily\n   *    overridden by user css selectors which come after the\n   *    library's in the same file.\n   * 3) Import the user's css last since we want the user's css to\n   *    potentially easily override library css with the same\n   *    css specificity.\n   */\n\n  const moduleDirectories: string[] = [];\n  if (context.moduleFiles) {\n    context.moduleFiles.forEach(moduleFile => {\n      const moduleDirectory = dirname(moduleFile);\n      if (moduleDirectories.indexOf(moduleDirectory) < 0) {\n        moduleDirectories.push(moduleDirectory);\n      }\n    });\n  }\n\n  Logger.debug(`sass moduleDirectories: ${moduleDirectories.length}`);\n\n  // gather a list of all the sass variable files that should be used\n  // these variable files will be the first imports\n  const userSassVariableFiles = sassConfig.variableSassFiles.map(f => {\n    return replacePathVars(context, f);\n  });\n\n  // gather a list of all the sass files that are next to components we're bundling\n  const componentSassFiles = getComponentSassFiles(moduleDirectories, context, sassConfig);\n\n  Logger.debug(`sass userSassVariableFiles: ${userSassVariableFiles.length}`);\n  Logger.debug(`sass componentSassFiles: ${componentSassFiles.length}`);\n\n  const sassImports = userSassVariableFiles.concat(componentSassFiles).map(sassFile => '\"' + sassFile.replace(/\\\\/g, '\\\\\\\\') + '\"');\n\n  if (sassImports.length) {\n    sassConfig.data = `@charset \"UTF-8\"; @import ${sassImports.join(',')};`;\n  }\n}\n\n\nfunction getComponentSassFiles(moduleDirectories: string[], context: BuildContext, sassConfig: SassConfig) {\n  const collectedSassFiles: string[] = [];\n  const componentDirectories = getComponentDirectories(moduleDirectories, sassConfig);\n\n  // sort all components with the library components being first\n  // and user components coming last, so it's easier for user css\n  // to override library css with the same specificity\n  const sortedComponentPaths = componentDirectories.sort(sassConfig.sortComponentPathsFn);\n\n  sortedComponentPaths.forEach(componentPath => {\n    addComponentSassFiles(componentPath, collectedSassFiles, context, sassConfig);\n  });\n\n  return collectedSassFiles;\n}\n\n\nfunction addComponentSassFiles(componentPath: string, collectedSassFiles: string[], context: BuildContext, sassConfig: SassConfig) {\n  let siblingFiles = getSiblingSassFiles(componentPath, sassConfig);\n\n  if (!siblingFiles.length && componentPath.indexOf(sep + 'node_modules') === -1) {\n\n    // if we didn't find anything, see if this module is mapped to another directory\n    for (const k in sassConfig.directoryMaps) {\n      if (sassConfig.directoryMaps.hasOwnProperty(k)) {\n        var actualDirectory = replacePathVars(context, k);\n        var mappedDirectory = replacePathVars(context, sassConfig.directoryMaps[k]);\n\n        componentPath = componentPath.replace(actualDirectory, mappedDirectory);\n\n        siblingFiles = getSiblingSassFiles(componentPath, sassConfig);\n        if (siblingFiles.length) {\n          break;\n        }\n      }\n    }\n  }\n\n  if (siblingFiles.length) {\n    siblingFiles = siblingFiles.sort(sassConfig.sortComponentFilesFn);\n\n    siblingFiles.forEach(componentFile => {\n      collectedSassFiles.push(componentFile);\n    });\n  }\n}\n\n\nfunction getSiblingSassFiles(componentPath: string, sassConfig: SassConfig) {\n  try {\n    return readdirSync(componentPath).filter(f => {\n      return isValidSassFile(f, sassConfig);\n    }).map(f => {\n      return join(componentPath, f);\n    });\n  } catch (ex) {\n    // it's an invalid path\n    return [];\n  }\n}\n\n\nfunction isValidSassFile(filename: string, sassConfig: SassConfig) {\n  for (var i = 0; i < sassConfig.includeFiles.length; i++) {\n    if (sassConfig.includeFiles[i].test(filename)) {\n      // filename passes the test to be included\n      for (var j = 0; j < sassConfig.excludeFiles.length; j++) {\n        if (sassConfig.excludeFiles[j].test(filename)) {\n          // however, it also passed the test that it should be excluded\n          Logger.debug(`sass excluded: ${filename}`);\n          return false;\n        }\n      }\n      return true;\n    }\n  }\n  return false;\n}\n\n\nfunction getComponentDirectories(moduleDirectories: string[], sassConfig: SassConfig) {\n  // filter out module directories we know wouldn't have sibling component sass file\n  // just a way to reduce the amount of lookups to be done later\n  return moduleDirectories.filter(moduleDirectory => {\n    // normalize this directory is using / between directories\n    moduleDirectory = moduleDirectory.replace(/\\\\/g, '/');\n\n    for (var i = 0; i < sassConfig.excludeModules.length; i++) {\n      if (moduleDirectory.indexOf('/node_modules/' + sassConfig.excludeModules[i] + '/') > -1) {\n        return false;\n      }\n    }\n    return true;\n  });\n}\n\n\nfunction render(context: BuildContext, sassConfig: SassConfig): Promise<string> {\n  return new Promise((resolve, reject) => {\n\n    sassConfig.omitSourceMapUrl = false;\n\n    if (sassConfig.sourceMap) {\n      sassConfig.sourceMapContents = true;\n    }\n\n    nodeSassRender(sassConfig, (sassError: SassError, sassResult: Result) => {\n      const diagnostics = runSassDiagnostics(context, sassError);\n\n      if (diagnostics.length) {\n        printDiagnostics(context, DiagnosticsType.Sass, diagnostics, true, true);\n        // sass render error :(\n        reject(new BuildError('Failed to render sass to css'));\n\n      } else {\n        // sass render success :)\n        renderSassSuccess(context, sassResult, sassConfig).then(outFile => {\n          resolve(outFile);\n\n        }).catch(err => {\n          reject(new BuildError(err));\n        });\n      }\n    });\n  });\n}\n\n\nfunction renderSassSuccess(context: BuildContext, sassResult: Result, sassConfig: SassConfig): Promise<string> {\n  if (sassConfig.autoprefixer) {\n    // with autoprefixer\n\n    let autoPrefixerMapOptions: any = false;\n    if (sassConfig.sourceMap) {\n      autoPrefixerMapOptions = {\n        inline: false,\n        prev: generateSourceMaps(sassResult, sassConfig)\n      };\n    }\n\n    const postcssOptions: any = {\n      to: basename(sassConfig.outFile),\n      map: autoPrefixerMapOptions,\n      from: void 0\n    };\n\n    Logger.debug(`sass, start postcss/autoprefixer`);\n\n    let postCssPlugins = [autoprefixer(sassConfig.autoprefixer)];\n\n    if (sassConfig.postCssPlugins) {\n      postCssPlugins = [\n        ...sassConfig.postCssPlugins,\n        ...postCssPlugins\n      ];\n    }\n\n    return postcss(postCssPlugins)\n      .process(sassResult.css, postcssOptions).then((postCssResult: any) => {\n        postCssResult.warnings().forEach((warn: any) => {\n          Logger.warn(warn.toString());\n        });\n\n        let apMapResult: SassMap = null;\n        if (sassConfig.sourceMap && postCssResult.map) {\n          Logger.debug(`sass, parse postCssResult.map`);\n          apMapResult = generateSourceMaps(postCssResult, sassConfig);\n        }\n\n        Logger.debug(`sass: postcss/autoprefixer completed`);\n        return writeOutput(context, sassConfig, postCssResult.css, apMapResult);\n      });\n  }\n\n  // without autoprefixer\n  let sassMapResult: SassMap = generateSourceMaps(sassResult, sassConfig);\n\n  return writeOutput(context, sassConfig, sassResult.css.toString(), sassMapResult);\n}\n\n\nfunction generateSourceMaps(sassResult: Result, sassConfig: SassConfig): SassMap {\n  // this can be async and nothing needs to wait on it\n\n  // build Source Maps!\n  if (sassResult.map) {\n    Logger.debug(`sass, generateSourceMaps`);\n\n    // transform map into JSON\n    const sassMap: SassMap = JSON.parse(sassResult.map.toString());\n\n    // grab the stdout and transform it into stdin\n    const sassMapFile = sassMap.file.replace(/^stdout$/, 'stdin');\n\n    // grab the base file name that's being worked on\n    const sassFileSrc = sassConfig.outFile;\n\n    // grab the path portion of the file that's being worked on\n    const sassFileSrcPath = dirname(sassFileSrc);\n    if (sassFileSrcPath) {\n      // prepend the path to all files in the sources array except the file that's being worked on\n      const sourceFileIndex = sassMap.sources.indexOf(sassMapFile);\n      sassMap.sources = sassMap.sources.map((source, index) => {\n        return (index === sourceFileIndex) ? source : join(sassFileSrcPath, source);\n      });\n    }\n\n    // remove 'stdin' from souces and replace with filenames!\n    sassMap.sources = sassMap.sources.filter(src => {\n      if (src !== 'stdin') {\n        return src;\n      }\n    });\n    return sassMap;\n  }\n}\n\n\nfunction writeOutput(context: BuildContext, sassConfig: SassConfig, cssOutput: string, sourceMap: SassMap): Promise<string> {\n  let mappingsOutput: string = JSON.stringify(sourceMap);\n  return new Promise((resolve, reject) => {\n\n    Logger.debug(`sass start write output: ${sassConfig.outFile}`);\n\n    const buildDir = dirname(sassConfig.outFile);\n    ensureDirSync(buildDir);\n\n    writeFile(sassConfig.outFile, cssOutput, (cssWriteErr: any) => {\n      if (cssWriteErr) {\n        reject(new BuildError(`Error writing css file, ${sassConfig.outFile}: ${cssWriteErr}`));\n\n      } else {\n        Logger.debug(`sass saved output: ${sassConfig.outFile}`);\n\n        if (mappingsOutput) {\n          // save the css map file too\n          // this save completes async and does not hold up the resolve\n          const sourceMapPath = join(buildDir, basename(sassConfig.outFile) + '.map');\n\n          Logger.debug(`sass start write css map: ${sourceMapPath}`);\n\n          writeFile(sourceMapPath, mappingsOutput, (mapWriteErr: any) => {\n            if (mapWriteErr) {\n              Logger.error(`Error writing css map file, ${sourceMapPath}: ${mapWriteErr}`);\n\n            } else {\n              Logger.debug(`sass saved css map: ${sourceMapPath}`);\n            }\n          });\n        }\n\n        // css file all saved\n        // note that we're not waiting on the css map to finish saving\n        resolve(sassConfig.outFile);\n      }\n    });\n  });\n}\n\n\nfunction defaultSortComponentPathsFn(a: any, b: any): number {\n  const aIndexOfNodeModules = a.indexOf('node_modules');\n  const bIndexOfNodeModules = b.indexOf('node_modules');\n\n  if (aIndexOfNodeModules > -1 && bIndexOfNodeModules > -1) {\n    return (a > b) ? 1 : -1;\n  }\n\n  if (aIndexOfNodeModules > -1 && bIndexOfNodeModules === -1) {\n    return -1;\n  }\n\n  if (aIndexOfNodeModules === -1 && bIndexOfNodeModules > -1) {\n    return 1;\n  }\n\n  return (a > b) ? 1 : -1;\n}\n\n\nfunction defaultSortComponentFilesFn(a: any, b: any): number {\n  const aPeriods = a.split('.').length;\n  const bPeriods = b.split('.').length;\n  const aDashes = a.split('-').length;\n  const bDashes = b.split('-').length;\n\n  if (aPeriods > bPeriods) {\n    return 1;\n  } else if (aPeriods < bPeriods) {\n    return -1;\n  }\n\n  if (aDashes > bDashes) {\n    return 1;\n  } else if (aDashes < bDashes) {\n    return -1;\n  }\n\n  return (a > b) ? 1 : -1;\n}\n\n\nconst taskInfo: TaskInfo = {\n  fullArg: '--sass',\n  shortArg: '-s',\n  envVar: 'IONIC_SASS',\n  packageConfig: 'ionic_sass',\n  defaultConfigFile: 'sass.config'\n};\n\n\nexport interface SassConfig {\n  // https://www.npmjs.com/package/node-sass\n  outputFilename?: string;\n  outFile?: string;\n  file?: string;\n  data?: string;\n  includePaths?: string[];\n  excludeModules?: string[];\n  includeFiles?: RegExp[];\n  excludeFiles?: RegExp[];\n  directoryMaps?: { [key: string]: string };\n  sortComponentPathsFn?: (a: any, b: any) => number;\n  sortComponentFilesFn?: (a: any, b: any) => number;\n  variableSassFiles?: string[];\n  autoprefixer?: any;\n  sourceMap?: string;\n  omitSourceMapUrl?: boolean;\n  sourceMapContents?: boolean;\n  postCssPlugins?: any[];\n}\n\n\nexport interface SassMap {\n  version: number;\n  file: string;\n  sources: string[];\n  mappings: string;\n  names: any[];\n}\n"
  },
  {
    "path": "src/serve.spec.ts",
    "content": "import * as serve from './serve';\nimport * as config from './util/config';\nimport { BuildContext } from './util/interfaces';\nimport { ServeConfig } from './dev-server/serve-config';\n\nimport * as watch from './watch';\nimport * as open from './util/open';\nimport * as notificationServer from './dev-server/notification-server';\nimport * as httpServer from './dev-server/http-server';\nimport * as liveReloadServer from './dev-server/live-reload';\nimport * as network from './util/network';\n\ndescribe('test serve', () => {\n  let configResults: ServeConfig;\n  let context: BuildContext;\n  let openSpy: jasmine.Spy;\n\n  beforeEach(() => {\n    context = {\n      rootDir: '/',\n      wwwDir: '/www',\n      buildDir: '/build',\n    };\n    configResults = {\n      httpPort: 8100,\n      hostBaseUrl: 'http://localhost:8100',\n      host: '0.0.0.0',\n      rootDir: '/',\n      wwwDir: '/www',\n      buildDir: '/build',\n      isCordovaServe: false,\n      launchBrowser: true,\n      launchLab: false,\n      browserToLaunch: null,\n      useLiveReload: true,\n      liveReloadPort: 35729,\n      notificationPort: 53703,\n      useServerLogs: false,\n      useProxy: true,\n      notifyOnConsoleLog: false,\n      devapp: false\n    };\n    spyOn(network, 'findClosestOpenPorts').and.callFake((host: string, ports: number[]) => Promise.resolve(ports));\n    spyOn(notificationServer, 'createNotificationServer');\n    spyOn(liveReloadServer, 'createLiveReloadServer');\n    spyOn(httpServer, 'createHttpServer');\n    spyOn(watch, 'watch').and.returnValue(Promise.resolve());\n    openSpy = spyOn(open, 'default');\n  });\n\n  it('should work with no args on a happy path', () => {\n    return serve.serve(context).then(() => {\n      expect(network.findClosestOpenPorts).toHaveBeenCalledWith('0.0.0.0', [53703, 35729, 8100]);\n      expect(notificationServer.createNotificationServer).toHaveBeenCalledWith(configResults);\n      expect(liveReloadServer.createLiveReloadServer).toHaveBeenCalledWith(configResults);\n      expect(httpServer.createHttpServer).toHaveBeenCalledWith(configResults);\n      expect(openSpy.calls.mostRecent().args[0]).toEqual('http://localhost:8100');\n      expect(openSpy.calls.mostRecent().args[1]).toEqual(null);\n    });\n  });\n\n  it('should include ionicplatform in the browser url if platform is passed', () => {\n    config.addArgv('--platform');\n    config.addArgv('android');\n\n    return serve.serve(context).then(() => {\n      expect(network.findClosestOpenPorts).toHaveBeenCalledWith('0.0.0.0', [53703, 35729, 8100]);\n      expect(notificationServer.createNotificationServer).toHaveBeenCalledWith(configResults);\n      expect(liveReloadServer.createLiveReloadServer).toHaveBeenCalledWith(configResults);\n      expect(httpServer.createHttpServer).toHaveBeenCalledWith(configResults);\n      expect(openSpy.calls.mostRecent().args[0]).toEqual('http://localhost:8100?ionicplatform=android');\n      expect(openSpy.calls.mostRecent().args[1]).toEqual(null);\n    });\n  });\n\n  it('all args should be set in the config object and should be passed on to server functions', () => {\n    config.setProcessArgs([]);\n    config.addArgv('--serverlogs');\n    configResults.useServerLogs = true;\n    config.addArgv('--consolelogs');\n    configResults.notifyOnConsoleLog = true;\n    config.addArgv('--noproxy');\n    configResults.useProxy = false;\n    config.addArgv('--nolivereload');\n    configResults.useLiveReload = false;\n    config.addArgv('--lab');\n    configResults.launchLab = true;\n    config.addArgv('--browser');\n    config.addArgv('safari');\n    configResults.browserToLaunch = 'safari';\n    config.addArgv('--port');\n    config.addArgv('8101');\n    configResults.httpPort = 8101;\n    config.addArgv('--address');\n    config.addArgv('127.0.0.1');\n    configResults.host = '127.0.0.1';\n    configResults.hostBaseUrl = 'http://127.0.0.1:8101';\n    config.addArgv('--livereload-port');\n    config.addArgv('35730');\n    configResults.liveReloadPort = 35730;\n    config.addArgv('--dev-logger-port');\n    config.addArgv('53704');\n    configResults.notificationPort = 53704;\n\n    return serve.serve(context).then(() => {\n      expect(network.findClosestOpenPorts).toHaveBeenCalledWith('127.0.0.1', [53704, 35730, 8101]);\n      expect(notificationServer.createNotificationServer).toHaveBeenCalledWith(configResults);\n      expect(liveReloadServer.createLiveReloadServer).toHaveBeenCalledWith(configResults);\n      expect(httpServer.createHttpServer).toHaveBeenCalledWith(configResults);\n      expect(openSpy.calls.mostRecent().args[0]).toEqual('http://127.0.0.1:8101/ionic-lab');\n      expect(openSpy.calls.mostRecent().args[1]).toEqual('safari');\n    });\n  });\n});\n"
  },
  {
    "path": "src/serve.ts",
    "content": "import * as express from 'express';\nimport { BuildContext } from './util/interfaces';\nimport { getConfigValue, hasConfigValue } from './util/config';\nimport { BuildError } from './util/errors';\nimport { setContext } from './util/helpers';\nimport { Logger } from './logger/logger';\nimport { watch } from './watch';\nimport open from './util/open';\nimport { createNotificationServer } from './dev-server/notification-server';\nimport { createHttpServer } from './dev-server/http-server';\nimport { createLiveReloadServer } from './dev-server/live-reload';\nimport { ServeConfig, IONIC_LAB_URL } from './dev-server/serve-config';\nimport { findClosestOpenPorts } from './util/network';\n\nconst DEV_LOGGER_DEFAULT_PORT = 53703;\nconst LIVE_RELOAD_DEFAULT_PORT = 35729;\nconst DEV_SERVER_DEFAULT_PORT = 8100;\nconst DEV_SERVER_DEFAULT_HOST = '0.0.0.0';\n\nexport function serve(context: BuildContext) {\n  setContext(context);\n\n  let config: ServeConfig;\n  let httpServer: express.Application;\n  const host = getHttpServerHost(context);\n  const notificationPort = getNotificationPort(context);\n  const liveReloadServerPort = getLiveReloadServerPort(context);\n  const hostPort = getHttpServerPort(context);\n\n  function finish() {\n    if (config) {\n      if (httpServer) {\n        httpServer.listen(config.httpPort, config.host, function() {\n          Logger.debug(`listening on ${config.httpPort}`);\n        });\n      }\n\n      onReady(config, context);\n    }\n  }\n\n  return findClosestOpenPorts(host, [notificationPort, liveReloadServerPort, hostPort])\n    .then(([notificationPortFound, liveReloadServerPortFound, hostPortFound]) => {\n      const hostLocation = (host === '0.0.0.0') ? 'localhost' : host;\n\n      config = {\n        httpPort: hostPortFound,\n        host: host,\n        hostBaseUrl: `http://${hostLocation}:${hostPortFound}`,\n        rootDir: context.rootDir,\n        wwwDir: context.wwwDir,\n        buildDir: context.buildDir,\n        isCordovaServe: isCordovaServe(context),\n        launchBrowser: launchBrowser(context),\n        launchLab: launchLab(context),\n        browserToLaunch: browserToLaunch(context),\n        useLiveReload: useLiveReload(context),\n        liveReloadPort: liveReloadServerPortFound,\n        notificationPort: notificationPortFound,\n        useServerLogs: useServerLogs(context),\n        useProxy: useProxy(context),\n        notifyOnConsoleLog: sendClientConsoleLogs(context),\n        devapp: false\n      };\n\n      createNotificationServer(config);\n      createLiveReloadServer(config);\n      httpServer = createHttpServer(config);\n\n      return watch(context);\n    })\n    .then(() => {\n      finish();\n      return config;\n    }, (err: BuildError) => {\n      throw err;\n    })\n    .catch((err: BuildError) => {\n      if (err && err.isFatal) {\n        throw err;\n      } else {\n        finish();\n        return config;\n      }\n    });\n}\n\nfunction onReady(config: ServeConfig, context: BuildContext) {\n  if (config.launchBrowser) {\n    const openOptions: string[] = [config.hostBaseUrl]\n      .concat(launchLab(context) ? [IONIC_LAB_URL] : [])\n      .concat(browserOption(context) ? [browserOption(context)] : [])\n      .concat(platformOption(context) ? ['?ionicplatform=', platformOption(context)] : []);\n\n    open(openOptions.join(''), browserToLaunch(context), (error: Error) => {\n      if (error) {\n        const errorMessage = error && error.message ? error.message : error.toString();\n        Logger.warn(`Failed to open the browser: ${errorMessage}`);\n      }\n    });\n  }\n  Logger.info(`dev server running: ${config.hostBaseUrl}/`, 'green', true);\n  Logger.newLine();\n}\n\nfunction getHttpServerPort(context: BuildContext): number {\n  const port = getConfigValue(context, '--port', '-p', 'IONIC_PORT', 'ionic_port', null);\n  if (port) {\n    return parseInt(port, 10);\n  }\n  return DEV_SERVER_DEFAULT_PORT;\n}\n\nfunction getHttpServerHost(context: BuildContext): string {\n  const host = getConfigValue(context, '--address', '-h', 'IONIC_ADDRESS', 'ionic_address', null);\n  if (host) {\n    return host;\n  }\n  return DEV_SERVER_DEFAULT_HOST;\n}\n\nfunction getLiveReloadServerPort(context: BuildContext): number {\n  const port = getConfigValue(context, '--livereload-port', null, 'IONIC_LIVERELOAD_PORT', 'ionic_livereload_port', null);\n  if (port) {\n    return parseInt(port, 10);\n  }\n  return LIVE_RELOAD_DEFAULT_PORT;\n}\n\nexport function getNotificationPort(context: BuildContext): number {\n  const port = getConfigValue(context, '--dev-logger-port', null, 'IONIC_DEV_LOGGER_PORT', 'ionic_dev_logger_port', null);\n  if (port) {\n    return parseInt(port, 10);\n  }\n  return DEV_LOGGER_DEFAULT_PORT;\n}\n\nfunction useServerLogs(context: BuildContext): boolean {\n  return hasConfigValue(context, '--serverlogs', '-s', 'ionic_serverlogs', false);\n}\n\nfunction isCordovaServe(context: BuildContext): boolean {\n  return hasConfigValue(context, '--iscordovaserve', '-z', 'ionic_cordova_serve', false);\n}\n\nfunction launchBrowser(context: BuildContext): boolean {\n  return !hasConfigValue(context, '--nobrowser', '-b', 'ionic_launch_browser', false);\n}\n\nfunction browserToLaunch(context: BuildContext): string {\n  return getConfigValue(context, '--browser', '-w', 'IONIC_BROWSER', 'ionic_browser', null);\n}\n\nfunction browserOption(context: BuildContext): string {\n  return getConfigValue(context, '--browseroption', '-o', 'IONIC_BROWSEROPTION', 'ionic_browseroption', null);\n}\n\nfunction launchLab(context: BuildContext): boolean {\n  return hasConfigValue(context, '--lab', '-l', 'ionic_lab', false);\n}\n\nfunction platformOption(context: BuildContext): string {\n  return getConfigValue(context, '--platform', '-t', 'IONIC_PLATFORM_BROWSER', 'ionic_platform_browser', null);\n}\n\nfunction useLiveReload(context: BuildContext): boolean {\n  return !hasConfigValue(context, '--nolivereload', '-d', 'ionic_livereload', false);\n}\n\nfunction useProxy(context: BuildContext): boolean {\n  return !hasConfigValue(context, '--noproxy', '-x', 'ionic_proxy', false);\n}\n\nfunction sendClientConsoleLogs(context: BuildContext): boolean {\n  return hasConfigValue(context, '--consolelogs', '-c', 'ionic_consolelogs', false);\n}\n"
  },
  {
    "path": "src/template.spec.ts",
    "content": "import { join, resolve } from 'path';\n\nimport * as mockFs from 'mock-fs';\n\nimport { Logger } from './logger/logger';\nimport { inlineTemplate, replaceTemplateUrl, updateTemplate } from './template';\nimport { getTemplateMatch, getTemplateFormat, replaceExistingJsTemplate } from './template';\n\n\ndescribe('template', () => {\n\n  describe('inlineTemplate', () => {\n\n    it('should inline multiple external html files which are the same for multiple @Components in same .ts file', () => {\n      const sourceText = '/*someprefix*/@Component({templateUrl: \"some-file.html\" });/*somebetween*/@Component({templateUrl: \"some-file.html\" })/*somesuffix*/';\n\n      const baseDir = join(process.cwd(), 'path', 'to', 'dir');\n\n      const d: any = { };\n\n      d[baseDir] = {\n        'some-file.html': '<div>A</div>',\n        'some-file.scss': 'body { color: red; }',\n        'some-file.ts': sourceText,\n      };\n      mockFs(d);\n\n      const results = inlineTemplate(sourceText, join(baseDir, 'some-file.ts'));\n\n      expect(results).toEqual(`/*someprefix*/@Component({template:/*ion-inline-start:\"${join(baseDir, 'some-file.html')}\"*/\\'<div>A</div>\\'/*ion-inline-end:\"${join(baseDir, 'some-file.html')}\"*/ });/*somebetween*/@Component({template:/*ion-inline-start:\"${join(baseDir, 'some-file.html')}\"*/\\'<div>A</div>\\'/*ion-inline-end:\"${join(baseDir, 'some-file.html')}\"*/ })/*somesuffix*/`);\n      mockFs.restore();\n    });\n\n    it('should inline multiple external html files with multiple @Components in same .ts file', () => {\n      const sourceText = '/*someprefix*/@Component({templateUrl: \"some-file1.html\" });/*somebetween*/@Component({templateUrl: \"some-file2.html\" })/*somesuffix*/';\n\n      const baseDir = join(process.cwd(), 'path', 'to', 'dir');\n      const d: any = { };\n\n      d[baseDir] = {\n        'some-file1.html': '<div>A</div>',\n        'some-file2.html': '<div>B</div>',\n        'some-file.scss': 'body { color: red; }',\n        'some-file.ts': sourceText,\n      };\n      mockFs(d);\n\n      const results = inlineTemplate(sourceText, join(baseDir, 'some-file.ts'));\n\n      expect(results).toEqual(`/*someprefix*/@Component({template:/*ion-inline-start:\"${join(baseDir, 'some-file1.html')}\"*/\\'<div>A</div>\\'/*ion-inline-end:\"${join(baseDir, 'some-file1.html')}\"*/ });/*somebetween*/@Component({template:/*ion-inline-start:\"${join(baseDir, 'some-file2.html')}\"*/\\'<div>B</div>\\'/*ion-inline-end:\"${join(baseDir, 'some-file2.html')}\"*/ })/*somesuffix*/`);\n      mockFs.restore();\n    });\n\n    it('should inline the external html file content', () => {\n      const sourceText = '@Component({templateUrl: \"some-file.html\" })';\n\n      const baseDir = join(process.cwd(), 'path', 'to', 'dir');\n\n      const d: any = { };\n\n      d[baseDir] = {\n        'some-file.html': '<div>hello</div>',\n        'some-file.scss': 'body { color: red; }',\n        'some-file.ts': sourceText,\n      };\n      mockFs(d);\n\n      const results = inlineTemplate(sourceText, join(baseDir, 'some-file.ts'));\n\n      expect(results).toEqual(`@Component({template:/*ion-inline-start:\"${join(baseDir, 'some-file.html')}\"*/\\'<div>hello</div>\\'/*ion-inline-end:\"${join(baseDir, 'some-file.html')}\"*/ })`);\n      mockFs.restore();\n    });\n\n    it('should do nothing for files with incomplete Component', () => {\n      const sourceText = `\n        // Component this be bork\n      `;\n      const sourcePath = 'somefile.ts';\n      const output = inlineTemplate(sourceText, sourcePath);\n\n      expect(output).toEqual(sourceText);\n    });\n\n    it('should do nothing for files without Component', () => {\n      const sourceText = `\n        console.log('yeah nothing');\n      `;\n      const sourcePath = 'somefile.ts';\n      const output = inlineTemplate(sourceText, sourcePath);\n\n      expect(output).toEqual(sourceText);\n    });\n\n  });\n\n  describe('updateTemplate', () => {\n\n    it('should load and replace html file content', () => {\n      const d = {\n        'path/to/dir': {\n          'some-file.html': '<div>hello</div>',\n          'some-file.scss': 'body { color: red; }',\n          'some-file.ts': '@Component({templateUrl: \"some-file.html\" })',\n        },\n      };\n      mockFs(d);\n\n      const match = getTemplateMatch(d['path/to/dir']['some-file.ts']);\n      const expected = replaceTemplateUrl(match, 'path/to/dir/some-file.html', '<div>hello</div>');\n\n      const results = updateTemplate('path/to/dir', match);\n\n      expect(results).toEqual(expected);\n      mockFs.restore();\n    });\n\n    it('should load null for unfound html file content', () => {\n      const d: any = {\n        'path/to/dir': {\n          'some-file.html': '<div>hello</div>',\n          'some-file.scss': 'body { color: red; }',\n          'some-file.ts': '@Component({templateUrl: \"some-file-doesnt-exist.html\" })',\n        },\n      };\n      mockFs(d);\n\n      const match = getTemplateMatch(d['path/to/dir']['some-file.ts']);\n\n      const results = updateTemplate('path/to/dir', match);\n\n      expect(results).toEqual(null);\n      mockFs.restore();\n    });\n\n  });\n\n  describe('replaceTemplateUrl', () => {\n\n    it('should turn the template into one line', () => {\n      const str = `\n        Component({\n          templateUrl: \"somepage.html\"})`;\n      const templateContent = `\n        <div>\\t\n          this is \"multiline\" 'content'\n        </div>\\r\n      `;\n      const htmlFilePath = join(process.cwd(), 'full', 'path', 'to', 'somepage.html');\n      const match = getTemplateMatch(str);\n      const result = replaceTemplateUrl(match, htmlFilePath, templateContent);\n\n      const expected = `Component({template:/*ion-inline-start:\"${join(process.cwd(), 'full', 'path', 'to', 'somepage.html')}\"*/\\'\\\\n        <div>\\t\\\\n          this is \"multiline\" \\\\'content\\\\'\\\\n        </div>\\\\n\\\\n      \\'/*ion-inline-end:\"${join(process.cwd(), 'full', 'path', 'to', 'somepage.html')}\"*/})`;\n\n      expect(result).toEqual(expected);\n    });\n\n  });\n\n  describe('getTemplateFormat', () => {\n\n    it('should resolve the path', () => {\n      const path = 'some/crazy/path/my.html';\n      const resolvedPath = resolve(path);\n      const results = getTemplateFormat(path, 'filibuster');\n      expect(path).not.toEqual(resolvedPath);\n      expect(results).toEqual(`template:/*ion-inline-start:\"${resolvedPath}\"*/\\'filibuster\\'/*ion-inline-end:\"${resolvedPath}\"*/`);\n    });\n\n  });\n\n  describe('replaceBundleJsTemplate', () => {\n\n    it('should replace already inlined template with new content', () => {\n      const htmlFilePath = 'c:\\\\path/to\\some/crazy:thing.html;';\n      const oldContent = 'some old content';\n      const tmplate = getTemplateFormat(htmlFilePath, oldContent);\n      const bundleSourceText = `\n        @Component({\n          selector: 'yo-div',\n          /*blah*/${tmplate}/*derp*/\n        })\n        @Component({\n          selector: 'yo-div2',\n          /*222*/${tmplate}/*2222*/\n        })\n      `;\n      const newContent = 'some new content';\n      const output = replaceExistingJsTemplate(bundleSourceText, newContent, htmlFilePath);\n      expect(output.indexOf(newContent)).toBeGreaterThan(-1);\n      expect(output.indexOf(newContent)).toBeGreaterThan(-1);\n    });\n\n  });\n\n  describe('COMPONENT_REGEX match', () => {\n\n    it('should get Component with template url and selector above', () => {\n      const str = `\n        Component({\n          selector: 'page-home',\n          templateUrl: 'home.html'\n        })\n      `;\n\n      const match = getTemplateMatch(str);\n      expect(match.templateUrl).toEqual('home.html');\n    });\n\n    it('should get Component with template url and selector below', () => {\n      const str = `\n        Component({\n          templateUrl: 'home.html',\n          selector: 'page-home\n        })\n      `;\n\n      const match = getTemplateMatch(str);\n      expect(match.templateUrl).toEqual('home.html');\n    });\n\n    it('should get Component with template url, spaces, tabs and new lines', () => {\n      const str = `\\t\\n\\r\n        Component(\n          {\n\n            templateUrl :\n              \\t\\n\\r\"c:\\\\some\\windows\\path.ts\"\n\n          }\n        )\n      `;\n\n      const match = getTemplateMatch(str);\n      expect(match.templateUrl).toEqual('c:\\\\some\\windows\\path.ts');\n    });\n\n    it('should get Component with template url and spaces', () => {\n      const str = '  Component  (  {  templateUrl  :  `  hi  `  }  )  ';\n      const match = getTemplateMatch(str);\n      expect(match.component).toEqual('Component  (  {  templateUrl  :  `  hi  `  }  )');\n      expect(match.templateProperty).toEqual('  templateUrl  :  `  hi  `');\n      expect(match.templateUrl).toEqual('hi');\n    });\n\n    it('should get Component with template url and back-ticks', () => {\n      const str = 'Component({templateUrl:`hi`})';\n      const match = getTemplateMatch(str);\n      expect(match.component).toEqual('Component({templateUrl:`hi`})');\n      expect(match.templateProperty).toEqual('templateUrl:`hi`');\n      expect(match.templateUrl).toEqual('hi');\n    });\n\n    it('should get Component with template url and double quotes', () => {\n      const str = 'Component({templateUrl:\"hi\"})';\n      const match = getTemplateMatch(str);\n      expect(match.component).toEqual('Component({templateUrl:\"hi\"})');\n      expect(match.templateProperty).toEqual('templateUrl:\"hi\"');\n      expect(match.templateUrl).toEqual('hi');\n    });\n\n    it('should get Component with template url and single quotes', () => {\n      const str = 'Component({templateUrl:\\'hi\\'})';\n      const match = getTemplateMatch(str);\n      expect(match.component).toEqual('Component({templateUrl:\\'hi\\'})');\n      expect(match.templateProperty).toEqual('templateUrl:\\'hi\\'');\n      expect(match.templateUrl).toEqual('hi');\n    });\n\n    it('should get null for Component without string for templateUrl', () => {\n      const str = 'Component({templateUrl:someVar})';\n      const match = getTemplateMatch(str);\n      expect(match).toEqual(null);\n    });\n\n    it('should get null for Component without templateUrl', () => {\n      const str = 'Component({template:\"hi\"})';\n      const match = getTemplateMatch(str);\n      expect(match).toEqual(null);\n    });\n\n    it('should get null for Component without brackets', () => {\n      const str = 'Component()';\n      const match = getTemplateMatch(str);\n      expect(match).toEqual(null);\n    });\n\n    it('should get null for Component without parentheses', () => {\n      const str = 'Component';\n      const match = getTemplateMatch(str);\n      expect(match).toEqual(null);\n    });\n\n    it('should get null for Component({})', () => {\n      const str = 'Component';\n      const match = getTemplateMatch(str);\n      expect(match).toEqual(null);\n    });\n\n    it('should get null for no Component', () => {\n      const str = 'whatever';\n      const match = getTemplateMatch(str);\n      expect(match).toEqual(null);\n    });\n\n  });\n\n  const oldLoggerError = Logger.error;\n  Logger.error = function() {};\n\n  afterAll(() => {\n    Logger.error = oldLoggerError;\n  });\n\n});\n"
  },
  {
    "path": "src/template.ts",
    "content": "import { readFileSync, writeFileSync } from 'fs';\nimport { dirname, extname, join, parse, resolve } from 'path';\n\nimport * as Constants from './util/constants';\nimport { BuildContext, BuildState, ChangedFile, File } from './util/interfaces';\nimport { changeExtension, getStringPropertyValue } from './util/helpers';\nimport { Logger } from './logger/logger';\n\n\n\nexport function templateUpdate(changedFiles: ChangedFile[], context: BuildContext) {\n  try {\n    const changedTemplates = changedFiles.filter(changedFile => changedFile.ext === '.html');\n    const start = Date.now();\n\n    const bundleFiles = context.fileCache.getAll().filter(file => file.path.indexOf(context.buildDir) >= 0 && extname(file.path) === '.js');\n\n    // update the corresponding transpiled javascript file with the template changed (inline it)\n    // as well as the bundle\n    for (const changedTemplateFile of changedTemplates) {\n      const file = context.fileCache.get(changedTemplateFile.filePath);\n      if (!updateCorrespondingJsFile(context, file.content, changedTemplateFile.filePath)) {\n        throw new Error(`Failed to inline template ${changedTemplateFile.filePath}`);\n      }\n      // find the corresponding bundles\n      for (const bundleFile of bundleFiles) {\n        const newContent = replaceExistingJsTemplate(bundleFile.content, file.content, changedTemplateFile.filePath);\n        if (newContent && newContent !== bundleFile.content) {\n          context.fileCache.set(bundleFile.path, { path: bundleFile.path, content: newContent});\n          writeFileSync(bundleFile.path, newContent);\n        }\n      }\n    }\n\n    // awesome, all good and template updated in the bundle file\n    const logger = new Logger(`template update`);\n    logger.setStartTime(start);\n\n    // congrats, all good\n    changedTemplates.forEach(changedTemplate => {\n      Logger.debug(`templateUpdate, updated: ${changedTemplate.filePath}`);\n    });\n\n    context.templateState = BuildState.SuccessfulBuild;\n    logger.finish();\n    return Promise.resolve();\n\n  } catch (ex) {\n    Logger.debug(`templateUpdate error: ${ex.message}`);\n    context.transpileState = BuildState.RequiresBuild;\n    context.deepLinkState = BuildState.RequiresBuild;\n    context.bundleState = BuildState.RequiresUpdate;\n    return Promise.resolve();\n  }\n}\n\nfunction updateCorrespondingJsFile(context: BuildContext, newTemplateContent: string, existingHtmlTemplatePath: string) {\n  const moduleFileExtension = changeExtension(getStringPropertyValue(Constants.ENV_NG_MODULE_FILE_NAME_SUFFIX), '.js');\n  const javascriptFiles = context.fileCache.getAll().filter((file: File) => dirname(file.path) === dirname(existingHtmlTemplatePath) && extname(file.path) === '.js' && !file.path.endsWith(moduleFileExtension));\n  for (const javascriptFile of javascriptFiles) {\n    const newContent = replaceExistingJsTemplate(javascriptFile.content, newTemplateContent, existingHtmlTemplatePath);\n    if (newContent && newContent !== javascriptFile.content) {\n      javascriptFile.content = newContent;\n      // set the file again to generate a new timestamp\n      // do the same for the typescript file just to invalidate any caches, etc.\n      context.fileCache.set(javascriptFile.path, javascriptFile);\n      const typescriptFilePath = changeExtension(javascriptFile.path, '.ts');\n      context.fileCache.set(typescriptFilePath, context.fileCache.get(typescriptFilePath));\n      return true;\n    }\n  }\n  return false;\n}\n\nexport function inlineTemplate(sourceText: string, sourcePath: string): string {\n  const componentDir = parse(sourcePath).dir;\n  let match: TemplateUrlMatch;\n  let replacement: string;\n  let lastMatch: string = null;\n\n  while (match = getTemplateMatch(sourceText)) {\n    if (match.component === lastMatch) {\n      // panic! we don't want to melt any machines if there's a bug\n      Logger.debug(`Error matching component: ${match.component}`);\n      return sourceText;\n    }\n    lastMatch = match.component;\n\n    if (match.templateUrl === '') {\n      Logger.error(`Error @Component templateUrl missing in: \"${sourcePath}\"`);\n      return sourceText;\n    }\n\n    replacement = updateTemplate(componentDir, match);\n    if (replacement) {\n      sourceText = sourceText.replace(match.component, replacement);\n    }\n  }\n\n  return sourceText;\n}\n\n\nexport function updateTemplate(componentDir: string, match: TemplateUrlMatch): string {\n  const htmlFilePath = join(componentDir, match.templateUrl);\n\n  try {\n    const templateContent = readFileSync(htmlFilePath, 'utf8');\n    return replaceTemplateUrl(match, htmlFilePath, templateContent);\n  } catch (e) {\n    Logger.error(`template error, \"${htmlFilePath}\": ${e}`);\n  }\n\n  return null;\n}\n\n\nexport function replaceTemplateUrl(match: TemplateUrlMatch, htmlFilePath: string, templateContent: string): string {\n  const orgTemplateProperty = match.templateProperty;\n  const newTemplateProperty = getTemplateFormat(htmlFilePath, templateContent);\n\n  return match.component.replace(orgTemplateProperty, newTemplateProperty);\n}\n\n\nexport function replaceExistingJsTemplate(existingSourceText: string, newTemplateContent: string, htmlFilePath: string): string {\n  let prefix = getTemplatePrefix(htmlFilePath);\n  let startIndex = existingSourceText.indexOf(prefix);\n\n  let isStringified = false;\n\n  if (startIndex === -1) {\n    prefix = stringify(prefix);\n    isStringified = true;\n  }\n\n  startIndex = existingSourceText.indexOf(prefix);\n  if (startIndex === -1) {\n    return null;\n  }\n\n  let suffix = getTemplateSuffix(htmlFilePath);\n  if (isStringified) {\n    suffix = stringify(suffix);\n  }\n\n  const endIndex = existingSourceText.indexOf(suffix, startIndex + 1);\n  if (endIndex === -1) {\n    return null;\n  }\n\n  const oldTemplate = existingSourceText.substring(startIndex, endIndex + suffix.length);\n  let newTemplate = getTemplateFormat(htmlFilePath, newTemplateContent);\n\n  if (isStringified) {\n    newTemplate = stringify(newTemplate);\n  }\n\n  let lastChange: string = null;\n  while (existingSourceText.indexOf(oldTemplate) > -1 && existingSourceText !== lastChange) {\n    lastChange = existingSourceText = existingSourceText.replace(oldTemplate, newTemplate);\n  }\n\n  return existingSourceText;\n}\n\nfunction stringify(str: string) {\n  str = JSON.stringify(str);\n  return str.substr(1, str.length - 2);\n}\n\n\nexport function getTemplateFormat(htmlFilePath: string, content: string) {\n  // turn the template into one line and espcape single quotes\n  content = content.replace(/\\r|\\n/g, '\\\\n');\n  content = content.replace(/\\'/g, '\\\\\\'');\n\n  return `${getTemplatePrefix(htmlFilePath)}\\'${content}\\'${getTemplateSuffix(htmlFilePath)}`;\n}\n\n\nfunction getTemplatePrefix(htmlFilePath: string) {\n  return `template:/*ion-inline-start:\"${resolve(htmlFilePath)}\"*/`;\n}\n\n\nfunction getTemplateSuffix(htmlFilePath: string) {\n  return `/*ion-inline-end:\"${resolve(htmlFilePath)}\"*/`;\n}\n\n\nexport function getTemplateMatch(str: string): TemplateUrlMatch {\n  const match = COMPONENT_REGEX.exec(str);\n  if (match) {\n    return {\n      start: match.index,\n      end: match.index + match[0].length,\n      component: match[0],\n      templateProperty: match[3],\n      templateUrl: match[5].trim()\n    };\n  }\n  return null;\n}\n\n\nconst COMPONENT_REGEX = /Component\\s*?\\(\\s*?(\\{([\\s\\S]*?)(\\s*templateUrl\\s*:\\s*(['\"`])(.*?)(['\"`])\\s*?)([\\s\\S]*?)}\\s*?)\\)/m;\n\nexport interface TemplateUrlMatch {\n  start: number;\n  end: number;\n  component: string;\n  templateProperty: string;\n  templateUrl: string;\n}\n"
  },
  {
    "path": "src/transpile-worker.ts",
    "content": "import { BuildContext } from './util/interfaces';\nimport { transpileWorker, TranspileWorkerMessage, TranspileWorkerConfig } from './transpile';\n\n\nconst context: BuildContext = {};\n\nprocess.on('message', (incomingMsg: TranspileWorkerMessage) => {\n  context.rootDir = incomingMsg.rootDir;\n  context.buildDir = incomingMsg.buildDir;\n\n  const workerConfig: TranspileWorkerConfig = {\n    configFile: incomingMsg.configFile,\n    writeInMemory: false,\n    sourceMaps: false,\n    cache: false,\n    inlineTemplate: false,\n    useTransforms: false\n  };\n\n  transpileWorker(context, workerConfig)\n    .then(() => {\n      const outgoingMsg: TranspileWorkerMessage = {\n        transpileSuccess: true\n      };\n      process.send(outgoingMsg);\n    })\n    .catch(() => {\n      const outgoingMsg: TranspileWorkerMessage = {\n        transpileSuccess: false\n      };\n      process.send(outgoingMsg);\n    });\n\n});\n"
  },
  {
    "path": "src/transpile.spec.ts",
    "content": "import * as transpile from './transpile';\n\nimport { FileCache } from './util/file-cache';\nimport { BuildContext } from './util/interfaces';\n\ndescribe('transpile', () => {\n  describe('resetSourceFiles', () => {\n    it('should remove any files with temporary suffix, and reset content to the original, non-modified value', () => {\n      const context = {\n        fileCache: new FileCache()\n      };\n\n      const aboutFilePath = 'about.ts';\n      const aboutFile = { path: aboutFilePath, content: 'modifiedContent'};\n      const originalAboutFilePath = aboutFilePath + transpile.inMemoryFileCopySuffix;\n      const originalAboutFile = { path: originalAboutFilePath, content: 'originalContent'};\n      context.fileCache.set(aboutFilePath, aboutFile);\n      context.fileCache.set(originalAboutFilePath, originalAboutFile);\n\n      transpile.resetSourceFiles(context.fileCache);\n\n      expect(context.fileCache.get(originalAboutFilePath)).toBeFalsy();\n      expect(context.fileCache.get(aboutFilePath).content).toEqual(originalAboutFile.content);\n    });\n  });\n});\n"
  },
  {
    "path": "src/transpile.ts",
    "content": "import { fork, ChildProcess } from 'child_process';\nimport { EventEmitter } from 'events';\nimport { readFileSync } from 'fs';\nimport * as path from 'path';\n\nimport * as ts from 'typescript';\n\nimport { getFileSystemCompilerHostInstance } from './aot/compiler-host-factory';\nimport { buildJsSourceMaps } from './bundle';\nimport {\n  getInjectDeepLinkConfigTypescriptTransform,\n  purgeDeepLinkDecoratorTSTransform }\nfrom './deep-linking/util';\n\nimport {\n  convertDeepLinkConfigEntriesToString,\n  getUpdatedAppNgModuleContentWithDeepLinkConfig,\n  filterTypescriptFilesForDeepLinks,\n  hasExistingDeepLinkConfig,\n  isDeepLinkingFile,\n  purgeDeepLinkDecorator\n} from './deep-linking/util';\n\nimport { Logger } from './logger/logger';\nimport { printDiagnostics, clearDiagnostics, DiagnosticsType } from './logger/logger-diagnostics';\nimport { runTypeScriptDiagnostics } from './logger/logger-typescript';\nimport { inlineTemplate } from './template';\nimport * as Constants from './util/constants';\nimport { BuildError } from './util/errors';\nimport { FileCache } from './util/file-cache';\nimport { changeExtension, getBooleanPropertyValue, getParsedDeepLinkConfig, getStringPropertyValue } from './util/helpers';\nimport { BuildContext, BuildState, ChangedFile, File } from './util/interfaces';\n\nexport function transpile(context: BuildContext) {\n\n  const workerConfig: TranspileWorkerConfig = {\n    configFile: getTsConfigPath(context),\n    writeInMemory: true,\n    sourceMaps: true,\n    cache: true,\n    inlineTemplate: context.inlineTemplates,\n    useTransforms: true\n  };\n\n  const logger = new Logger('transpile');\n\n  return transpileWorker(context, workerConfig)\n    .then(() => {\n      context.transpileState = BuildState.SuccessfulBuild;\n      logger.finish();\n    })\n    .catch(err => {\n      context.transpileState = BuildState.RequiresBuild;\n      throw logger.fail(err);\n    });\n}\n\n\nexport function transpileUpdate(changedFiles: ChangedFile[], context: BuildContext) {\n  const workerConfig: TranspileWorkerConfig = {\n    configFile: getTsConfigPath(context),\n    writeInMemory: true,\n    sourceMaps: true,\n    cache: false,\n    inlineTemplate: context.inlineTemplates,\n    useTransforms: true\n  };\n\n  const logger = new Logger('transpile update');\n\n  const changedTypescriptFiles = changedFiles.filter(changedFile => changedFile.ext === '.ts');\n\n  const promises: Promise<void>[] = [];\n  for (const changedTypescriptFile of changedTypescriptFiles) {\n    promises.push(transpileUpdateWorker(changedTypescriptFile.event, changedTypescriptFile.filePath, context, workerConfig));\n  }\n\n  return Promise.all(promises)\n    .then(() => {\n      context.transpileState = BuildState.SuccessfulBuild;\n      logger.finish();\n    })\n    .catch(err => {\n      context.transpileState = BuildState.RequiresBuild;\n      throw logger.fail(err);\n    });\n\n}\n\n\n/**\n * The full TS build for all app files.\n */\nexport function transpileWorker(context: BuildContext, workerConfig: TranspileWorkerConfig) {\n\n  // let's do this\n  return new Promise((resolve, reject) => {\n\n    clearDiagnostics(context, DiagnosticsType.TypeScript);\n\n    // get the tsconfig data\n    const tsConfig = getTsConfig(context, workerConfig.configFile);\n\n    if (workerConfig.sourceMaps === false) {\n      // the worker config say, \"hey, don't ever bother making a source map, because.\"\n      tsConfig.options.sourceMap = false;\n\n    } else {\n      // build the ts source maps if the bundler is going to use source maps\n      tsConfig.options.sourceMap = buildJsSourceMaps(context);\n    }\n\n    // collect up all the files we need to transpile, tsConfig itself does all this for us\n    const tsFileNames = cleanFileNames(context, tsConfig.fileNames);\n\n    // for dev builds let's not create d.ts files\n    tsConfig.options.declaration = undefined;\n\n    // let's start a new tsFiles object to cache all the transpiled files in\n    const host = getFileSystemCompilerHostInstance(tsConfig.options);\n\n    if (workerConfig.useTransforms && getBooleanPropertyValue(Constants.ENV_PARSE_DEEPLINKS)) {\n      // beforeArray.push(purgeDeepLinkDecoratorTSTransform());\n      // beforeArray.push(getInjectDeepLinkConfigTypescriptTransform());\n\n      // temporarily copy the files to a new location\n      copyOriginalSourceFiles(context.fileCache);\n\n      // okay, purge the deep link files NOT using a transform\n      const deepLinkFiles = filterTypescriptFilesForDeepLinks(context.fileCache);\n\n      deepLinkFiles.forEach(file => {\n        file.content = purgeDeepLinkDecorator(file.content);\n      });\n\n      const file = context.fileCache.get(getStringPropertyValue(Constants.ENV_APP_NG_MODULE_PATH));\n      const hasExisting = hasExistingDeepLinkConfig(file.path, file.content);\n      if (!hasExisting) {\n        const deepLinkString = convertDeepLinkConfigEntriesToString(getParsedDeepLinkConfig());\n        file.content = getUpdatedAppNgModuleContentWithDeepLinkConfig(file.path, file.content, deepLinkString);\n      }\n    }\n\n    const program = ts.createProgram(tsFileNames, tsConfig.options, host, cachedProgram);\n\n    resetSourceFiles(context.fileCache);\n\n    const beforeArray: ts.TransformerFactory<ts.SourceFile>[] = [];\n\n    program.emit(undefined, (path: string, data: string, writeByteOrderMark: boolean, onError: Function, sourceFiles: ts.SourceFile[]) => {\n      if (workerConfig.writeInMemory) {\n        writeTranspiledFilesCallback(context.fileCache, path, data, workerConfig.inlineTemplate);\n      }\n    });\n\n    // cache the typescript program for later use\n    cachedProgram = program;\n\n    const tsDiagnostics = program.getSyntacticDiagnostics()\n      .concat(program.getSemanticDiagnostics())\n      .concat(program.getOptionsDiagnostics());\n\n    const diagnostics = runTypeScriptDiagnostics(context, tsDiagnostics);\n\n    if (diagnostics.length) {\n      // darn, we've got some things wrong, transpile failed :(\n      printDiagnostics(context, DiagnosticsType.TypeScript, diagnostics, true, true);\n\n      reject(new BuildError('Failed to transpile program'));\n\n    } else {\n      // transpile success :)\n      resolve();\n    }\n  });\n}\n\n\nexport function canRunTranspileUpdate(event: string, filePath: string, context: BuildContext) {\n  if (event === 'change' && context.fileCache) {\n    return context.fileCache.has(path.resolve(filePath));\n  }\n  return false;\n}\n\n\n/**\n * Iterative build for one TS file. If it's not an existing file change, or\n * something errors out then it falls back to do the full build.\n */\nfunction transpileUpdateWorker(event: string, filePath: string, context: BuildContext, workerConfig: TranspileWorkerConfig) {\n  try {\n    clearDiagnostics(context, DiagnosticsType.TypeScript);\n\n    filePath = path.normalize(path.resolve(filePath));\n\n    // an existing ts file we already know about has changed\n    // let's \"TRY\" to do a single module build for this one file\n    if (!cachedTsConfig) {\n      cachedTsConfig = getTsConfig(context, workerConfig.configFile);\n    }\n\n    // build the ts source maps if the bundler is going to use source maps\n    cachedTsConfig.options.sourceMap = buildJsSourceMaps(context);\n\n    const beforeArray: ts.TransformerFactory<ts.SourceFile>[] = [];\n\n    const transpileOptions: ts.TranspileOptions = {\n      compilerOptions: cachedTsConfig.options,\n      fileName: filePath,\n      reportDiagnostics: true,\n    };\n\n    // let's manually transpile just this one ts file\n    // since it is an update, it's in memory already\n    const sourceText = context.fileCache.get(filePath).content;\n    const textToTranspile = workerConfig.useTransforms && getBooleanPropertyValue(Constants.ENV_PARSE_DEEPLINKS) ? transformSource(filePath, sourceText) : sourceText;\n\n    // transpile this one module\n    const transpileOutput = ts.transpileModule(textToTranspile, transpileOptions);\n\n    const diagnostics = runTypeScriptDiagnostics(context, transpileOutput.diagnostics);\n\n    if (diagnostics.length) {\n      printDiagnostics(context, DiagnosticsType.TypeScript, diagnostics, false, true);\n\n      // darn, we've got some errors with this transpiling :(\n      // but at least we reported the errors like really really fast, so there's that\n      Logger.debug(`transpileUpdateWorker: transpileModule, diagnostics: ${diagnostics.length}`);\n\n      throw new BuildError(`Failed to transpile file - ${filePath}`);\n    } else {\n      // convert the path to have a .js file extension for consistency\n      const newPath = changeExtension(filePath, '.js');\n\n      const sourceMapFile = { path: newPath + '.map', content: transpileOutput.sourceMapText };\n      let jsContent: string = transpileOutput.outputText;\n      if (workerConfig.inlineTemplate) {\n        // use original path for template inlining\n        jsContent = inlineTemplate(transpileOutput.outputText, filePath);\n      }\n      const jsFile = { path: newPath, content: jsContent };\n      const tsFile = { path: filePath, content: sourceText };\n\n      context.fileCache.set(sourceMapFile.path, sourceMapFile);\n      context.fileCache.set(jsFile.path, jsFile);\n      context.fileCache.set(tsFile.path, tsFile);\n    }\n\n    return Promise.resolve();\n  } catch (ex) {\n    return Promise.reject(ex);\n  }\n}\n\n\nexport function transpileDiagnosticsOnly(context: BuildContext) {\n  return new Promise(resolve => {\n    workerEvent.once('DiagnosticsWorkerDone', () => {\n      resolve();\n    });\n\n    runDiagnosticsWorker(context);\n  });\n}\n\nconst workerEvent = new EventEmitter();\nlet diagnosticsWorker: ChildProcess = null;\n\nfunction runDiagnosticsWorker(context: BuildContext) {\n  if (!diagnosticsWorker) {\n    const workerModule = path.join(__dirname, 'transpile-worker.js');\n    diagnosticsWorker = fork(workerModule, [], { env: { FORCE_COLOR: true } });\n\n    Logger.debug(`diagnosticsWorker created, pid: ${diagnosticsWorker.pid}`);\n\n    diagnosticsWorker.on('error', (err: any) => {\n      Logger.error(`diagnosticsWorker error, pid: ${diagnosticsWorker.pid}, error: ${err}`);\n      workerEvent.emit('DiagnosticsWorkerDone');\n    });\n\n    diagnosticsWorker.on('exit', (code: number) => {\n      Logger.debug(`diagnosticsWorker exited, pid: ${diagnosticsWorker.pid}`);\n      diagnosticsWorker = null;\n    });\n\n    diagnosticsWorker.on('message', (msg: TranspileWorkerMessage) => {\n      workerEvent.emit('DiagnosticsWorkerDone');\n    });\n  }\n\n  const msg: TranspileWorkerMessage = {\n    rootDir: context.rootDir,\n    buildDir: context.buildDir,\n    configFile: getTsConfigPath(context)\n  };\n  diagnosticsWorker.send(msg);\n}\n\n\nexport interface TranspileWorkerMessage {\n  rootDir?: string;\n  buildDir?: string;\n  configFile?: string;\n  transpileSuccess?: boolean;\n}\n\n\nfunction cleanFileNames(context: BuildContext, fileNames: string[]) {\n  // make sure we're not transpiling the prod when dev and stuff\n  return fileNames;\n}\n\nfunction writeTranspiledFilesCallback(fileCache: FileCache, sourcePath: string, data: string, shouldInlineTemplate: boolean) {\n  sourcePath = path.normalize(path.resolve(sourcePath));\n\n  if (sourcePath.endsWith('.js')) {\n    let file = fileCache.get(sourcePath);\n    if (!file) {\n      file = { content: '', path: sourcePath };\n    }\n\n    if (shouldInlineTemplate) {\n      file.content = inlineTemplate(data, sourcePath);\n    } else {\n      file.content = data;\n    }\n\n    fileCache.set(sourcePath, file);\n\n  } else if (sourcePath.endsWith('.js.map')) {\n\n    let file = fileCache.get(sourcePath);\n    if (!file) {\n      file = { content: '', path: sourcePath };\n    }\n    file.content = data;\n\n    fileCache.set(sourcePath, file);\n  }\n}\n\nexport async function getTsConfigAsync(context: BuildContext, tsConfigPath?: string): Promise<TsConfig> {\n  return await getTsConfig(context, tsConfigPath);\n}\n\nexport function getTsConfig(context: BuildContext, tsConfigPath?: string): TsConfig {\n  let config: TsConfig = null;\n  tsConfigPath = tsConfigPath || getTsConfigPath(context);\n\n  const tsConfigFile = ts.readConfigFile(tsConfigPath, path => readFileSync(path, 'utf8'));\n\n  if (!tsConfigFile) {\n    throw new BuildError(`tsconfig: invalid tsconfig file, \"${tsConfigPath}\"`);\n\n  } else if (tsConfigFile.error && tsConfigFile.error.messageText) {\n    throw new BuildError(`tsconfig: ${tsConfigFile.error.messageText}`);\n\n  } else if (!tsConfigFile.config) {\n    throw new BuildError(`tsconfig: invalid config, \"${tsConfigPath}\"\"`);\n\n  } else {\n    const parsedConfig = ts.parseJsonConfigFileContent(\n      tsConfigFile.config,\n      ts.sys, context.rootDir,\n      {}, tsConfigPath);\n\n    const diagnostics = runTypeScriptDiagnostics(context, parsedConfig.errors);\n\n    if (diagnostics.length) {\n      printDiagnostics(context, DiagnosticsType.TypeScript, diagnostics, true, true);\n      throw new BuildError(`tsconfig: invalid config, \"${tsConfigPath}\"\"`);\n    }\n\n    config = {\n      options: parsedConfig.options,\n      fileNames: parsedConfig.fileNames,\n      raw: parsedConfig.raw\n    };\n  }\n\n  return config;\n}\n\nexport function transpileTsString(context: BuildContext, filePath: string, stringToTranspile: string, ) {\n  if (!cachedTsConfig) {\n    cachedTsConfig = getTsConfig(context);\n  }\n\n  const transpileOptions: ts.TranspileOptions = {\n    compilerOptions: cachedTsConfig.options,\n    fileName: filePath,\n    reportDiagnostics: true,\n  };\n\n  transpileOptions.compilerOptions.allowJs = true;\n  transpileOptions.compilerOptions.sourceMap = true;\n\n  // transpile this one module\n  return ts.transpileModule(stringToTranspile, transpileOptions);\n}\n\nexport function transformSource(filePath: string, input: string) {\n  if (isDeepLinkingFile(filePath) ) {\n    input = purgeDeepLinkDecorator(input);\n  } else if (filePath === getStringPropertyValue(Constants.ENV_APP_NG_MODULE_PATH) && !hasExistingDeepLinkConfig(filePath, input)) {\n    const deepLinkString = convertDeepLinkConfigEntriesToString(getParsedDeepLinkConfig());\n    input = getUpdatedAppNgModuleContentWithDeepLinkConfig(filePath, input, deepLinkString);\n  }\n  return input;\n}\n\nexport function copyOriginalSourceFiles(fileCache: FileCache) {\n  const deepLinkFiles = filterTypescriptFilesForDeepLinks(fileCache);\n  const appNgModule = fileCache.get(getStringPropertyValue(Constants.ENV_APP_NG_MODULE_PATH));\n  deepLinkFiles.push(appNgModule);\n  deepLinkFiles.forEach(deepLinkFile => {\n    fileCache.set(deepLinkFile.path + inMemoryFileCopySuffix, {\n      path: deepLinkFile.path + inMemoryFileCopySuffix,\n      content: deepLinkFile.content\n    });\n  });\n}\n\nexport function resetSourceFiles(fileCache: FileCache) {\n  fileCache.getAll().forEach(file => {\n    if (path.extname(file.path) === `.ts${inMemoryFileCopySuffix}`) {\n      const originalExtension = changeExtension(file.path, '.ts');\n      fileCache.set(originalExtension, {\n        path: originalExtension,\n        content: file.content\n      });\n      fileCache.getRawStore().delete(file.path);\n    }\n  });\n}\n\nexport const inMemoryFileCopySuffix = 'original';\n\nlet cachedProgram: ts.Program = null;\nlet cachedTsConfig: TsConfig = null;\n\nexport function getTsConfigPath(context: BuildContext) {\n  return process.env[Constants.ENV_TS_CONFIG];\n}\n\nexport interface TsConfig {\n  options: ts.CompilerOptions;\n  fileNames: string[];\n  raw: any;\n}\n\nexport interface TranspileWorkerConfig {\n  configFile: string;\n  writeInMemory: boolean;\n  sourceMaps: boolean;\n  cache: boolean;\n  inlineTemplate: boolean;\n  useTransforms: boolean;\n}\n"
  },
  {
    "path": "src/uglifyjs.ts",
    "content": "import * as Uglify from 'uglify-es';\n\nimport { Logger } from './logger/logger';\nimport { fillConfigDefaults, generateContext, getUserConfigFile } from './util/config';\nimport { BuildError } from './util/errors';\nimport { readFileAsync, writeFileAsync } from './util/helpers';\nimport { BuildContext, TaskInfo } from './util/interfaces';\nimport { runWorker } from './worker-client';\n\n\n\nexport function uglifyjs(context: BuildContext, configFile?: string) {\n  configFile = getUserConfigFile(context, taskInfo, configFile);\n\n  const logger = new Logger('uglify');\n\n  return runWorker('uglifyjs', 'uglifyjsWorker', context, configFile)\n    .then(() => {\n      logger.finish();\n    })\n    .catch((err: BuildError) => {\n      throw logger.fail(new BuildError(err));\n    });\n}\n\nexport function uglifyjsWorker(context: BuildContext, configFile: string): Promise<any> {\n  const uglifyJsConfig: UglifyJsConfig = fillConfigDefaults(configFile, taskInfo.defaultConfigFile);\n  if (!context) {\n    context = generateContext(context);\n  }\n  return uglifyjsWorkerImpl(context, uglifyJsConfig);\n}\n\nexport async function uglifyjsWorkerImpl(context: BuildContext, uglifyJsConfig: UglifyJsConfig) {\n  try {\n    const jsFilePaths = context.bundledFilePaths.filter(bundledFilePath => bundledFilePath.endsWith('.js'));\n    const promises = jsFilePaths.map(filePath => {\n      const sourceMapPath = filePath + '.map';\n      return runUglifyInternal(filePath, filePath, sourceMapPath, sourceMapPath, uglifyJsConfig);\n    });\n    return await Promise.all(promises);\n  } catch (ex) {\n    // uglify has it's own strange error format\n    const errorString = `${ex.message} in ${ex.filename} at line ${ex.line}, col ${ex.col}, pos ${ex.pos}`;\n    throw new BuildError(new Error(errorString));\n  }\n}\n\nasync function runUglifyInternal(sourceFilePath: string, destFilePath: string, sourceMapPath: string, destMapPath: string, configObject: any): Promise<any> {\n  const [sourceFileContent, sourceMapContent] = await Promise.all([readFileAsync(sourceFilePath), readFileAsync(sourceMapPath)]);\n  const uglifyConfig = Object.assign({}, configObject, {\n    sourceMap: {\n        content: sourceMapContent\n    }\n  });\n  const result = Uglify.minify(sourceFileContent, uglifyConfig) as any;\n  if (result.error) {\n    throw new BuildError(`Uglify failed: ${result.error.message}`);\n  }\n  return Promise.all([writeFileAsync(destFilePath, result.code), writeFileAsync(destMapPath, result.map)]);\n}\n\nexport const taskInfo: TaskInfo = {\n  fullArg: '--uglifyjs',\n  shortArg: '-u',\n  envVar: 'IONIC_UGLIFYJS',\n  packageConfig: 'ionic_uglifyjs',\n  defaultConfigFile: 'uglifyjs.config'\n};\n\n\nexport interface UglifyJsConfig {\n  // https://www.npmjs.com/package/uglify-js\n  sourceFile?: string;\n  destFileName?: string;\n  inSourceMap?: string;\n  outSourceMap?: string;\n  mangle?: boolean;\n  compress?: boolean;\n  comments?: boolean;\n}\n\nexport interface UglifyResponse {\n  code?: string;\n  map?: any;\n}\n"
  },
  {
    "path": "src/upgrade-scripts/add-default-ngmodules.spec.ts",
    "content": "import * as fs from 'fs';\nimport { join } from 'path';\n\nimport * as upgradeScript from './add-default-ngmodules';\nimport * as deeplinkUtils from '../deep-linking/util';\nimport { FileCache } from '../util/file-cache';\nimport * as globUtil from '../util/glob-util';\nimport * as helpers from '../util/helpers';\n\ndescribe('add default ngmodules upgrade script', () => {\n  describe('getTsFilePaths', () => {\n    it('should return a list of absolute file paths', () => {\n      const srcDirectory = join('Users', 'noone', 'this', 'path', 'is', 'fake', 'src');\n      const context = {\n        srcDir: srcDirectory\n      };\n\n      const knownFileOne = join(srcDirectory, 'pages', 'page-one', 'page-one.ts');\n      const knownFileTwo = join(srcDirectory, 'pages', 'page-two', 'page-two.ts');\n      const knownFileThree = join(srcDirectory, 'pages', 'page-three', 'page-three.ts');\n      const knownFileFour = join(srcDirectory, 'util', 'some-util.ts');\n      const globResults = [\n                            { absolutePath: knownFileOne},\n                            { absolutePath: knownFileTwo},\n                            { absolutePath: knownFileThree},\n                            { absolutePath: knownFileFour},\n                          ];\n      spyOn(globUtil, globUtil.globAll.name).and.returnValue(Promise.resolve(globResults));\n      const promise = upgradeScript.getTsFilePaths(context);\n\n      return promise.then((filePaths: string[]) => {\n        expect(filePaths.length).toEqual(4);\n        expect(filePaths[0]).toEqual(knownFileOne);\n        expect(filePaths[1]).toEqual(knownFileTwo);\n        expect(filePaths[2]).toEqual(knownFileThree);\n        expect(filePaths[3]).toEqual(knownFileFour);\n      });\n    });\n  });\n\n  describe('readTsFiles', () => {\n    it('should read the ts files', () => {\n      const context = {\n        fileCache: new FileCache()\n      };\n      const srcDirectory = join('Users', 'noone', 'this', 'path', 'is', 'fake', 'src');\n      const knownFileOne = join(srcDirectory, 'pages', 'page-one', 'page-one.ts');\n      const knownFileTwo = join(srcDirectory, 'pages', 'page-two', 'page-two.ts');\n      const knownFileThree = join(srcDirectory, 'pages', 'page-three', 'page-three.ts');\n      const knownFileFour = join(srcDirectory, 'util', 'some-util.ts');\n\n      const fileList = [knownFileOne, knownFileTwo, knownFileThree, knownFileFour];\n\n      spyOn(helpers, helpers.readFileAsync.name).and.callFake((filePath: string) => {\n        // just set the file content to the path name + 'content' to keep things simple\n        return Promise.resolve(filePath + 'content');\n      });\n\n      const promise = upgradeScript.readTsFiles(context, fileList);\n\n      return promise.then(() => {\n        // the files should be cached now\n        const fileOne = context.fileCache.get(knownFileOne);\n        expect(fileOne.content).toEqual(knownFileOne + 'content');\n\n        const fileTwo = context.fileCache.get(knownFileTwo);\n        expect(fileTwo.content).toEqual(knownFileTwo + 'content');\n\n        const fileThree = context.fileCache.get(knownFileThree);\n        expect(fileThree.content).toEqual(knownFileThree + 'content');\n\n        const fileFour = context.fileCache.get(knownFileFour);\n        expect(fileFour.content).toEqual(knownFileFour + 'content');\n      });\n    });\n  });\n\n  describe('generateAndWriteNgModules', () => {\n    it('should generate NgModules for only the pages with deeplink decorator AND if the module.ts file doesnt exist', () => {\n      const srcDirectory = join('Users', 'noone', 'this', 'path', 'is', 'fake', 'src');\n      const knownFileOne = join(srcDirectory, 'pages', 'page-one', 'page-one.ts');\n      const knownFileTwo = join(srcDirectory, 'pages', 'page-two', 'page-two.ts');\n      const knownFileThree = join(srcDirectory, 'pages', 'page-three', 'page-three.ts');\n      const knownFileThreeModule = join(srcDirectory, 'pages', 'page-three', 'page-three.module.ts');\n      const knownFileFour = join(srcDirectory, 'util', 'some-util.ts');\n      const knownFileFive = join(srcDirectory, 'pages', 'page-three', 'provider.ts');\n      const knownFileSix = join(srcDirectory, 'modals', 'modal-one', 'modal-one.ts');\n\n      const context = {\n        fileCache: new FileCache()\n      };\n\n      context.fileCache.set(knownFileOne, { path: knownFileOne, content: getClassContent('PageOne', 'page-one')});\n      context.fileCache.set(knownFileTwo, { path: knownFileTwo, content: getClassContent('PageTwo', 'page-two')});\n      context.fileCache.set(knownFileThree, { path: knownFileThree, content: getClassContent('PageThree', 'page-three')});\n      context.fileCache.set(knownFileThreeModule, { path: knownFileThreeModule, content: deeplinkUtils.generateDefaultDeepLinkNgModuleContent(knownFileThree, 'PageThree')});\n      context.fileCache.set(knownFileFour, { path: knownFileFour, content: `${knownFileFour} content`});\n      context.fileCache.set(knownFileFive, { path: knownFileFive, content: `${knownFileFive} content`});\n      context.fileCache.set(knownFileSix, { path: knownFileSix, content: getClassContent('ModalOne', 'modal-one')});\n\n      const ngModuleFileExtension = '.module.ts';\n\n      const knownNgModulePageOne = `\nimport { NgModule } from '@angular/core';\nimport { IonicPageModule } from 'ionic-angular';\nimport { PageOne } from './page-one';\n\n@NgModule({\n  declarations: [\n    PageOne,\n  ],\n  imports: [\n    IonicPageModule.forChild(PageOne)\n  ]\n})\nexport class PageOneModule {}\n\n`;\n\n      const knownNgModulePageTwo = `\nimport { NgModule } from '@angular/core';\nimport { IonicPageModule } from 'ionic-angular';\nimport { PageTwo } from './page-two';\n\n@NgModule({\n  declarations: [\n    PageTwo,\n  ],\n  imports: [\n    IonicPageModule.forChild(PageTwo)\n  ]\n})\nexport class PageTwoModule {}\n\n`;\n\n      const knownNgModuleModalPage = `\nimport { NgModule } from '@angular/core';\nimport { IonicPageModule } from 'ionic-angular';\nimport { ModalOne } from './modal-one';\n\n@NgModule({\n  declarations: [\n    ModalOne,\n  ],\n  imports: [\n    IonicPageModule.forChild(ModalOne)\n  ]\n})\nexport class ModalOneModule {}\n\n`;\n\n      spyOn(helpers, helpers.getStringPropertyValue.name).and.returnValue(ngModuleFileExtension);\n      const fsSpy = spyOn(fs, 'writeFileSync');\n\n      upgradeScript.generateAndWriteNgModules(context.fileCache);\n\n      expect(fsSpy.calls.count()).toEqual(3);\n      expect(fsSpy.calls.argsFor(0)[0]).toEqual(helpers.changeExtension(knownFileOne, ngModuleFileExtension));\n      expect(fsSpy.calls.argsFor(0)[1]).toEqual(knownNgModulePageOne);\n\n      expect(fsSpy.calls.argsFor(1)[0]).toEqual(helpers.changeExtension(knownFileTwo, ngModuleFileExtension));\n      expect(fsSpy.calls.argsFor(1)[1]).toEqual(knownNgModulePageTwo);\n\n      expect(fsSpy.calls.argsFor(2)[0]).toEqual(helpers.changeExtension(knownFileSix, ngModuleFileExtension));\n      expect(fsSpy.calls.argsFor(2)[1]).toEqual(knownNgModuleModalPage);\n    });\n  });\n});\n\nfunction getClassContent(className: string, folderName: string) {\n  return `\nimport { Component } from '@angular/core';\nimport { IonicPage, NavController } from 'ionic-angular';\n\n@IonicPage()\n@Component({\n  selector: '${folderName}',\n  templateUrl: './${folderName}.html'\n})\nexport class ${className} {\n\n  constructor(public navCtrl: NavController) {}\n\n}\n`;\n}\n"
  },
  {
    "path": "src/upgrade-scripts/add-default-ngmodules.ts",
    "content": "import { writeFileSync } from 'fs';\nimport { join } from 'path';\n\nimport { generateDefaultDeepLinkNgModuleContent, getDeepLinkDecoratorContentForSourceFile, getNgModulePathFromCorrespondingPage } from '../deep-linking/util';\nimport { generateContext } from '../util/config';\nimport * as Constants from '../util/constants';\nimport { FileCache } from '../util/file-cache';\nimport { globAll, GlobResult } from '../util/glob-util';\nimport { changeExtension, getStringPropertyValue, readFileAsync } from '../util/helpers';\nimport { BuildContext, File } from '../util/interfaces';\n\nimport { getTypescriptSourceFile } from '../util/typescript-utils';\n\nexport function getTsFilePaths(context: BuildContext) {\n  const tsFileGlobString = join(context.srcDir, '**', '*.ts');\n  return globAll([tsFileGlobString]).then((results: GlobResult[]) => {\n    return results.map(result => result.absolutePath);\n  });\n}\n\nexport function readTsFiles(context: BuildContext, tsFilePaths: string[]) {\n  const promises = tsFilePaths.map(tsFilePath => {\n    const promise = readFileAsync(tsFilePath);\n    promise.then((fileContent: string) => {\n      context.fileCache.set(tsFilePath, { path: tsFilePath, content: fileContent});\n    });\n    return promise;\n  });\n  return Promise.all(promises);\n}\n\nexport function generateAndWriteNgModules(fileCache: FileCache) {\n  fileCache.getAll().forEach(file => {\n    const sourceFile = getTypescriptSourceFile(file.path, file.content);\n    const deepLinkDecoratorData = getDeepLinkDecoratorContentForSourceFile(sourceFile);\n    if (deepLinkDecoratorData) {\n      // we have a valid DeepLink decorator\n      const correspondingNgModulePath = getNgModulePathFromCorrespondingPage(file.path);\n      const ngModuleFile = fileCache.get(correspondingNgModulePath);\n      if (!ngModuleFile) {\n        // the ngModule file does not exist, so go ahead and create a default one\n        const defaultNgModuleContent = generateDefaultDeepLinkNgModuleContent(file.path, deepLinkDecoratorData.className);\n        const ngModuleFilePath = changeExtension(file.path, getStringPropertyValue(Constants.ENV_NG_MODULE_FILE_NAME_SUFFIX));\n        writeFileSync(ngModuleFilePath, defaultNgModuleContent);\n      }\n    }\n  });\n}\n\nfunction run() {\n  const context = generateContext();\n\n  // find out what files to read\n  return getTsFilePaths(context).then((filePaths: string[]) => {\n    // read the files\n    return readTsFiles(context, filePaths);\n  }).then(() => {\n    generateAndWriteNgModules(context.fileCache);\n  });\n}\n\nrun();\n"
  },
  {
    "path": "src/util/clean-css-factory.ts",
    "content": "import * as cleanCss from 'clean-css';\n\nexport function getCleanCssInstance(options: cleanCss.Options) {\n  return new cleanCss(options);\n}\n\nexport interface CleanCssConfig {\n  // https://www.npmjs.com/package/clean-css\n  sourceFileName: string;\n  // sourceSourceMapName: string;\n  destFileName: string;\n  // options: cleanCss Options;\n  options?: cleanCss.Options;\n}\n"
  },
  {
    "path": "src/util/config.spec.ts",
    "content": "import { join, resolve } from 'path';\n\nimport * as helpers from './helpers';\nimport { BuildContext } from './interfaces';\nimport * as config from './config';\nimport * as Constants from './constants';\n\n\ndescribe('config', () => {\n\n  describe('config.generateContext', () => {\n\n    it('should set isWatch true with isWatch true context', () => {\n      const context = config.generateContext({\n        isWatch: true\n      });\n      expect(context.isWatch).toEqual(true);\n    });\n\n    it('should set isWatch false by default', () => {\n      const context = config.generateContext();\n      expect(context.isWatch).toEqual(false);\n    });\n\n    it('should set isProd false with isProd false context', () => {\n      const context = config.generateContext({\n        isProd: false\n      });\n      expect(context.isProd).toEqual(false);\n    });\n\n    it('should set default bundler when invalid value', () => {\n      const context = config.generateContext();\n      expect(context.bundler).toEqual('webpack');\n    });\n\n    it('should set default bundler when not set', () => {\n      const context = config.generateContext();\n      expect(context.bundler).toEqual('webpack');\n    });\n\n    it('should set isProd by default', () => {\n      const context = config.generateContext();\n      expect(context.isProd).toEqual(false);\n    });\n\n    it('should create an object when passed nothing', () => {\n      const context = config.generateContext();\n      expect(context).toBeDefined();\n    });\n\n    it('should set the correct defaults for a dev build', () => {\n      // arrange\n      const fakeConfig: any = { };\n      config.setProcessEnv(fakeConfig);\n\n      // act\n      const context = config.generateContext({\n        isProd: false\n      });\n\n      // assert\n      expect(context.isProd).toEqual(false);\n      expect(context.runAot).toEqual(false);\n      expect(context.runMinifyJs).toEqual(false);\n      expect(context.runMinifyCss).toEqual(false);\n      expect(context.optimizeJs).toEqual(false);\n      expect(fakeConfig[Constants.ENV_VAR_IONIC_ENV]).toEqual(Constants.ENV_VAR_DEV);\n\n      expect(fakeConfig[Constants.ENV_VAR_IONIC_AOT]).toEqual('false');\n      expect(fakeConfig[Constants.ENV_VAR_IONIC_MINIFY_JS]).toEqual('false');\n      expect(fakeConfig[Constants.ENV_VAR_IONIC_MINIFY_CSS]).toEqual('false');\n      expect(fakeConfig[Constants.ENV_VAR_IONIC_OPTIMIZE_JS]).toEqual('false');\n\n      expect(context.rootDir).toEqual(process.cwd());\n      expect(context.tmpDir).toEqual(join(process.cwd(), Constants.TMP_DIR));\n      expect(context.srcDir).toEqual(join(process.cwd(), Constants.SRC_DIR));\n      expect(fakeConfig[Constants.ENV_VAR_DEEPLINKS_DIR]).toEqual(context.srcDir);\n      expect(context.wwwDir).toEqual(join(process.cwd(), Constants.WWW_DIR));\n      expect(context.wwwIndex).toEqual('index.html');\n      expect(context.buildDir).toEqual(join(process.cwd(), Constants.WWW_DIR, Constants.BUILD_DIR));\n      expect(fakeConfig[Constants.ENV_VAR_FONTS_DIR]).toEqual(join(context.wwwDir, 'assets', 'fonts'));\n      expect(context.pagesDir).toEqual(join(context.srcDir, 'pages'));\n      expect(context.componentsDir).toEqual(join(context.srcDir, 'components'));\n      expect(context.directivesDir).toEqual(join(context.srcDir, 'directives'));\n      expect(context.pipesDir).toEqual(join(context.srcDir, 'pipes'));\n      expect(context.providersDir).toEqual(join(context.srcDir, 'providers'));\n      expect(context.nodeModulesDir).toEqual(join(process.cwd(), Constants.NODE_MODULES));\n      expect(context.ionicAngularDir).toEqual(join(process.cwd(), Constants.NODE_MODULES, Constants.IONIC_ANGULAR));\n      expect(fakeConfig[Constants.ENV_VAR_ANGULAR_CORE_DIR]).toEqual(join(process.cwd(), Constants.NODE_MODULES, Constants.AT_ANGULAR, 'core'));\n      expect(fakeConfig[Constants.ENV_VAR_TYPESCRIPT_DIR]).toEqual(join(process.cwd(), Constants.NODE_MODULES, Constants.TYPESCRIPT));\n      expect(context.coreCompilerFilePath).toEqual(join(context.ionicAngularDir, 'compiler'));\n      expect(context.coreDir).toEqual(context.ionicAngularDir);\n      expect(fakeConfig[Constants.ENV_VAR_RXJS_DIR]).toEqual(join(process.cwd(), Constants.NODE_MODULES, Constants.RXJS));\n      expect(fakeConfig[Constants.ENV_VAR_IONIC_ANGULAR_TEMPLATE_DIR]).toEqual(join(context.ionicAngularDir, 'templates'));\n      expect(context.platform).toEqual(null);\n      expect(context.target).toEqual(null);\n      expect(fakeConfig[Constants.ENV_VAR_IONIC_ANGULAR_ENTRY_POINT]).toEqual(join(context.ionicAngularDir, 'index.js'));\n      expect(fakeConfig[Constants.ENV_VAR_APP_SCRIPTS_DIR]).toEqual(join(__dirname, '..', '..'));\n      expect(fakeConfig[Constants.ENV_VAR_GENERATE_SOURCE_MAP]).toEqual('true');\n      expect(fakeConfig[Constants.ENV_VAR_SOURCE_MAP_TYPE]).toEqual(Constants.SOURCE_MAP_TYPE_EXPENSIVE);\n      expect(fakeConfig[Constants.ENV_TS_CONFIG]).toEqual(join(process.cwd(), 'tsconfig.json'));\n      expect(fakeConfig[Constants.ENV_READ_CONFIG_JSON]).toEqual('true');\n      expect(fakeConfig[Constants.ENV_APP_ENTRY_POINT]).toEqual(join(context.srcDir, 'app', 'main.ts'));\n      expect(fakeConfig[Constants.ENV_APP_NG_MODULE_PATH]).toEqual(join(context.srcDir, 'app', 'app.module.ts'));\n      expect(fakeConfig[Constants.ENV_APP_NG_MODULE_CLASS]).toEqual('AppModule');\n      expect(fakeConfig[Constants.ENV_GLOB_UTIL]).toEqual(join(fakeConfig[Constants.ENV_VAR_APP_SCRIPTS_DIR], 'dist', 'util', 'glob-util.js'));\n      expect(fakeConfig[Constants.ENV_CLEAN_BEFORE_COPY]).toBeFalsy();\n      expect(fakeConfig[Constants.ENV_OUTPUT_JS_FILE_NAME]).toEqual('main.js');\n      expect(fakeConfig[Constants.ENV_OUTPUT_CSS_FILE_NAME]).toEqual('main.css');\n      expect(fakeConfig[Constants.ENV_WEBPACK_FACTORY]).toEqual(join(fakeConfig[Constants.ENV_VAR_APP_SCRIPTS_DIR], 'dist', 'webpack', 'ionic-webpack-factory.js'));\n      expect(fakeConfig[Constants.ENV_WEBPACK_LOADER]).toEqual(join(fakeConfig[Constants.ENV_VAR_APP_SCRIPTS_DIR], 'dist', 'webpack', 'loader.js'));\n      expect(fakeConfig[Constants.ENV_AOT_WRITE_TO_DISK]).toBeFalsy();\n      expect(fakeConfig[Constants.ENV_PRINT_WEBPACK_DEPENDENCY_TREE]).toBeFalsy();\n      expect(fakeConfig[Constants.ENV_TYPE_CHECK_ON_LINT]).toBeFalsy();\n      expect(fakeConfig[Constants.ENV_BAIL_ON_LINT_ERROR]).toBeFalsy();\n      expect(fakeConfig[Constants.ENV_ENABLE_LINT]).toEqual('true');\n      expect(fakeConfig[Constants.ENV_DISABLE_LOGGING]).toBeFalsy();\n      expect(fakeConfig[Constants.ENV_START_WATCH_TIMEOUT]).toEqual('3000');\n      expect(fakeConfig[Constants.ENV_NG_MODULE_FILE_NAME_SUFFIX]).toEqual('.module.ts');\n      expect(fakeConfig[Constants.ENV_POLYFILL_FILE_NAME]).toEqual('polyfills.js');\n\n      expect(fakeConfig[Constants.ENV_ACTION_SHEET_CONTROLLER_CLASSNAME]).toEqual('ActionSheetController');\n      expect(fakeConfig[Constants.ENV_ACTION_SHEET_CONTROLLER_PATH]).toEqual(join(context.ionicAngularDir, 'components', 'action-sheet', 'action-sheet-controller.js'));\n      expect(fakeConfig[Constants.ENV_ACTION_SHEET_VIEW_CONTROLLER_PATH]).toEqual(join(context.ionicAngularDir, 'components', 'action-sheet', 'action-sheet.js'));\n      expect(fakeConfig[Constants.ENV_ACTION_SHEET_COMPONENT_PATH]).toEqual(join(context.ionicAngularDir, 'components', 'action-sheet', 'action-sheet-component.js'));\n      expect(fakeConfig[Constants.ENV_ACTION_SHEET_COMPONENT_FACTORY_PATH]).toEqual(join(context.ionicAngularDir, 'components', 'action-sheet', 'action-sheet-component.ngfactory.js'));\n\n      expect(fakeConfig[Constants.ENV_ALERT_CONTROLLER_CLASSNAME]).toEqual('AlertController');\n      expect(fakeConfig[Constants.ENV_ALERT_CONTROLLER_PATH]).toEqual(join(context.ionicAngularDir, 'components', 'alert', 'alert-controller.js'));\n      expect(fakeConfig[Constants.ENV_ALERT_VIEW_CONTROLLER_PATH]).toEqual(join(context.ionicAngularDir, 'components', 'alert', 'alert.js'));\n      expect(fakeConfig[Constants.ENV_ALERT_COMPONENT_PATH]).toEqual(join(context.ionicAngularDir, 'components', 'alert', 'alert-component.js'));\n      expect(fakeConfig[Constants.ENV_ALERT_COMPONENT_FACTORY_PATH]).toEqual(join(context.ionicAngularDir, 'components', 'alert', 'alert-component.ngfactory.js'));\n\n      expect(fakeConfig[Constants.ENV_APP_ROOT_COMPONENT_PATH]).toEqual(join(context.ionicAngularDir, 'components', 'app', 'app-root.js'));\n\n      expect(fakeConfig[Constants.ENV_LOADING_CONTROLLER_CLASSNAME]).toEqual('LoadingController');\n      expect(fakeConfig[Constants.ENV_LOADING_CONTROLLER_PATH]).toEqual(join(context.ionicAngularDir, 'components', 'loading', 'loading-controller.js'));\n      expect(fakeConfig[Constants.ENV_LOADING_VIEW_CONTROLLER_PATH]).toEqual(join(context.ionicAngularDir, 'components', 'loading', 'loading.js'));\n      expect(fakeConfig[Constants.ENV_LOADING_COMPONENT_PATH]).toEqual(join(context.ionicAngularDir, 'components', 'loading', 'loading-component.js'));\n      expect(fakeConfig[Constants.ENV_LOADING_COMPONENT_FACTORY_PATH]).toEqual(join(context.ionicAngularDir, 'components', 'loading', 'loading-component.ngfactory.js'));\n\n      expect(fakeConfig[Constants.ENV_MODAL_CONTROLLER_CLASSNAME]).toEqual('ModalController');\n      expect(fakeConfig[Constants.ENV_MODAL_CONTROLLER_PATH]).toEqual(join(context.ionicAngularDir, 'components', 'modal', 'modal-controller.js'));\n      expect(fakeConfig[Constants.ENV_MODAL_VIEW_CONTROLLER_PATH]).toEqual(join(context.ionicAngularDir, 'components', 'modal', 'modal.js'));\n      expect(fakeConfig[Constants.ENV_MODAL_COMPONENT_PATH]).toEqual(join(context.ionicAngularDir, 'components', 'modal', 'modal-component.js'));\n      expect(fakeConfig[Constants.ENV_MODAL_COMPONENT_FACTORY_PATH]).toEqual(join(context.ionicAngularDir, 'components', 'modal', 'modal-component.ngfactory.js'));\n\n      expect(fakeConfig[Constants.ENV_PICKER_CONTROLLER_CLASSNAME]).toEqual('PickerController');\n      expect(fakeConfig[Constants.ENV_PICKER_CONTROLLER_PATH]).toEqual(join(context.ionicAngularDir, 'components', 'picker', 'picker-controller.js'));\n      expect(fakeConfig[Constants.ENV_PICKER_VIEW_CONTROLLER_PATH]).toEqual(join(context.ionicAngularDir, 'components', 'picker', 'picker.js'));\n      expect(fakeConfig[Constants.ENV_PICKER_COMPONENT_PATH]).toEqual(join(context.ionicAngularDir, 'components', 'picker', 'picker-component.js'));\n      expect(fakeConfig[Constants.ENV_PICKER_COMPONENT_FACTORY_PATH]).toEqual(join(context.ionicAngularDir, 'components', 'picker', 'picker-component.ngfactory.js'));\n\n      expect(fakeConfig[Constants.ENV_POPOVER_CONTROLLER_CLASSNAME]).toEqual('PopoverController');\n      expect(fakeConfig[Constants.ENV_POPOVER_CONTROLLER_PATH]).toEqual(join(context.ionicAngularDir, 'components', 'popover', 'popover-controller.js'));\n      expect(fakeConfig[Constants.ENV_POPOVER_VIEW_CONTROLLER_PATH]).toEqual(join(context.ionicAngularDir, 'components', 'popover', 'popover.js'));\n      expect(fakeConfig[Constants.ENV_POPOVER_COMPONENT_PATH]).toEqual(join(context.ionicAngularDir, 'components', 'popover', 'popover-component.js'));\n      expect(fakeConfig[Constants.ENV_POPOVER_COMPONENT_FACTORY_PATH]).toEqual(join(context.ionicAngularDir, 'components', 'popover', 'popover-component.ngfactory.js'));\n\n      expect(fakeConfig[Constants.ENV_TOAST_CONTROLLER_CLASSNAME]).toEqual('ToastController');\n      expect(fakeConfig[Constants.ENV_TOAST_CONTROLLER_PATH]).toEqual(join(context.ionicAngularDir, 'components', 'toast', 'toast-controller.js'));\n      expect(fakeConfig[Constants.ENV_TOAST_VIEW_CONTROLLER_PATH]).toEqual(join(context.ionicAngularDir, 'components', 'toast', 'toast.js'));\n      expect(fakeConfig[Constants.ENV_TOAST_COMPONENT_PATH]).toEqual(join(context.ionicAngularDir, 'components', 'toast', 'toast-component.js'));\n      expect(fakeConfig[Constants.ENV_TOAST_COMPONENT_FACTORY_PATH]).toEqual(join(context.ionicAngularDir, 'components', 'toast', 'toast-component.ngfactory.js'));\n\n      expect(fakeConfig[Constants.ENV_PARSE_DEEPLINKS]).toBeTruthy();\n      expect(fakeConfig[Constants.ENV_SKIP_IONIC_ANGULAR_VERSION]).toEqual('false');\n      expect(context.bundler).toEqual('webpack');\n    });\n\n    it('should set defaults for a prod build', () => {\n      // arrange\n      const fakeConfig: any = { };\n      config.setProcessEnv(fakeConfig);\n\n      // act\n      const context = config.generateContext({\n        isProd: true\n      });\n\n      // assert\n      expect(context.isProd).toEqual(true);\n      expect(context.runAot).toEqual(true);\n      expect(context.runMinifyJs).toEqual(true);\n      expect(context.runMinifyCss).toEqual(true);\n      expect(context.optimizeJs).toEqual(true);\n      expect(fakeConfig[Constants.ENV_VAR_IONIC_AOT]).toEqual('true');\n      expect(fakeConfig[Constants.ENV_VAR_IONIC_MINIFY_JS]).toEqual('true');\n      expect(fakeConfig[Constants.ENV_VAR_IONIC_MINIFY_CSS]).toEqual('true');\n      expect(fakeConfig[Constants.ENV_VAR_IONIC_OPTIMIZE_JS]).toEqual('true');\n      expect(fakeConfig[Constants.ENV_VAR_IONIC_ENV]).toEqual(Constants.ENV_VAR_PROD);\n      expect(fakeConfig[Constants.ENV_VAR_GENERATE_SOURCE_MAP]).toBeFalsy();\n    });\n\n    it('should override console', () => {\n      const originalDebug = console.debug;\n      const originalError = console.error;\n      const originalInfo = console.info;\n      const originalLog = console.log;\n      const originalTrace = console.trace;\n      const originalWarn = console.warn;\n\n      const fakeConfig: any = { };\n      config.setProcessEnv(fakeConfig);\n\n      spyOn(helpers, helpers.getBooleanPropertyValue.name).and.returnValue(true);\n\n      config.generateContext({\n        isProd: true\n      });\n\n      expect(console.debug).not.toEqual(originalDebug);\n      expect(console.error).not.toEqual(originalError);\n      expect(console.info).not.toEqual(originalInfo);\n      expect(console.log).not.toEqual(originalLog);\n      expect(console.trace).not.toEqual(originalTrace);\n      expect(console.warn).not.toEqual(originalWarn);\n    });\n\n  });\n\n  describe('config.replacePathVars', () => {\n    it('should interpolated value when string', () => {\n      const context = {\n        srcDir: 'src',\n      };\n\n      const rtn = config.replacePathVars(context, '{{SRC}}');\n      expect(rtn).toEqual('src');\n    });\n\n    it('should interpolated values in string array', () => {\n      const context = {\n        wwwDir: 'www',\n        srcDir: 'src',\n      };\n\n      const filePaths = ['{{SRC}}', '{{WWW}}'];\n      const rtn = config.replacePathVars(context, filePaths);\n      expect(rtn).toEqual(['src', 'www']);\n    });\n\n    it('should interpolated values in key value pair', () => {\n      const context = {\n        wwwDir: 'www',\n        srcDir: 'src',\n      };\n\n      const filePaths = {\n        src: '{{SRC}}',\n        www: '{{WWW}}'\n      };\n\n      const rtn = config.replacePathVars(context, filePaths);\n      expect(rtn).toEqual({\n        src: 'src',\n        www: 'www'\n      });\n    });\n\n  });\n\n  describe('config.getConfigValue', () => {\n\n    it('should get arg full value', () => {\n      config.addArgv('--full');\n      config.addArgv('fullArgValue');\n      config.addArgv('-s');\n      config.addArgv('shortArgValue');\n      config.setProcessEnvVar('ENV_VAR', 'myProcessEnvVar');\n      config.setAppPackageJsonData({ config: { config_prop: 'myPackageConfigVal' } });\n      const val = config.getConfigValue(context, '--full', '-s', 'ENV_VAR', 'config_prop', 'defaultValue');\n      expect(val).toEqual('fullArgValue');\n    });\n\n    it('should get arg short value', () => {\n      config.addArgv('-s');\n      config.addArgv('shortArgValue');\n      config.setProcessEnvVar('ENV_VAR', 'myProcessEnvVar');\n      config.setAppPackageJsonData({ config: { config_prop: 'myPackageConfigVal' } });\n      const val = config.getConfigValue(context, '--full', '-s', 'ENV_VAR', 'config_prop', 'defaultValue');\n      expect(val).toEqual('shortArgValue');\n    });\n\n    it('should get envVar value', () => {\n      config.setProcessEnvVar('ENV_VAR', 'myProcessEnvVar');\n      config.setAppPackageJsonData({ config: { config_prop: 'myPackageConfigVal' } });\n      const val = config.getConfigValue(context, '--full', '-s', 'ENV_VAR', 'config_prop', 'defaultValue');\n      expect(val).toEqual('myProcessEnvVar');\n    });\n\n    it('should get package.json config value', () => {\n      config.setAppPackageJsonData({ config: { config_prop: 'myPackageConfigVal' } });\n      const val = config.getConfigValue(context, '--full', '-s', 'ENV_VAR', 'config_prop', 'defaultValue');\n      expect(val).toEqual('myPackageConfigVal');\n    });\n\n    it('should get default value', () => {\n      const val = config.getConfigValue(context, '--full', '-s', 'ENV_VAR', 'config_prop', 'defaultValue');\n      expect(val).toEqual('defaultValue');\n    });\n\n  });\n\n  describe('config.bundlerStrategy', () => {\n\n    it('should get webpack with invalid env var', () => {\n      config.setProcessEnv({\n        ionic_bundler: 'bobsBundler'\n      });\n      const bundler = config.bundlerStrategy(context);\n      expect(bundler).toEqual('webpack');\n    });\n\n    it('should get webpack by default', () => {\n      const bundler = config.bundlerStrategy(context);\n      expect(bundler).toEqual('webpack');\n    });\n\n  });\n\n  describe('config.getUserConfigFile', () => {\n\n    it('should get config from package.json config', () => {\n      config.setAppPackageJsonData({\n        config: { ionic_config: 'myconfig.js' }\n      });\n\n      const userConfigFile: string = null;\n      const context = { rootDir: process.cwd() };\n      const taskInfo = { fullArg: '--full', shortArg: '-s', defaultConfigFile: 'default.config.js', envVar: 'IONIC_CONFIG', packageConfig: 'ionic_config' };\n      const rtn = config.getUserConfigFile(context, taskInfo, userConfigFile);\n      expect(rtn).toEqual(resolve('myconfig.js'));\n    });\n\n    it('should get config from env var', () => {\n      config.setProcessEnv({\n        IONIC_CONFIG: 'myconfig.js'\n      });\n\n      const userConfigFile: string = null;\n      const context = { rootDir: process.cwd() };\n      const taskInfo = { fullArg: '--full', shortArg: '-s', defaultConfigFile: 'default.config.js', envVar: 'IONIC_CONFIG', packageConfig: 'ionic_config' };\n      const rtn = config.getUserConfigFile(context, taskInfo, userConfigFile);\n      expect(rtn).toEqual(resolve('myconfig.js'));\n    });\n\n    it('should get config from short arg', () => {\n      config.addArgv('-s');\n      config.addArgv('myconfig.js');\n\n      const userConfigFile: string = null;\n      const context = { rootDir: process.cwd() };\n      const taskInfo = { fullArg: '--full', shortArg: '-s', defaultConfigFile: 'default.config.js', envVar: 'IONIC_CONFIG', packageConfig: 'ionic_config' };\n      const rtn = config.getUserConfigFile(context, taskInfo, userConfigFile);\n      expect(rtn).toEqual(resolve('myconfig.js'));\n    });\n\n    it('should get config from full arg', () => {\n      config.addArgv('--full');\n      config.addArgv('myconfig.js');\n\n      const userConfigFile: string = null;\n      const context = { rootDir: process.cwd() };\n      const taskInfo = { fullArg: '--full', shortArg: '-s', defaultConfigFile: 'default.config.js', envVar: 'IONIC_CONFIG', packageConfig: 'ionic_config' };\n      const rtn = config.getUserConfigFile(context, taskInfo, userConfigFile);\n      expect(rtn).toEqual(resolve('myconfig.js'));\n    });\n\n    it('should get userConfigFile', () => {\n      const userConfigFile = 'myconfig.js';\n      const context = { rootDir: process.cwd() };\n      const taskInfo = { fullArg: '--full', shortArg: '-s', defaultConfigFile: 'default.config.js', envVar: 'IONIC_CONFIG', packageConfig: 'ionic_config' };\n      const rtn = config.getUserConfigFile(context, taskInfo, userConfigFile);\n      expect(rtn).toEqual(resolve('myconfig.js'));\n    });\n\n    it('should not get a user config', () => {\n      const userConfigFile: string = null;\n      const context = { rootDir: process.cwd() };\n      const taskInfo = { fullArg: '--full', shortArg: '-s', defaultConfigFile: 'default.config.js', envVar: 'IONIC_CONFIG', packageConfig: 'ionic_config' };\n      const rtn = config.getUserConfigFile(context, taskInfo, userConfigFile);\n      expect(rtn).toEqual(null);\n    });\n\n  });\n\n  describe('config.hasArg function', () => {\n    it('should return false when a match is not found', () => {\n      const result = config.hasArg('--full', '-f');\n      expect(result).toBeFalsy();\n    });\n\n    it('should match on a fullname arg', () => {\n      config.addArgv('--full');\n\n      const result = config.hasArg('--full');\n      expect(result).toBeTruthy();\n    });\n\n    it('should match on a shortname arg', () => {\n      config.addArgv('-f');\n\n      const result = config.hasArg('--full', '-f');\n      expect(result).toBeTruthy();\n    });\n\n    it('should compare fullnames as case insensitive', () => {\n      config.addArgv('--full');\n      config.addArgv('--TEST');\n\n      const result = config.hasArg('--Full');\n      const result2 = config.hasArg('--test');\n      expect(result).toBeTruthy();\n      expect(result2).toBeTruthy();\n    });\n\n    it('should compare shortnames as case insensitive', () => {\n      config.addArgv('-f');\n      config.addArgv('-T');\n\n      const result = config.hasArg('-F');\n      const result2 = config.hasArg('-t');\n      expect(result).toBeTruthy();\n      expect(result2).toBeTruthy();\n    });\n  });\n\n  let context: BuildContext;\n  beforeEach(() => {\n    config.setProcessArgs(['node', 'ionic-app-scripts']);\n    config.setProcessEnv({});\n    config.setCwd('');\n    config.setAppPackageJsonData(null);\n    context = config.generateContext({});\n  });\n\n});\n"
  },
  {
    "path": "src/util/config.ts",
    "content": "import { join, resolve } from 'path';\nimport { accessSync, readJSONSync, statSync } from 'fs-extra';\n\nimport { Logger } from '../logger/logger';\nimport { BuildContext, TaskInfo } from './interfaces';\nimport { getBooleanPropertyValue, objectAssign } from './helpers';\nimport { FileCache } from './file-cache';\nimport * as Constants from './constants';\n\n/**\n * Create a context object which is used by all the build tasks.\n * Filling the config data uses the following hierarchy, which will\n * keep going down the list until it, or if it, finds data.\n *\n * 1) Get from the passed in context variable\n * 2) Get from the config file set using the command-line args\n * 3) Get from environment variable\n * 4) Get from package.json config property\n * 5) Get environment variables\n *\n * Lastly, Ionic's default configs will always fill in any data\n * which is missing from the user's data.\n */\nexport function generateContext(context?: BuildContext): BuildContext {\n  if (!context) {\n    context = {};\n  }\n\n  if (!context.fileCache) {\n     context.fileCache = new FileCache();\n  }\n\n  context.isProd = [\n    context.isProd,\n    hasArg('--prod')\n  ].find(val => typeof val === 'boolean');\n\n  setProcessEnvVar(Constants.ENV_VAR_IONIC_ENV, (context.isProd ? Constants.ENV_VAR_PROD : Constants.ENV_VAR_DEV));\n\n  // If context is prod then the following flags must be set to true\n  context.runAot = [\n    context.runAot,\n    context.isProd || hasArg('--aot'),\n  ].find(val => typeof val === 'boolean');\n\n  context.runMinifyJs = [\n    context.runMinifyJs,\n    context.isProd || hasArg('--minifyJs')\n  ].find(val => typeof val === 'boolean');\n\n  context.runMinifyCss = [\n    context.runMinifyCss,\n    context.isProd || hasArg('--minifyCss')\n  ].find(val => typeof val === 'boolean');\n\n  context.optimizeJs = [\n    context.optimizeJs,\n    context.isProd || hasArg('--optimizeJs')\n  ].find(val => typeof val === 'boolean');\n\n  if (typeof context.isWatch !== 'boolean') {\n    context.isWatch = hasArg('--watch');\n  }\n\n  setProcessEnvVar(Constants.ENV_VAR_IONIC_AOT, `${context.runAot}`);\n  Logger.debug(`${Constants.ENV_VAR_IONIC_AOT} set to ${context.runAot}`);\n\n  setProcessEnvVar(Constants.ENV_VAR_IONIC_MINIFY_JS, `${context.runMinifyJs}`);\n  Logger.debug(`${Constants.ENV_VAR_IONIC_MINIFY_JS} set to ${context.runMinifyJs}`);\n\n  setProcessEnvVar(Constants.ENV_VAR_IONIC_MINIFY_CSS, `${context.runMinifyCss}`);\n  Logger.debug(`${Constants.ENV_VAR_IONIC_MINIFY_CSS} set to ${context.runMinifyCss}`);\n\n  setProcessEnvVar(Constants.ENV_VAR_IONIC_OPTIMIZE_JS, `${context.optimizeJs}`);\n  Logger.debug(`${Constants.ENV_VAR_IONIC_OPTIMIZE_JS} set to ${context.optimizeJs}`);\n\n  setProcessEnvVar(Constants.ENV_VAR_IONIC_MINIFY_JS, `${context.runMinifyJs}`);\n  Logger.debug(`${Constants.ENV_VAR_IONIC_MINIFY_JS} set to ${context.runMinifyJs}`);\n\n  context.rootDir = resolve(context.rootDir || getConfigValue(context, '--rootDir', null, Constants.ENV_VAR_ROOT_DIR, Constants.ENV_VAR_ROOT_DIR.toLowerCase(), processCwd));\n  setProcessEnvVar(Constants.ENV_VAR_ROOT_DIR, context.rootDir);\n  Logger.debug(`rootDir set to ${context.rootDir}`);\n\n  context.tmpDir = resolve(context.tmpDir || getConfigValue(context, '--tmpDir', null, Constants.ENV_VAR_TMP_DIR, Constants.ENV_VAR_TMP_DIR.toLowerCase(), join(context.rootDir, Constants.TMP_DIR)));\n  setProcessEnvVar(Constants.ENV_VAR_TMP_DIR, context.tmpDir);\n  Logger.debug(`tmpDir set to ${context.tmpDir}`);\n\n  context.srcDir = resolve(context.srcDir || getConfigValue(context, '--srcDir', null, Constants.ENV_VAR_SRC_DIR, Constants.ENV_VAR_SRC_DIR.toLowerCase(), join(context.rootDir, Constants.SRC_DIR)));\n  setProcessEnvVar(Constants.ENV_VAR_SRC_DIR, context.srcDir);\n  Logger.debug(`srcDir set to ${context.srcDir}`);\n\n  const deepLinksDir = resolve(getConfigValue(context, '--deepLinksDir', null, Constants.ENV_VAR_DEEPLINKS_DIR, Constants.ENV_VAR_DEEPLINKS_DIR.toLowerCase(), context.srcDir));\n  setProcessEnvVar(Constants.ENV_VAR_DEEPLINKS_DIR, deepLinksDir);\n  Logger.debug(`deepLinksDir set to ${deepLinksDir}`);\n\n  context.wwwDir = resolve(context.wwwDir || getConfigValue(context, '--wwwDir', null, Constants.ENV_VAR_WWW_DIR, Constants.ENV_VAR_WWW_DIR.toLowerCase(), join(context.rootDir, Constants.WWW_DIR)));\n  setProcessEnvVar(Constants.ENV_VAR_WWW_DIR, context.wwwDir);\n  Logger.debug(`wwwDir set to ${context.wwwDir}`);\n\n  context.wwwIndex = getConfigValue(context, '--wwwIndex', null, Constants.ENV_VAR_HTML_TO_SERVE, Constants.ENV_VAR_HTML_TO_SERVE.toLowerCase(), 'index.html');\n  setProcessEnvVar(Constants.ENV_VAR_HTML_TO_SERVE, context.wwwIndex);\n  Logger.debug(`wwwIndex set to ${context.wwwIndex}`);\n\n  context.buildDir = resolve(context.buildDir || getConfigValue(context, '--buildDir', null, Constants.ENV_VAR_BUILD_DIR, Constants.ENV_VAR_BUILD_DIR.toLowerCase(), join(context.wwwDir, Constants.BUILD_DIR)));\n  setProcessEnvVar(Constants.ENV_VAR_BUILD_DIR, context.buildDir);\n  Logger.debug(`buildDir set to ${context.buildDir}`);\n\n  const fontsDir = resolve(getConfigValue(context, '--fontsDir', null, Constants.ENV_VAR_FONTS_DIR, Constants.ENV_VAR_FONTS_DIR.toLowerCase(), join(context.wwwDir, 'assets', 'fonts')));\n  setProcessEnvVar(Constants.ENV_VAR_FONTS_DIR, fontsDir);\n  Logger.debug(`fontsDir set to ${fontsDir}`);\n\n  context.sourcemapDir = resolve(context.sourcemapDir || getConfigValue(context, '--sourcemapDir', null, Constants.ENV_VAR_SOURCEMAP_DIR, Constants.ENV_VAR_SOURCEMAP_DIR.toLowerCase(), Constants.SOURCEMAP_DIR));\n  setProcessEnvVar(Constants.ENV_VAR_SOURCEMAP_DIR, context.sourcemapDir);\n  Logger.debug(`sourcemapDir set to ${context.sourcemapDir}`);\n\n  context.pagesDir = resolve(context.pagesDir || getConfigValue(context, '--pagesDir', null, Constants.ENV_VAR_PAGES_DIR, Constants.ENV_VAR_PAGES_DIR.toLowerCase(), join(context.srcDir, 'pages')));\n  setProcessEnvVar(Constants.ENV_VAR_PAGES_DIR, context.pagesDir);\n  Logger.debug(`pagesDir set to ${context.pagesDir}`);\n\n  context.componentsDir = resolve(context.componentsDir || getConfigValue(context, '--componentsDir', null, Constants.ENV_VAR_COMPONENTS_DIR, Constants.ENV_VAR_COMPONENTS_DIR.toLowerCase(), join(context.srcDir, 'components')));\n  setProcessEnvVar(Constants.ENV_VAR_COMPONENTS_DIR, context.componentsDir);\n  Logger.debug(`componentsDir set to ${context.componentsDir}`);\n\n  context.directivesDir = resolve(context.directivesDir || getConfigValue(context, '--directivesDir', null, Constants.ENV_VAR_DIRECTIVES_DIR, Constants.ENV_VAR_DIRECTIVES_DIR.toLowerCase(), join(context.srcDir, 'directives')));\n  setProcessEnvVar(Constants.ENV_VAR_DIRECTIVES_DIR, context.directivesDir);\n  Logger.debug(`directivesDir set to ${context.directivesDir}`);\n\n  context.pipesDir = resolve(context.pipesDir || getConfigValue(context, '--pipesDir', null, Constants.ENV_VAR_PIPES_DIR, Constants.ENV_VAR_PIPES_DIR.toLowerCase(), join(context.srcDir, 'pipes')));\n  setProcessEnvVar(Constants.ENV_VAR_PIPES_DIR, context.pipesDir);\n  Logger.debug(`pipesDir set to ${context.pipesDir}`);\n\n  context.providersDir = resolve(context.providersDir || getConfigValue(context, '--providersDir', null, Constants.ENV_VAR_PROVIDERS_DIR, Constants.ENV_VAR_PROVIDERS_DIR.toLowerCase(), join(context.srcDir, 'providers')));\n  setProcessEnvVar(Constants.ENV_VAR_PROVIDERS_DIR, context.providersDir);\n  Logger.debug(`providersDir set to ${context.providersDir}`);\n\n  context.nodeModulesDir = join(context.rootDir, Constants.NODE_MODULES);\n  setProcessEnvVar(Constants.ENV_VAR_NODE_MODULES_DIR, context.nodeModulesDir);\n  Logger.debug(`nodeModulesDir set to ${context.nodeModulesDir}`);\n\n  context.ionicAngularDir = resolve(context.ionicAngularDir || getConfigValue(context, '--ionicAngularDir', null, Constants.ENV_VAR_IONIC_ANGULAR_DIR, Constants.ENV_VAR_IONIC_ANGULAR_DIR.toLowerCase(), join(context.nodeModulesDir, Constants.IONIC_ANGULAR)));\n  setProcessEnvVar(Constants.ENV_VAR_IONIC_ANGULAR_DIR, context.ionicAngularDir);\n  Logger.debug(`ionicAngularDir set to ${context.ionicAngularDir}`);\n\n  const angularDir = resolve(getConfigValue(context, '--angularDir', null, Constants.ENV_VAR_ANGULAR_CORE_DIR, Constants.ENV_VAR_ANGULAR_CORE_DIR.toLowerCase(), join(context.nodeModulesDir, Constants.AT_ANGULAR, 'core')));\n  setProcessEnvVar(Constants.ENV_VAR_ANGULAR_CORE_DIR, angularDir);\n  Logger.debug(`angularDir set to ${angularDir}`);\n  context.angularCoreDir = angularDir;\n\n  const typescriptDir = resolve(getConfigValue(context, '--typescriptDir', null, Constants.ENV_VAR_TYPESCRIPT_DIR, Constants.ENV_VAR_TYPESCRIPT_DIR.toLowerCase(), join(context.nodeModulesDir, Constants.TYPESCRIPT)));\n  setProcessEnvVar(Constants.ENV_VAR_TYPESCRIPT_DIR, typescriptDir);\n  Logger.debug(`typescriptDir set to ${typescriptDir}`);\n  context.typescriptDir = typescriptDir;\n\n  const defaultCoreCompilerFilePath = join(context.ionicAngularDir, 'compiler');\n  context.coreCompilerFilePath = resolve(context.coreCompilerFilePath || getConfigValue(context, '--coreCompilerFilePath', null, Constants.ENV_VAR_CORE_COMPILER_FILE_PATH, Constants.ENV_VAR_CORE_COMPILER_FILE_PATH.toLowerCase(), defaultCoreCompilerFilePath));\n  setProcessEnvVar(Constants.ENV_VAR_CORE_COMPILER_FILE_PATH, context.coreCompilerFilePath);\n  Logger.debug(`coreCompilerFilePath set to ${context.coreCompilerFilePath}`);\n\n  const defaultCoreDir = context.ionicAngularDir;\n  context.coreDir = resolve(context.coreDir || getConfigValue(context, '--coreDir', null, Constants.ENV_VAR_CORE_DIR, Constants.ENV_VAR_CORE_DIR.toLowerCase(), defaultCoreDir));\n  setProcessEnvVar(Constants.ENV_VAR_CORE_DIR, context.coreDir);\n  Logger.debug(`coreDir set to ${context.coreDir}`);\n\n  const rxjsDir = resolve(getConfigValue(context, '--rxjsDir', null, Constants.ENV_VAR_RXJS_DIR, Constants.ENV_VAR_RXJS_DIR.toLowerCase(), join(context.nodeModulesDir, Constants.RXJS)));\n  setProcessEnvVar(Constants.ENV_VAR_RXJS_DIR, rxjsDir);\n  Logger.debug(`rxjsDir set to ${rxjsDir}`);\n\n  const ionicAngularTemplatesDir = join(context.ionicAngularDir, 'templates');\n  setProcessEnvVar(Constants.ENV_VAR_IONIC_ANGULAR_TEMPLATE_DIR, ionicAngularTemplatesDir);\n  Logger.debug(`ionicAngularTemplatesDir set to ${ionicAngularTemplatesDir}`);\n\n  context.platform = getConfigValue(context, '--platform', null, Constants.ENV_VAR_PLATFORM, null, null);\n  setProcessEnvVar(Constants.ENV_VAR_PLATFORM, context.platform);\n  Logger.debug(`platform set to ${context.platform}`);\n\n  context.target = getConfigValue(context, '--target', null, Constants.ENV_VAR_TARGET, null, null);\n  setProcessEnvVar(Constants.ENV_VAR_TARGET, context.target);\n  Logger.debug(`target set to ${context.target}`);\n\n  const ionicAngularEntryPoint = resolve(getConfigValue(context, '--ionicAngularEntryPoint', null, Constants.ENV_VAR_IONIC_ANGULAR_ENTRY_POINT, Constants.ENV_VAR_IONIC_ANGULAR_ENTRY_POINT.toLowerCase(), join(context.ionicAngularDir, 'index.js')));\n  setProcessEnvVar(Constants.ENV_VAR_IONIC_ANGULAR_ENTRY_POINT, ionicAngularEntryPoint);\n  Logger.debug(`ionicAngularEntryPoint set to ${ionicAngularEntryPoint}`);\n\n  const appScriptsDir = join(__dirname, '..', '..');\n  setProcessEnvVar(Constants.ENV_VAR_APP_SCRIPTS_DIR, appScriptsDir);\n  Logger.debug(`appScriptsDir set to ${appScriptsDir}`);\n\n  const generateSourceMap = getConfigValue(context, '--generateSourceMap', null, Constants.ENV_VAR_GENERATE_SOURCE_MAP, Constants.ENV_VAR_GENERATE_SOURCE_MAP.toLowerCase(), context.isProd || context.runMinifyJs ? null : 'true');\n  setProcessEnvVar(Constants.ENV_VAR_GENERATE_SOURCE_MAP, generateSourceMap);\n  Logger.debug(`generateSourceMap set to ${generateSourceMap}`);\n\n  const sourceMapTypeValue = getConfigValue(context, '--sourceMapType', null, Constants.ENV_VAR_SOURCE_MAP_TYPE, Constants.ENV_VAR_SOURCE_MAP_TYPE.toLowerCase(), Constants.SOURCE_MAP_TYPE_EXPENSIVE);\n  setProcessEnvVar(Constants.ENV_VAR_SOURCE_MAP_TYPE, sourceMapTypeValue);\n  Logger.debug(`sourceMapType set to ${sourceMapTypeValue}`);\n\n  const moveSourceMaps = getConfigValue(context, '--moveSourceMaps', null, Constants.ENV_VAR_MOVE_SOURCE_MAPS, Constants.ENV_VAR_MOVE_SOURCE_MAPS.toLowerCase(), 'true');\n  setProcessEnvVar(Constants.ENV_VAR_MOVE_SOURCE_MAPS, moveSourceMaps);\n  Logger.debug(`moveSourceMaps set to ${moveSourceMaps}`);\n\n  const tsConfigPathValue = resolve(getConfigValue(context, '--tsconfig', null, Constants.ENV_TS_CONFIG, Constants.ENV_TS_CONFIG.toLowerCase(), join(context.rootDir, 'tsconfig.json')));\n  setProcessEnvVar(Constants.ENV_TS_CONFIG, tsConfigPathValue);\n  Logger.debug(`tsconfig set to ${tsConfigPathValue}`);\n\n  const readConfigJson = getConfigValue(context, '--readConfigJson', null, Constants.ENV_READ_CONFIG_JSON, Constants.ENV_READ_CONFIG_JSON.toLowerCase(), 'true');\n  setProcessEnvVar(Constants.ENV_READ_CONFIG_JSON, readConfigJson);\n  Logger.debug(`readConfigJson set to ${readConfigJson}`);\n\n  const appEntryPointPathValue = resolve(getConfigValue(context, '--appEntryPoint', null, Constants.ENV_APP_ENTRY_POINT, Constants.ENV_APP_ENTRY_POINT.toLowerCase(), join(context.srcDir, 'app', 'main.ts')));\n  setProcessEnvVar(Constants.ENV_APP_ENTRY_POINT, appEntryPointPathValue);\n  Logger.debug(`appEntryPoint set to ${appEntryPointPathValue}`);\n\n  context.appNgModulePath = resolve(getConfigValue(context, '--appNgModulePath', null, Constants.ENV_APP_NG_MODULE_PATH, Constants.ENV_APP_NG_MODULE_PATH.toLowerCase(), join(context.srcDir, 'app', 'app.module.ts')));\n  setProcessEnvVar(Constants.ENV_APP_NG_MODULE_PATH, context.appNgModulePath);\n  Logger.debug(`appNgModulePath set to ${context.appNgModulePath}`);\n\n\n  context.componentsNgModulePath = resolve(getConfigValue(context, '--componentsNgModulePath', null, Constants.ENV_COMPONENTS_NG_MODULE_PATH, Constants.ENV_COMPONENTS_NG_MODULE_PATH.toLowerCase(), join(context.srcDir, 'components', 'components.module.ts')));\n  setProcessEnvVar(Constants.ENV_COMPONENTS_NG_MODULE_PATH, context.componentsNgModulePath);\n  Logger.debug(`componentsNgModulePath set to ${context.componentsNgModulePath}`);\n\n  context.pipesNgModulePath = resolve(getConfigValue(context, '--pipesNgModulePath', null, Constants.ENV_PIPES_NG_MODULE_PATH, Constants.ENV_PIPES_NG_MODULE_PATH.toLowerCase(), join(context.srcDir, 'pipes', 'pipes.module.ts')));\n  setProcessEnvVar(Constants.ENV_PIPES_NG_MODULE_PATH, context.pipesNgModulePath);\n  Logger.debug(`pipesNgModulePath set to ${context.pipesNgModulePath}`);\n\n  context.directivesNgModulePath = resolve(getConfigValue(context, '--directivesNgModulePath', null, Constants.ENV_DIRECTIVES_NG_MODULE_PATH, Constants.ENV_DIRECTIVES_NG_MODULE_PATH.toLowerCase(), join(context.srcDir, 'directives', 'directives.module.ts')));\n  setProcessEnvVar(Constants.ENV_DIRECTIVES_NG_MODULE_PATH, context.directivesNgModulePath);\n  Logger.debug(`directivesNgModulePath set to ${context.directivesNgModulePath}`);\n\n  const appNgModuleClass = getConfigValue(context, '--appNgModuleClass', null, Constants.ENV_APP_NG_MODULE_CLASS, Constants.ENV_APP_NG_MODULE_CLASS.toLowerCase(), 'AppModule');\n  setProcessEnvVar(Constants.ENV_APP_NG_MODULE_CLASS, appNgModuleClass);\n  Logger.debug(`appNgModuleClass set to ${appNgModuleClass}`);\n\n  const pathToGlobUtil = join(getProcessEnvVar(Constants.ENV_VAR_APP_SCRIPTS_DIR), 'dist', 'util', 'glob-util.js');\n  setProcessEnvVar(Constants.ENV_GLOB_UTIL, pathToGlobUtil);\n  Logger.debug(`pathToGlobUtil set to ${pathToGlobUtil}`);\n\n  const cleanBeforeCopy = getConfigValue(context, '--cleanBeforeCopy', null, Constants.ENV_CLEAN_BEFORE_COPY, Constants.ENV_CLEAN_BEFORE_COPY.toLowerCase(), null);\n  setProcessEnvVar(Constants.ENV_CLEAN_BEFORE_COPY, cleanBeforeCopy);\n  Logger.debug(`cleanBeforeCopy set to ${cleanBeforeCopy}`);\n\n  context.outputJsFileName = getConfigValue(context, '--outputJsFileName', null, Constants.ENV_OUTPUT_JS_FILE_NAME, Constants.ENV_OUTPUT_JS_FILE_NAME.toLowerCase(), 'main.js');\n  setProcessEnvVar(Constants.ENV_OUTPUT_JS_FILE_NAME, context.outputJsFileName);\n  Logger.debug(`outputJsFileName set to ${context.outputJsFileName}`);\n\n  context.outputCssFileName = getConfigValue(context, '--outputCssFileName', null, Constants.ENV_OUTPUT_CSS_FILE_NAME, Constants.ENV_OUTPUT_CSS_FILE_NAME.toLowerCase(), 'main.css');\n  setProcessEnvVar(Constants.ENV_OUTPUT_CSS_FILE_NAME, context.outputCssFileName);\n  Logger.debug(`outputCssFileName set to ${context.outputCssFileName}`);\n\n  const webpackFactoryPath = join(getProcessEnvVar(Constants.ENV_VAR_APP_SCRIPTS_DIR), 'dist', 'webpack', 'ionic-webpack-factory.js');\n  setProcessEnvVar(Constants.ENV_WEBPACK_FACTORY, webpackFactoryPath);\n  Logger.debug(`webpackFactoryPath set to ${webpackFactoryPath}`);\n\n  const webpackLoaderPath = join(getProcessEnvVar(Constants.ENV_VAR_APP_SCRIPTS_DIR), 'dist', 'webpack', 'loader.js');\n  setProcessEnvVar(Constants.ENV_WEBPACK_LOADER, webpackLoaderPath);\n  Logger.debug(`webpackLoaderPath set to ${webpackLoaderPath}`);\n\n  const cacheLoaderPath = join(getProcessEnvVar(Constants.ENV_VAR_APP_SCRIPTS_DIR), 'dist', 'webpack', 'cache-loader.js');\n  setProcessEnvVar(Constants.ENV_CACHE_LOADER, cacheLoaderPath);\n  Logger.debug(`cacheLoaderPath set to ${cacheLoaderPath}`);\n\n  const aotWriteToDisk = getConfigValue(context, '--aotWriteToDisk', null, Constants.ENV_AOT_WRITE_TO_DISK, Constants.ENV_AOT_WRITE_TO_DISK.toLowerCase(), null);\n  setProcessEnvVar(Constants.ENV_AOT_WRITE_TO_DISK, aotWriteToDisk);\n  Logger.debug(`aotWriteToDisk set to ${aotWriteToDisk}`);\n\n  const printWebpackDependencyTree = getConfigValue(context, '--printWebpackDependencyTree', null, Constants.ENV_PRINT_WEBPACK_DEPENDENCY_TREE, Constants.ENV_PRINT_WEBPACK_DEPENDENCY_TREE.toLowerCase(), null);\n  setProcessEnvVar(Constants.ENV_PRINT_WEBPACK_DEPENDENCY_TREE, printWebpackDependencyTree);\n  Logger.debug(`printWebpackDependencyTree set to ${printWebpackDependencyTree}`);\n  const typeCheckOnLint = getConfigValue(context, '--typeCheckOnLint', null, Constants.ENV_TYPE_CHECK_ON_LINT, Constants.ENV_TYPE_CHECK_ON_LINT.toLowerCase(), null);\n  setProcessEnvVar(Constants.ENV_TYPE_CHECK_ON_LINT, typeCheckOnLint);\n  Logger.debug(`typeCheckOnLint set to ${typeCheckOnLint}`);\n\n  const bailOnLintError = getConfigValue(context, '--bailOnLintError', null, Constants.ENV_BAIL_ON_LINT_ERROR, Constants.ENV_BAIL_ON_LINT_ERROR.toLowerCase(), null);\n  setProcessEnvVar(Constants.ENV_BAIL_ON_LINT_ERROR, bailOnLintError);\n  Logger.debug(`bailOnLintError set to ${bailOnLintError}`);\n\n  const enableLint = getConfigValue(context, '--enableLint', null, Constants.ENV_ENABLE_LINT, Constants.ENV_ENABLE_LINT.toLowerCase(), 'true');\n  setProcessEnvVar(Constants.ENV_ENABLE_LINT, enableLint);\n  Logger.debug(`enableLint set to ${enableLint}`);\n\n  const disableLogging = getConfigValue(context, '--disableLogging', null, Constants.ENV_DISABLE_LOGGING, Constants.ENV_DISABLE_LOGGING.toLowerCase(), null);\n  setProcessEnvVar(Constants.ENV_DISABLE_LOGGING, disableLogging);\n  Logger.debug(`disableLogging set to ${disableLogging}`);\n\n  const startWatchTimeout = getConfigValue(context, '--startWatchTimeout', null, Constants.ENV_START_WATCH_TIMEOUT, Constants.ENV_START_WATCH_TIMEOUT.toLowerCase(), '3000');\n  setProcessEnvVar(Constants.ENV_START_WATCH_TIMEOUT, startWatchTimeout);\n  Logger.debug(`startWatchTimeout set to ${startWatchTimeout}`);\n\n  const ngModuleFileNameSuffix = getConfigValue(context, '--ngModuleFileNameSuffix', null, Constants.ENV_NG_MODULE_FILE_NAME_SUFFIX, Constants.ENV_NG_MODULE_FILE_NAME_SUFFIX.toLowerCase(), '.module.ts');\n  setProcessEnvVar(Constants.ENV_NG_MODULE_FILE_NAME_SUFFIX, ngModuleFileNameSuffix);\n  Logger.debug(`ngModuleFileNameSuffix set to ${ngModuleFileNameSuffix}`);\n\n  const polyfillName = getConfigValue(context, '--polyfillFileName', null, Constants.ENV_POLYFILL_FILE_NAME, Constants.ENV_POLYFILL_FILE_NAME.toLowerCase(), 'polyfills.js');\n  setProcessEnvVar(Constants.ENV_POLYFILL_FILE_NAME, polyfillName);\n  Logger.debug(`polyfillName set to ${polyfillName}`);\n\n  const purgeUnusedFonts = getConfigValue(context, '--purgeUnusedFonts', null, Constants.ENV_PURGE_UNUSED_FONTS, Constants.ENV_PURGE_UNUSED_FONTS.toLowerCase(), 'true');\n  setProcessEnvVar(Constants.ENV_PURGE_UNUSED_FONTS, purgeUnusedFonts);\n  Logger.debug(`purgeUnusedFonts set to ${purgeUnusedFonts}`);\n\n  /* Provider Path Stuff */\n  setProcessEnvVar(Constants.ENV_ACTION_SHEET_CONTROLLER_CLASSNAME, 'ActionSheetController');\n  setProcessEnvVar(Constants.ENV_ACTION_SHEET_CONTROLLER_PATH, join(context.ionicAngularDir, 'components', 'action-sheet', 'action-sheet-controller.js'));\n  setProcessEnvVar(Constants.ENV_ACTION_SHEET_VIEW_CONTROLLER_PATH, join(context.ionicAngularDir, 'components', 'action-sheet', 'action-sheet.js'));\n  setProcessEnvVar(Constants.ENV_ACTION_SHEET_COMPONENT_PATH, join(context.ionicAngularDir, 'components', 'action-sheet', 'action-sheet-component.js'));\n  setProcessEnvVar(Constants.ENV_ACTION_SHEET_COMPONENT_FACTORY_PATH, join(context.ionicAngularDir, 'components', 'action-sheet', 'action-sheet-component.ngfactory.js'));\n\n  setProcessEnvVar(Constants.ENV_ALERT_CONTROLLER_CLASSNAME, 'AlertController');\n  setProcessEnvVar(Constants.ENV_ALERT_CONTROLLER_PATH, join(context.ionicAngularDir, 'components', 'alert', 'alert-controller.js'));\n  setProcessEnvVar(Constants.ENV_ALERT_VIEW_CONTROLLER_PATH, join(context.ionicAngularDir, 'components', 'alert', 'alert.js'));\n  setProcessEnvVar(Constants.ENV_ALERT_COMPONENT_PATH, join(context.ionicAngularDir, 'components', 'alert', 'alert-component.js'));\n  setProcessEnvVar(Constants.ENV_ALERT_COMPONENT_FACTORY_PATH, join(context.ionicAngularDir, 'components', 'alert', 'alert-component.ngfactory.js'));\n\n  setProcessEnvVar(Constants.ENV_APP_ROOT_COMPONENT_PATH, join(context.ionicAngularDir, 'components', 'app', 'app-root.js'));\n\n  setProcessEnvVar(Constants.ENV_LOADING_CONTROLLER_CLASSNAME, 'LoadingController');\n  setProcessEnvVar(Constants.ENV_LOADING_CONTROLLER_PATH, join(context.ionicAngularDir, 'components', 'loading', 'loading-controller.js'));\n  setProcessEnvVar(Constants.ENV_LOADING_VIEW_CONTROLLER_PATH, join(context.ionicAngularDir, 'components', 'loading', 'loading.js'));\n  setProcessEnvVar(Constants.ENV_LOADING_COMPONENT_PATH, join(context.ionicAngularDir, 'components', 'loading', 'loading-component.js'));\n  setProcessEnvVar(Constants.ENV_LOADING_COMPONENT_FACTORY_PATH, join(context.ionicAngularDir, 'components', 'loading', 'loading-component.ngfactory.js'));\n\n  setProcessEnvVar(Constants.ENV_MODAL_CONTROLLER_CLASSNAME, 'ModalController');\n  setProcessEnvVar(Constants.ENV_MODAL_CONTROLLER_PATH, join(context.ionicAngularDir, 'components', 'modal', 'modal-controller.js'));\n  setProcessEnvVar(Constants.ENV_MODAL_VIEW_CONTROLLER_PATH, join(context.ionicAngularDir, 'components', 'modal', 'modal.js'));\n  setProcessEnvVar(Constants.ENV_MODAL_COMPONENT_PATH, join(context.ionicAngularDir, 'components', 'modal', 'modal-component.js'));\n  setProcessEnvVar(Constants.ENV_MODAL_COMPONENT_FACTORY_PATH, join(context.ionicAngularDir, 'components', 'modal', 'modal-component.ngfactory.js'));\n\n  setProcessEnvVar(Constants.ENV_PICKER_CONTROLLER_CLASSNAME, 'PickerController');\n  setProcessEnvVar(Constants.ENV_PICKER_CONTROLLER_PATH, join(context.ionicAngularDir, 'components', 'picker', 'picker-controller.js'));\n  setProcessEnvVar(Constants.ENV_PICKER_VIEW_CONTROLLER_PATH, join(context.ionicAngularDir, 'components', 'picker', 'picker.js'));\n  setProcessEnvVar(Constants.ENV_PICKER_COMPONENT_PATH, join(context.ionicAngularDir, 'components', 'picker', 'picker-component.js'));\n  setProcessEnvVar(Constants.ENV_PICKER_COMPONENT_FACTORY_PATH, join(context.ionicAngularDir, 'components', 'picker', 'picker-component.ngfactory.js'));\n\n  setProcessEnvVar(Constants.ENV_POPOVER_CONTROLLER_CLASSNAME, 'PopoverController');\n  setProcessEnvVar(Constants.ENV_POPOVER_CONTROLLER_PATH, join(context.ionicAngularDir, 'components', 'popover', 'popover-controller.js'));\n  setProcessEnvVar(Constants.ENV_POPOVER_VIEW_CONTROLLER_PATH, join(context.ionicAngularDir, 'components', 'popover', 'popover.js'));\n  setProcessEnvVar(Constants.ENV_POPOVER_COMPONENT_PATH, join(context.ionicAngularDir, 'components', 'popover', 'popover-component.js'));\n  setProcessEnvVar(Constants.ENV_POPOVER_COMPONENT_FACTORY_PATH, join(context.ionicAngularDir, 'components', 'popover', 'popover-component.ngfactory.js'));\n\n  setProcessEnvVar(Constants.ENV_SELECT_POPOVER_CLASSNAME, 'SelectPopover');\n  setProcessEnvVar(Constants.ENV_SELECT_POPOVER_COMPONENT_PATH, join(context.ionicAngularDir, 'components', 'select', 'select-popover-component.js'));\n  setProcessEnvVar(Constants.ENV_SELECT_POPOVER_COMPONENT_FACTORY_PATH, join(context.ionicAngularDir, 'components', 'select', 'select-popover-component.ngfactory.js'));\n\n  setProcessEnvVar(Constants.ENV_TOAST_CONTROLLER_CLASSNAME, 'ToastController');\n  setProcessEnvVar(Constants.ENV_TOAST_CONTROLLER_PATH, join(context.ionicAngularDir, 'components', 'toast', 'toast-controller.js'));\n  setProcessEnvVar(Constants.ENV_TOAST_VIEW_CONTROLLER_PATH, join(context.ionicAngularDir, 'components', 'toast', 'toast.js'));\n  setProcessEnvVar(Constants.ENV_TOAST_COMPONENT_PATH, join(context.ionicAngularDir, 'components', 'toast', 'toast-component.js'));\n  setProcessEnvVar(Constants.ENV_TOAST_COMPONENT_FACTORY_PATH, join(context.ionicAngularDir, 'components', 'toast', 'toast-component.ngfactory.js'));\n\n  const parseDeepLinks = getConfigValue(context, '--parseDeepLinks', null, Constants.ENV_PARSE_DEEPLINKS, Constants.ENV_PARSE_DEEPLINKS.toLowerCase(), 'true');\n  setProcessEnvVar(Constants.ENV_PARSE_DEEPLINKS, parseDeepLinks);\n  Logger.debug(`parseDeepLinks set to ${parseDeepLinks}`);\n\n  const skipReadIonicAngular = getConfigValue(context, '--skipIonicAngularVersion', null, Constants.ENV_SKIP_IONIC_ANGULAR_VERSION, Constants.ENV_SKIP_IONIC_ANGULAR_VERSION.toLowerCase(), 'false');\n  setProcessEnvVar(Constants.ENV_SKIP_IONIC_ANGULAR_VERSION, skipReadIonicAngular);\n  Logger.debug(`skipReadIonicAngular set to ${skipReadIonicAngular}`);\n\n\n  if (!isValidBundler(context.bundler)) {\n    context.bundler = bundlerStrategy(context);\n    Logger.debug(`bundler set to ${context.bundler}`);\n  }\n\n  context.inlineTemplates = true;\n\n  checkDebugMode();\n\n  if (getBooleanPropertyValue(Constants.ENV_DISABLE_LOGGING)) {\n    console.debug = () => { };\n    console.error = () => { };\n    console.info = () => { };\n    console.log = () => { };\n    console.trace = () => { };\n    console.warn = () => { };\n  }\n\n  return context;\n}\n\nexport function getUserConfigFile(context: BuildContext, task: TaskInfo, userConfigFile: string) {\n  if (!context) {\n    context = generateContext(context);\n  }\n\n  if (userConfigFile) {\n    return resolve(userConfigFile);\n  }\n\n  const defaultConfig = getConfigValue(context, task.fullArg, task.shortArg, task.envVar, task.packageConfig, null);\n  if (defaultConfig) {\n    return join(context.rootDir, defaultConfig);\n  }\n\n  return null;\n}\n\n\nexport function fillConfigDefaults(userConfigFile: string, defaultConfigFile: string): any {\n  let userConfig: any = null;\n\n  if (userConfigFile) {\n    try {\n      // check if exists first, so we can print a more specific error message\n      // since required config could also throw MODULE_NOT_FOUND\n      statSync(userConfigFile);\n      // create a fresh copy of the config each time\n      userConfig = require(userConfigFile);\n\n      // if user config returns a function call it to determine proper object\n      if (typeof userConfig === 'function') {\n         userConfig = userConfig();\n      }\n    } catch (e) {\n      if (e.code === 'ENOENT') {\n        console.error(`Config file \"${userConfigFile}\" not found. Using defaults instead.`);\n      } else {\n        console.error(`There was an error in config file \"${userConfigFile}\". Using defaults instead.`);\n        console.error(e);\n      }\n    }\n  }\n\n  const defaultConfig = require(join('..', '..', 'config', defaultConfigFile));\n\n  // create a fresh copy of the config each time\n  // always assign any default values which were not already supplied by the user\n  return objectAssign({}, defaultConfig, userConfig);\n}\n\nexport function bundlerStrategy(context: BuildContext): string {\n  return Constants.BUNDLER_WEBPACK;\n}\n\n\nfunction isValidBundler(bundler: any) {\n  return bundler === Constants.BUNDLER_WEBPACK;\n}\n\n\nexport function getConfigValue(context: BuildContext, argFullName: string, argShortName: string, envVarName: string, packageConfigProp: string, defaultValue: string) {\n  if (!context) {\n    context = generateContext(context);\n  }\n\n  // first see if the value was set in the command-line args\n  const argVal = getArgValue(argFullName, argShortName);\n  if (argVal !== null) {\n    return argVal;\n  }\n\n  // next see if it was set in the environment variables\n  // which also checks if it was set in the package.json config property\n  const envVar = getProcessEnvVar(envVarName);\n  if (envVar !== null) {\n    return envVar;\n  }\n\n  const packageConfig = getPackageJsonConfig(context, packageConfigProp);\n  if (packageConfig !== null) {\n    return packageConfig;\n  }\n\n  // return the default if nothing above was found\n  return defaultValue;\n}\n\n\nfunction getArgValue(fullName: string, shortName: string): string {\n  for (var i = 2; i < processArgv.length; i++) {\n    var arg = processArgv[i];\n    if (arg === fullName || (shortName && arg === shortName)) {\n      var val = processArgv[i + 1];\n      if (val !== undefined && val !== '') {\n        return val;\n      }\n    }\n  }\n  return null;\n}\n\n\nexport function hasConfigValue(context: BuildContext, argFullName: string, argShortName: string, envVarName: string, defaultValue: boolean) {\n  if (!context) {\n    context = generateContext(context);\n  }\n\n  if (hasArg(argFullName, argShortName)) {\n    return true;\n  }\n\n  // next see if it was set in the environment variables\n  // which also checks if it was set in the package.json config property\n  const envVar = getProcessEnvVar(envVarName);\n  if (envVar !== null) {\n    return true;\n  }\n\n  const packageConfig = getPackageJsonConfig(context, envVarName);\n  if (packageConfig !== null) {\n    return true;\n  }\n\n  // return the default if nothing above was found\n  return defaultValue;\n}\n\n\nexport function hasArg(fullName: string, shortName: string = null): boolean {\n  return !!(processArgv.some(a => a.toLowerCase() === fullName.toLowerCase()) ||\n    (shortName !== null && processArgv.some(a => a.toLowerCase() === shortName.toLowerCase())));\n}\n\n\nexport function replacePathVars(context: BuildContext, filePath: string | string[] | { [key: string]: any }): any {\n  if (Array.isArray(filePath)) {\n    return filePath.map(f => replacePathVars(context, f));\n  }\n\n  if (typeof filePath === 'object') {\n    const clonedFilePaths = Object.assign({}, filePath);\n    for (let key in clonedFilePaths) {\n      clonedFilePaths[key] = replacePathVars(context, clonedFilePaths[key]);\n    }\n    return clonedFilePaths;\n  }\n\n  return filePath.replace('{{SRC}}', context.srcDir)\n    .replace('{{WWW}}', context.wwwDir)\n    .replace('{{TMP}}', context.tmpDir)\n    .replace('{{ROOT}}', context.rootDir)\n    .replace('{{BUILD}}', context.buildDir);\n}\n\nexport function getNodeBinExecutable(context: BuildContext, cmd: string) {\n  let cmdPath = join(context.rootDir, 'node_modules', '.bin', cmd);\n\n  try {\n    accessSync(cmdPath);\n  } catch (e) {\n    cmdPath = null;\n  }\n\n  return cmdPath;\n}\n\n\nlet checkedDebug = false;\nfunction checkDebugMode() {\n  if (!checkedDebug) {\n    if (hasArg('--debug') || getProcessEnvVar('ionic_debug_mode') === 'true') {\n      processEnv.ionic_debug_mode = 'true';\n    }\n    checkedDebug = true;\n  }\n}\n\n\nexport function isDebugMode() {\n  return (processEnv.ionic_debug_mode === 'true');\n}\n\nlet processArgv: string[];\nexport function setProcessArgs(argv: string[]) {\n  processArgv = argv;\n}\nsetProcessArgs(process.argv);\n\nexport function addArgv(value: string) {\n  processArgv.push(value);\n}\n\nlet processEnv: any;\nexport function setProcessEnv(env: any) {\n  processEnv = env;\n}\nsetProcessEnv(process.env);\n\nexport function setProcessEnvVar(key: string, value: any) {\n  if (key && value) {\n    processEnv[key] = value;\n  }\n}\n\nexport function getProcessEnvVar(key: string): any {\n  const val = processEnv[key];\n  if (val !== undefined) {\n    if (val === 'true') {\n      return true;\n    }\n    if (val === 'false') {\n      return false;\n    }\n    return val;\n  }\n  return null;\n}\n\nlet processCwd: string;\nexport function setCwd(cwd: string) {\n  processCwd = cwd;\n}\nsetCwd(process.cwd());\n\n\nexport function getPackageJsonConfig(context: BuildContext, key: string): any {\n  const packageJsonData = getAppPackageJsonData(context);\n  if (packageJsonData && packageJsonData.config) {\n    const val = packageJsonData.config[key];\n    if (val !== undefined) {\n      if (val === 'true') {\n        return true;\n      }\n      if (val === 'false') {\n        return false;\n      }\n      return val;\n    }\n  }\n  return null;\n}\n\n\nlet appPackageJsonData: any = null;\nexport function setAppPackageJsonData(data: any) {\n  appPackageJsonData = data;\n}\n\nfunction getAppPackageJsonData(context: BuildContext) {\n  if (!appPackageJsonData) {\n    try {\n      appPackageJsonData = readJSONSync(join(context.rootDir, 'package.json'));\n    } catch (e) {}\n  }\n\n  return appPackageJsonData;\n}\n"
  },
  {
    "path": "src/util/constants.ts",
    "content": "export const FILE_CHANGE_EVENT = 'change';\nexport const FILE_ADD_EVENT = 'add';\nexport const FILE_DELETE_EVENT = 'unlink';\nexport const DIRECTORY_ADD_EVENT = 'addDir';\nexport const DIRECTORY_DELETE_EVENT = 'unlinkDir';\n\nexport const SOURCE_MAP_TYPE_CHEAP = 'eval';\nexport const SOURCE_MAP_TYPE_EXPENSIVE = 'source-map';\n\nexport const BUILD_DIR = 'build';\nexport const SRC_DIR = 'src';\nexport const TMP_DIR = '.tmp';\nexport const SOURCEMAP_DIR = '.sourcemaps';\nexport const WWW_DIR = 'www';\nexport const NODE_MODULES = 'node_modules';\nexport const IONIC_ANGULAR = 'ionic-angular';\nexport const COMPONENT = 'component';\nexport const DIRECTIVE = 'directive';\nexport const PAGE = 'page';\nexport const PIPE = 'pipe';\nexport const PROVIDER = 'provider';\nexport const TABS = 'tabs';\nexport const AT_ANGULAR = '@angular';\nexport const RXJS = 'rxjs';\nexport const CORDOVA = 'cordova';\nexport const TYPESCRIPT = 'typescript';\n\nexport const ENV_VAR_PROD = 'prod';\nexport const ENV_VAR_DEV = 'dev';\nexport const ENV_VAR_IONIC_ENV = 'IONIC_ENV';\n\nexport const ENV_VAR_IONIC_AOT = 'IONIC_AOT';\nexport const ENV_VAR_IONIC_MINIFY_JS = 'IONIC_MINIFY_JS';\nexport const ENV_VAR_IONIC_MINIFY_CSS = 'IONIC_MINIFY_CSS';\nexport const ENV_VAR_IONIC_OPTIMIZE_JS = 'IONIC_OPTIMIZE_JS';\n\nexport const ENV_VAR_ROOT_DIR = 'IONIC_ROOT_DIR';\nexport const ENV_VAR_SRC_DIR = 'IONIC_SRC_DIR';\nexport const ENV_VAR_DEEPLINKS_DIR = 'IONIC_DEEPLINKS_DIR';\nexport const ENV_VAR_PAGES_DIR = 'IONIC_PAGES_DIR';\nexport const ENV_VAR_COMPONENTS_DIR = 'IONIC_COMPONENTS_DIR';\nexport const ENV_VAR_DIRECTIVES_DIR = 'IONIC_DIRECTIVES_DIR';\nexport const ENV_VAR_PIPES_DIR = 'IONIC_PIPES_DIR';\nexport const ENV_VAR_PROVIDERS_DIR = 'IONIC_PROVIDERS_DIR';\nexport const ENV_VAR_TMP_DIR = 'IONIC_TMP_DIR';\nexport const ENV_VAR_WWW_DIR = 'IONIC_WWW_DIR';\nexport const ENV_VAR_FONTS_DIR = 'IONIC_FONTS_DIR';\nexport const ENV_VAR_SOURCEMAP_DIR = 'IONIC_SOURCEMAP_DIR';\nexport const ENV_VAR_HTML_TO_SERVE = 'IONIC_HTML_TO_SERVE';\nexport const ENV_VAR_BUILD_DIR = 'IONIC_BUILD_DIR';\nexport const ENV_VAR_NODE_MODULES_DIR = 'IONIC_NODE_MODULES_DIR';\nexport const ENV_VAR_ANGULAR_CORE_DIR = 'IONIC_ANGULAR_CORE_DIR';\nexport const ENV_VAR_TYPESCRIPT_DIR = 'IONIC_TYPESCRIPT_DIR';\nexport const ENV_VAR_RXJS_DIR = 'IONIC_RXJS_DIR';\nexport const ENV_VAR_IONIC_ANGULAR_DIR = 'IONIC_ANGULAR_DIR';\nexport const ENV_VAR_CORE_COMPILER_FILE_PATH = 'IONIC_CORE_COMPILER_FILE_PATH';\nexport const ENV_VAR_CORE_DIR = 'IONIC_CORE_DIR';\nexport const ENV_VAR_IONIC_ANGULAR_TEMPLATE_DIR = 'IONIC_ANGULAR_TEMPLATE_DIR';\nexport const ENV_VAR_TARGET = 'IONIC_TARGET';\nexport const ENV_VAR_PLATFORM = 'IONIC_PLATFORM';\nexport const ENV_VAR_IONIC_ANGULAR_ENTRY_POINT = 'IONIC_ANGULAR_ENTRY_POINT';\nexport const ENV_VAR_APP_SCRIPTS_DIR = 'IONIC_APP_SCRIPTS_DIR';\nexport const ENV_VAR_GENERATE_SOURCE_MAP = 'IONIC_GENERATE_SOURCE_MAP';\nexport const ENV_VAR_SOURCE_MAP_TYPE = 'IONIC_SOURCE_MAP_TYPE';\nexport const ENV_VAR_MOVE_SOURCE_MAPS = 'IONIC_MOVE_SOURCE_MAPS';\nexport const ENV_TS_CONFIG = 'IONIC_TS_CONFIG';\nexport const ENV_APP_ENTRY_POINT = 'IONIC_APP_ENTRY_POINT';\nexport const ENV_APP_NG_MODULE_PATH = 'IONIC_APP_NG_MODULE_PATH';\nexport const ENV_APP_NG_MODULE_CLASS = 'IONIC_APP_NG_MODULE_CLASS';\n\nexport const ENV_COMPONENTS_NG_MODULE_PATH = 'IONIC_COMPONENTS_NG_MODULE_PATH';\nexport const ENV_PIPES_NG_MODULE_PATH = 'IONIC_PIPES_NG_MODULE_PATH';\nexport const ENV_DIRECTIVES_NG_MODULE_PATH = 'IONIC_DIRECTIVES_NG_MODULE_PATH';\n\nexport const ENV_GLOB_UTIL = 'IONIC_GLOB_UTIL';\nexport const ENV_CLEAN_BEFORE_COPY = 'IONIC_CLEAN_BEFORE_COPY';\nexport const ENV_READ_CONFIG_JSON = 'IONIC_READ_CONFIG_JSON';\nexport const ENV_OUTPUT_JS_FILE_NAME = 'IONIC_OUTPUT_JS_FILE_NAME';\nexport const ENV_OUTPUT_CSS_FILE_NAME = 'IONIC_OUTPUT_CSS_FILE_NAME';\nexport const ENV_WEBPACK_FACTORY = 'IONIC_WEBPACK_FACTORY';\nexport const ENV_WEBPACK_LOADER = 'IONIC_WEBPACK_LOADER';\nexport const ENV_CACHE_LOADER = 'IONIC_CACHE_LOADER';\nexport const ENV_AOT_WRITE_TO_DISK = 'IONIC_AOT_WRITE_TO_DISK';\nexport const ENV_BAIL_ON_LINT_ERROR = 'IONIC_BAIL_ON_LINT_ERROR';\nexport const ENV_TYPE_CHECK_ON_LINT = 'IONIC_TYPE_CHECK_ON_LINT';\nexport const ENV_ENABLE_LINT = 'IONIC_ENABLE_LINT';\nexport const ENV_DISABLE_LOGGING = 'IONIC_DISABLE_LOGGING';\nexport const ENV_START_WATCH_TIMEOUT = 'IONIC_START_WATCH_TIMEOUT';\nexport const ENV_NG_MODULE_FILE_NAME_SUFFIX = 'IONIC_NG_MODULE_FILENAME_SUFFIX';\nexport const ENV_POLYFILL_FILE_NAME = 'IONIC_POLYFILL_FILE_NAME';\nexport const ENV_PRINT_WEBPACK_DEPENDENCY_TREE = 'IONIC_PRINT_WEBPACK_DEPENDENCY_TREE';\nexport const ENV_PARSE_DEEPLINKS = 'IONIC_PARSE_DEEPLINKS';\nexport const ENV_PURGE_UNUSED_FONTS = 'IONIC_PURGE_UNUSED_FONTS';\nexport const ENV_SKIP_IONIC_ANGULAR_VERSION = 'IONIC_SKIP_IONIC_ANGULAR_VERSION';\n\n\n/* Providers */\nexport const ENV_ACTION_SHEET_CONTROLLER_CLASSNAME = 'IONIC_ACTION_SHEET_CONTROLLER_CLASSNAME';\nexport const ENV_ACTION_SHEET_CONTROLLER_PATH = 'IONIC_ACTION_SHEET_CONTROLLER_PATH';\nexport const ENV_ACTION_SHEET_VIEW_CONTROLLER_PATH = 'IONIC_ACTION_SHEET_VIEW_CONTROLLER_PATH';\nexport const ENV_ACTION_SHEET_COMPONENT_PATH = 'IONIC_ACTION_SHEET_COMPONENT_PATH';\nexport const ENV_ACTION_SHEET_COMPONENT_FACTORY_PATH = 'IONIC_ACTION_SHEET_COMPONENT_FACTORY_PATH';\n\nexport const ENV_ALERT_CONTROLLER_CLASSNAME = 'IONIC_ALERT_CONTROLLER_CLASSNAME';\nexport const ENV_ALERT_CONTROLLER_PATH = 'IONIC_ALERT_CONTROLLER_PATH';\nexport const ENV_ALERT_VIEW_CONTROLLER_PATH = 'IONIC_ALERT_VIEW_CONTROLLER_PATH';\nexport const ENV_ALERT_COMPONENT_PATH = 'IONIC_ALERT_COMPONENT_PATH';\nexport const ENV_ALERT_COMPONENT_FACTORY_PATH = 'IONIC_ALERT_COMPONENT_FACTORY_PATH';\n\nexport const ENV_APP_ROOT_COMPONENT_PATH = 'IONIC_APP_ROOT_COMPONENT_PATH';\n\nexport const ENV_LOADING_CONTROLLER_CLASSNAME = 'IONIC_LOADING_CONTROLLER_CLASSNAME';\nexport const ENV_LOADING_CONTROLLER_PATH = 'IONIC_LOADING_CONTROLLER_PATH';\nexport const ENV_LOADING_VIEW_CONTROLLER_PATH = 'IONIC_LOADING_VIEW_CONTROLLER_PATH';\nexport const ENV_LOADING_COMPONENT_PATH = 'IONIC_LOADING_COMPONENT_PATH';\nexport const ENV_LOADING_COMPONENT_FACTORY_PATH = 'IONIC_LOADING_COMPONENT_FACTORY_PATH';\n\nexport const ENV_MODAL_CONTROLLER_CLASSNAME = 'IONIC_MODAL_CONTROLLER_CLASSNAME';\nexport const ENV_MODAL_CONTROLLER_PATH = 'IONIC_MODAL_CONTROLLER_PATH';\nexport const ENV_MODAL_VIEW_CONTROLLER_PATH = 'IONIC_MODAL_VIEW_CONTROLLER_PATH';\nexport const ENV_MODAL_COMPONENT_PATH = 'IONIC_MODAL_COMPONENT_PATH';\nexport const ENV_MODAL_COMPONENT_FACTORY_PATH = 'IONIC_MODAL_COMPONENT_FACTORY_PATH';\n\nexport const ENV_PICKER_CONTROLLER_CLASSNAME = 'IONIC_PICKER_CONTROLLER_CLASSNAME';\nexport const ENV_PICKER_CONTROLLER_PATH = 'IONIC_PICKER_CONTROLLER_PATH';\nexport const ENV_PICKER_VIEW_CONTROLLER_PATH = 'IONIC_PICKER_VIEW_CONTROLLER_PATH';\nexport const ENV_PICKER_COMPONENT_PATH = 'IONIC_PICKER_COMPONENT_PATH';\nexport const ENV_PICKER_COMPONENT_FACTORY_PATH = 'IONIC_PICKER_COMPONENT_FACTORY_PATH';\n\nexport const ENV_POPOVER_CONTROLLER_CLASSNAME = 'IONIC_POPOVER_CONTROLLER_CLASSNAME';\nexport const ENV_POPOVER_CONTROLLER_PATH = 'IONIC_POPOVER_CONTROLLER_PATH';\nexport const ENV_POPOVER_VIEW_CONTROLLER_PATH = 'IONIC_POPOVER_VIEW_CONTROLLER_PATH';\nexport const ENV_POPOVER_COMPONENT_PATH = 'IONIC_POPOVER_COMPONENT_PATH';\nexport const ENV_POPOVER_COMPONENT_FACTORY_PATH = 'IONIC_POPOVER_COMPONENT_FACTORY_PATH';\n\nexport const ENV_SELECT_POPOVER_CLASSNAME = 'IONIC_SELECT_POPOVER_CLASSNAME';\nexport const ENV_SELECT_POPOVER_COMPONENT_PATH = 'IONIC_SELECT_POPOVER_COMPONENT_PATH';\nexport const ENV_SELECT_POPOVER_COMPONENT_FACTORY_PATH = 'IONIC_SELECT_POPOVER_COMPONENT_FACTORY_PATH';\n\nexport const ENV_TOAST_CONTROLLER_CLASSNAME = 'IONIC_TOAST_CONTROLLER_CLASSNAME';\nexport const ENV_TOAST_CONTROLLER_PATH = 'IONIC_TOAST_CONTROLLER_PATH';\nexport const ENV_TOAST_VIEW_CONTROLLER_PATH = 'IONIC_TOAST_VIEW_CONTROLLER_PATH';\nexport const ENV_TOAST_COMPONENT_PATH = 'IONIC_TOAST_COMPONENT_PATH';\nexport const ENV_TOAST_COMPONENT_FACTORY_PATH = 'IONIC_TOAST_COMPONENT_FACTORY_PATH';\n\nexport const BUNDLER_WEBPACK = 'webpack';\n"
  },
  {
    "path": "src/util/cordova-config.spec.ts",
    "content": "import * as cordovaConfig from './cordova-config';\n\ndescribe('parseConfig function', () => {\n  it('should return {} when the config does not contain a widget', () => {\n    var result = cordovaConfig.parseConfig({});\n    expect(result).toEqual({});\n  });\n\n  it('should return a CordovaProject without id or version if config.$ does not exist', () => {\n    var result = cordovaConfig.parseConfig({\n      widget: {\n        name: ['thename'],\n      }\n    });\n\n    expect(result).toEqual({\n      name: 'thename',\n    });\n  });\n\n  it('should return a CordovaProject on success', () => {\n    var result = cordovaConfig.parseConfig({\n      widget: {\n        name: ['thename'],\n        $: {\n          id: 'theid',\n          version: 'theversion'\n        }\n      }\n    });\n\n    expect(result).toEqual({\n      name: 'thename',\n      id: 'theid',\n      version: 'theversion'\n    });\n  });\n});\n\n/*\ndescribe('buildCordovaConfig', () => {\n  it('should read the config.xml file', (done) => {\n    let fs: any = jest.genMockFromModule('fs');\n    fs.readFile = jest.fn().mockReturnValue('blah');\n    jest.mock('xml2js', function() {\n      return {\n        Parser: function() {\n          return {\n            parseString: function (data, cb) {\n              cb(null, 'parseConfigData');\n            }\n          };\n        }\n      };\n    });\n\n    function daCallback() {\n      expect(fs.readfile).toHaveBeenCalledWith('config.xml');\n      done();\n    }\n\n    cordovaConfig.buildCordovaConfig(daCallback, daCallback);\n  });\n});\n*/\n"
  },
  {
    "path": "src/util/cordova-config.ts",
    "content": "import * as fs from 'fs';\nimport * as xml2js from 'xml2js';\n\nexport interface CordovaProject {\n  name?: string;\n  id?: string;\n  version?: string;\n}\n\nlet lastConfig: CordovaProject;\n\n/**\n * Parse and build a CordovaProject config object by parsing the\n * config.xml file in the project root.\n */\nexport let buildCordovaConfig = (errCb: Function, cb: Function) => {\n  var parser = new xml2js.Parser();\n  fs.readFile('config.xml', (err: any, data: any) => {\n    if (err) {\n      errCb(err);\n      return;\n    }\n    parser.parseString(data, (err: any, result: any) => {\n      if (err) {\n        errCb(err);\n        return;\n      }\n      cb(parseConfig(result));\n    });\n  });\n};\n\nexport let parseConfig = (parsedConfig: any): CordovaProject => {\n  if (!parsedConfig.widget) {\n    return {};\n  }\n\n  let widget = parsedConfig.widget;\n\n  // Widget attrs are defined on the <widget> tag\n  let widgetAttrs = widget.$;\n\n  let config: CordovaProject = {\n    name: widget.name[0]\n  };\n\n  if (widgetAttrs) {\n    config.id = widgetAttrs.id;\n    config.version = widgetAttrs.version;\n  }\n\n  lastConfig = config;\n\n  return config;\n};\n"
  },
  {
    "path": "src/util/errors.spec.ts",
    "content": "import { BuildError } from './errors';\n\n\ndescribe('Errors', () => {\n\n  describe('BuildError', () => {\n\n    it('should create BuildError from err object in constructor', () => {\n      const buildError = new BuildError('message1');\n      buildError.name = 'name1';\n      buildError.stack = 'stack1';\n      buildError.isFatal = true;\n      buildError.hasBeenLogged = true;\n\n      const buildErrorCopy = new BuildError(buildError);\n      expect(buildErrorCopy.message).toEqual(buildError.message);\n      expect(buildErrorCopy.message).toEqual('message1');\n      expect(buildErrorCopy.name).toEqual(buildError.name);\n      expect(buildErrorCopy.stack).toEqual(buildError.stack);\n      expect(buildErrorCopy.isFatal).toEqual(buildError.isFatal);\n      expect(buildErrorCopy.hasBeenLogged).toEqual(buildError.hasBeenLogged);\n    });\n\n    it('should create a default object', () => {\n      const buildError = new BuildError('message1');\n      expect(buildError.isFatal).toBeFalsy();\n      expect(buildError.hasBeenLogged).toBeFalsy();\n    });\n  });\n\n});\n"
  },
  {
    "path": "src/util/errors.ts",
    "content": "\nexport class BuildError extends Error {\n  hasBeenLogged = false;\n  isFatal: boolean = false;\n\n  constructor(error: Error | string) {\n    super(error instanceof Error ? error.message : error);\n    if (error instanceof Error) {\n      this.message = error.message;\n      this.stack = error.stack;\n      this.name = error.name;\n      this.hasBeenLogged = (error as BuildError).hasBeenLogged;\n      this.isFatal = (error as BuildError).isFatal;\n    }\n  }\n}\n\n\n/* There are special cases where strange things happen where we don't want any logging, etc.\n * For our sake, it is much easier to get off the happy path of code and just throw an exception\n * and do nothing with it\n */\nexport class IgnorableError extends Error {\n  constructor(msg?: string) {\n    super(msg);\n  }\n}\n"
  },
  {
    "path": "src/util/events.ts",
    "content": "import { EventEmitter } from 'events';\nimport { Logger } from '../logger/logger';\n\nconst emmitter = new EventEmitter();\n\n\nexport function on(eventType: string, listener: {(data?: any): void}) {\n  Logger.debug(`An ${eventType} event occurred`);\n  return emmitter.on(eventType, listener);\n}\n\n\nexport function emit(eventType: string, val?: any) {\n  Logger.debug(`Emitting event ${eventType}`);\n  return emmitter.emit(eventType, val);\n}\n\n\nexport const EventType = {\n  BuildUpdateCompleted: 'BuildUpdateCompleted',\n  BuildUpdateStarted: 'BuildUpdateStarted',\n  FileAdd: 'FileAdd',\n  FileChange: 'FileChange',\n  FileDelete: 'FileDelete',\n  DirectoryAdd: 'DirectoryAdd',\n  DirectoryDelete: 'DirectoryDelete',\n  ReloadApp: 'ReloadApp',\n  WebpackFilesChanged: 'WebpackFilesChanged'\n};\n"
  },
  {
    "path": "src/util/file-cache.ts",
    "content": "import { File } from './interfaces';\n\n\nexport class FileCache {\n\n  private map: Map<string, File>;\n\n  constructor() {\n    this.map = new Map<string, File>();\n  }\n\n  set(key: string, file: File) {\n    file.timestamp = Date.now();\n    this.map.set(key, file);\n  }\n\n  get(key: string): File {\n    return this.map.get(key);\n  }\n\n  has(key: string) {\n    return this.map.has(key);\n  }\n\n  remove(key: string): Boolean {\n    const result = this.map.delete(key);\n    return result;\n  }\n\n  getAll() {\n    var list: File[] = [];\n    this.map.forEach((file: File) => {\n      list.push(file);\n    });\n    return list;\n  }\n\n  getRawStore(): Map<string, File> {\n    return this.map;\n  }\n}\n"
  },
  {
    "path": "src/util/glob-util.ts",
    "content": "import { dirname, isAbsolute, join, normalize, resolve as pathResolve, sep } from 'path';\nimport * as globFunction from 'glob';\nimport { toUnixPath } from './helpers';\n\n\nfunction isNegative(pattern: string) {\n  return pattern[0] === '!';\n}\n\nfunction isString(pattern: string) {\n  return typeof pattern === 'string';\n}\n\nfunction assertPatternsInput(patterns: string[]) {\n  if (!patterns.every(isString)) {\n    throw new Error('Each glob entry must be a string');\n  }\n}\n\nexport function generateGlobTasks(patterns: string[], opts: any) {\n\n  patterns = [].concat(patterns);\n  assertPatternsInput(patterns);\n\n  const globTasks: GlobObject[] = [];\n\n  opts = Object.assign({\n    cache: Object.create(null),\n    statCache: Object.create(null),\n    realpathCache: Object.create(null),\n    symlinks: Object.create(null),\n    ignore: []\n  }, opts);\n\n  patterns.forEach(function (pattern, i) {\n    if (isNegative(pattern)) {\n      return;\n    }\n\n    const ignore = patterns.slice(i).filter(isNegative).map(function (pattern) {\n      return pattern.slice(1);\n    });\n\n    const task: GlobObject = {\n      pattern: pattern,\n      opts: Object.assign({}, opts, {\n        ignore: opts.ignore.concat(ignore).concat(DEFAULT_IGNORE_ARRAY),\n        nodir: true\n      }),\n      base: getBasePath(pattern)\n    };\n\n    globTasks.push(task);\n  });\n\n  return globTasks;\n}\n\nfunction globWrapper(task: GlobObject): Promise<GlobResult[]> {\n  return new Promise((resolve, reject) => {\n    globFunction(task.pattern, task.opts, (err: Error, files: string[]) => {\n      if (err) {\n        return reject(err);\n      }\n      const globResults = files.map(file => {\n        return {\n          absolutePath: normalize(pathResolve(file)),\n          base: normalize(pathResolve(getBasePath(task.pattern)))\n        };\n      });\n      return resolve(globResults);\n    });\n  });\n}\n\nexport function globAll(globs: string[]): Promise<GlobResult[]> {\n  return Promise.resolve().then(() => {\n    const globTasks = generateGlobTasks(globs, {});\n    let resultSet: GlobResult[] = [];\n    const promises: Promise<GlobResult[]>[] = [];\n    for (const globTask of globTasks) {\n      const promise = globWrapper(globTask);\n      promises.push(promise);\n      promise.then(globResult => {\n        resultSet = resultSet.concat(globResult);\n      });\n    }\n\n    return Promise.all(promises).then(() => {\n      return resultSet;\n    });\n  });\n}\n\nexport function getBasePath(pattern: string) {\n  var basePath: string;\n  const sepRe = (process.platform === 'win32' ? /[\\/\\\\]/ : /\\/+/);\n  var parent = globParent(pattern);\n\n  basePath = toAbsoluteGlob(parent);\n\n  if (!sepRe.test(basePath.charAt(basePath.length - 1))) {\n    basePath += sep;\n  }\n  return basePath;\n}\n\nfunction isNegatedGlob(pattern: string) {\n  var glob = { negated: false, pattern: pattern, original: pattern };\n  if (pattern.charAt(0) === '!' && pattern.charAt(1) !== '(') {\n    glob.negated = true;\n    glob.pattern = pattern.slice(1);\n  }\n  return glob;\n}\n\n// https://github.com/jonschlinkert/to-absolute-glob/blob/master/index.js\nfunction toAbsoluteGlob(pattern: string) {\n  const cwd = toUnixPath(process.cwd());\n\n  // trim starting ./ from glob patterns\n  if (pattern.slice(0, 2) === './') {\n    pattern = pattern.slice(2);\n  }\n\n  // when the glob pattern is only a . use an empty string\n  if (pattern.length === 1 && pattern === '.') {\n    pattern = '';\n  }\n\n  // store last character before glob is modified\n  const suffix = pattern.slice(-1);\n\n  // check to see if glob is negated (and not a leading negated-extglob)\n  const ing = isNegatedGlob(pattern);\n  pattern = ing.pattern;\n\n  if (!isAbsolute(pattern) || pattern.slice(0, 1) === '\\\\') {\n    pattern = join(cwd, pattern);\n  }\n\n  // if glob had a trailing `/`, re-add it now in case it was removed\n  if (suffix === '/' && pattern.slice(-1) !== '/') {\n    pattern += '/';\n  }\n\n  // re-add leading `!` if it was removed\n  return ing.negated ? '!' + pattern : pattern;\n}\n\n// https://github.com/es128/glob-parent/blob/master/index.js\nfunction globParent(pattern: string) {\n  // special case for strings ending in enclosure containing path separator\n  if (/[\\{\\[].*[\\/]*.*[\\}\\]]$/.test(pattern)) pattern += '/';\n\n  // preserves full path in case of trailing path separator\n  pattern += 'a';\n\n  // remove path parts that are globby\n  do {\n    pattern = toUnixPath(dirname(pattern));\n  }\n\n  while (isGlob(pattern) || /(^|[^\\\\])([\\{\\[]|\\([^\\)]+$)/.test(pattern));\n\n  // remove escape chars and return result\n  return pattern.replace(/\\\\([\\*\\?\\|\\[\\]\\(\\)\\{\\}])/g, '$1');\n}\n\n// https://github.com/jonschlinkert/is-glob/blob/master/index.js\nfunction isGlob(pattern: string) {\n  if (pattern === '') {\n    return false;\n  }\n\n  if (isExtglob(pattern)) return true;\n\n  var regex = /(\\\\).|([*?]|\\[.*\\]|\\{.*\\}|\\(.*\\|.*\\)|^!)/;\n  var match: any;\n\n  while ((match = regex.exec(pattern))) {\n    if (match[2]) return true;\n    pattern = pattern.slice(match.index + match[0].length);\n  }\n  return false;\n}\n\n// https://github.com/jonschlinkert/is-extglob/blob/master/index.js\nfunction isExtglob(pattern: string) {\n  if (pattern === '') {\n    return false;\n  }\n\n  var match: any;\n  while ((match = /(\\\\).|([@?!+*]\\(.*\\))/g.exec(pattern))) {\n    if (match[2]) return true;\n    pattern = pattern.slice(match.index + match[0].length);\n  }\n\n  return false;\n}\n\nexport interface GlobObject {\n  pattern: string;\n  opts: GlobOptions;\n  base: string;\n}\n\nexport interface GlobResult {\n  absolutePath: string;\n  base: string;\n}\n\nexport interface GlobOptions {\n  ignore: string[];\n}\n\nexport const DEFAULT_IGNORE_ARRAY = ['**/*.DS_Store'];\n"
  },
  {
    "path": "src/util/helpers/camel-case-regexp.ts",
    "content": "/* https://raw.githubusercontent.com/blakeembrey/no-case/master/vendor/camel-case-regexp.js */\nexport const CAMEL_CASE_REGEXP = /([a-z\\xB5\\xDF-\\xF6\\xF8-\\xFF\\u0101\\u0103\\u0105\\u0107\\u0109\\u010B\\u010D\\u010F\\u0111\\u0113\\u0115\\u0117\\u0119\\u011B\\u011D\\u011F\\u0121\\u0123\\u0125\\u0127\\u0129\\u012B\\u012D\\u012F\\u0131\\u0133\\u0135\\u0137\\u0138\\u013A\\u013C\\u013E\\u0140\\u0142\\u0144\\u0146\\u0148\\u0149\\u014B\\u014D\\u014F\\u0151\\u0153\\u0155\\u0157\\u0159\\u015B\\u015D\\u015F\\u0161\\u0163\\u0165\\u0167\\u0169\\u016B\\u016D\\u016F\\u0171\\u0173\\u0175\\u0177\\u017A\\u017C\\u017E-\\u0180\\u0183\\u0185\\u0188\\u018C\\u018D\\u0192\\u0195\\u0199-\\u019B\\u019E\\u01A1\\u01A3\\u01A5\\u01A8\\u01AA\\u01AB\\u01AD\\u01B0\\u01B4\\u01B6\\u01B9\\u01BA\\u01BD-\\u01BF\\u01C6\\u01C9\\u01CC\\u01CE\\u01D0\\u01D2\\u01D4\\u01D6\\u01D8\\u01DA\\u01DC\\u01DD\\u01DF\\u01E1\\u01E3\\u01E5\\u01E7\\u01E9\\u01EB\\u01ED\\u01EF\\u01F0\\u01F3\\u01F5\\u01F9\\u01FB\\u01FD\\u01FF\\u0201\\u0203\\u0205\\u0207\\u0209\\u020B\\u020D\\u020F\\u0211\\u0213\\u0215\\u0217\\u0219\\u021B\\u021D\\u021F\\u0221\\u0223\\u0225\\u0227\\u0229\\u022B\\u022D\\u022F\\u0231\\u0233-\\u0239\\u023C\\u023F\\u0240\\u0242\\u0247\\u0249\\u024B\\u024D\\u024F-\\u0293\\u0295-\\u02AF\\u0371\\u0373\\u0377\\u037B-\\u037D\\u0390\\u03AC-\\u03CE\\u03D0\\u03D1\\u03D5-\\u03D7\\u03D9\\u03DB\\u03DD\\u03DF\\u03E1\\u03E3\\u03E5\\u03E7\\u03E9\\u03EB\\u03ED\\u03EF-\\u03F3\\u03F5\\u03F8\\u03FB\\u03FC\\u0430-\\u045F\\u0461\\u0463\\u0465\\u0467\\u0469\\u046B\\u046D\\u046F\\u0471\\u0473\\u0475\\u0477\\u0479\\u047B\\u047D\\u047F\\u0481\\u048B\\u048D\\u048F\\u0491\\u0493\\u0495\\u0497\\u0499\\u049B\\u049D\\u049F\\u04A1\\u04A3\\u04A5\\u04A7\\u04A9\\u04AB\\u04AD\\u04AF\\u04B1\\u04B3\\u04B5\\u04B7\\u04B9\\u04BB\\u04BD\\u04BF\\u04C2\\u04C4\\u04C6\\u04C8\\u04CA\\u04CC\\u04CE\\u04CF\\u04D1\\u04D3\\u04D5\\u04D7\\u04D9\\u04DB\\u04DD\\u04DF\\u04E1\\u04E3\\u04E5\\u04E7\\u04E9\\u04EB\\u04ED\\u04EF\\u04F1\\u04F3\\u04F5\\u04F7\\u04F9\\u04FB\\u04FD\\u04FF\\u0501\\u0503\\u0505\\u0507\\u0509\\u050B\\u050D\\u050F\\u0511\\u0513\\u0515\\u0517\\u0519\\u051B\\u051D\\u051F\\u0521\\u0523\\u0525\\u0527\\u0529\\u052B\\u052D\\u052F\\u0561-\\u0587\\u13F8-\\u13FD\\u1D00-\\u1D2B\\u1D6B-\\u1D77\\u1D79-\\u1D9A\\u1E01\\u1E03\\u1E05\\u1E07\\u1E09\\u1E0B\\u1E0D\\u1E0F\\u1E11\\u1E13\\u1E15\\u1E17\\u1E19\\u1E1B\\u1E1D\\u1E1F\\u1E21\\u1E23\\u1E25\\u1E27\\u1E29\\u1E2B\\u1E2D\\u1E2F\\u1E31\\u1E33\\u1E35\\u1E37\\u1E39\\u1E3B\\u1E3D\\u1E3F\\u1E41\\u1E43\\u1E45\\u1E47\\u1E49\\u1E4B\\u1E4D\\u1E4F\\u1E51\\u1E53\\u1E55\\u1E57\\u1E59\\u1E5B\\u1E5D\\u1E5F\\u1E61\\u1E63\\u1E65\\u1E67\\u1E69\\u1E6B\\u1E6D\\u1E6F\\u1E71\\u1E73\\u1E75\\u1E77\\u1E79\\u1E7B\\u1E7D\\u1E7F\\u1E81\\u1E83\\u1E85\\u1E87\\u1E89\\u1E8B\\u1E8D\\u1E8F\\u1E91\\u1E93\\u1E95-\\u1E9D\\u1E9F\\u1EA1\\u1EA3\\u1EA5\\u1EA7\\u1EA9\\u1EAB\\u1EAD\\u1EAF\\u1EB1\\u1EB3\\u1EB5\\u1EB7\\u1EB9\\u1EBB\\u1EBD\\u1EBF\\u1EC1\\u1EC3\\u1EC5\\u1EC7\\u1EC9\\u1ECB\\u1ECD\\u1ECF\\u1ED1\\u1ED3\\u1ED5\\u1ED7\\u1ED9\\u1EDB\\u1EDD\\u1EDF\\u1EE1\\u1EE3\\u1EE5\\u1EE7\\u1EE9\\u1EEB\\u1EED\\u1EEF\\u1EF1\\u1EF3\\u1EF5\\u1EF7\\u1EF9\\u1EFB\\u1EFD\\u1EFF-\\u1F07\\u1F10-\\u1F15\\u1F20-\\u1F27\\u1F30-\\u1F37\\u1F40-\\u1F45\\u1F50-\\u1F57\\u1F60-\\u1F67\\u1F70-\\u1F7D\\u1F80-\\u1F87\\u1F90-\\u1F97\\u1FA0-\\u1FA7\\u1FB0-\\u1FB4\\u1FB6\\u1FB7\\u1FBE\\u1FC2-\\u1FC4\\u1FC6\\u1FC7\\u1FD0-\\u1FD3\\u1FD6\\u1FD7\\u1FE0-\\u1FE7\\u1FF2-\\u1FF4\\u1FF6\\u1FF7\\u210A\\u210E\\u210F\\u2113\\u212F\\u2134\\u2139\\u213C\\u213D\\u2146-\\u2149\\u214E\\u2184\\u2C30-\\u2C5E\\u2C61\\u2C65\\u2C66\\u2C68\\u2C6A\\u2C6C\\u2C71\\u2C73\\u2C74\\u2C76-\\u2C7B\\u2C81\\u2C83\\u2C85\\u2C87\\u2C89\\u2C8B\\u2C8D\\u2C8F\\u2C91\\u2C93\\u2C95\\u2C97\\u2C99\\u2C9B\\u2C9D\\u2C9F\\u2CA1\\u2CA3\\u2CA5\\u2CA7\\u2CA9\\u2CAB\\u2CAD\\u2CAF\\u2CB1\\u2CB3\\u2CB5\\u2CB7\\u2CB9\\u2CBB\\u2CBD\\u2CBF\\u2CC1\\u2CC3\\u2CC5\\u2CC7\\u2CC9\\u2CCB\\u2CCD\\u2CCF\\u2CD1\\u2CD3\\u2CD5\\u2CD7\\u2CD9\\u2CDB\\u2CDD\\u2CDF\\u2CE1\\u2CE3\\u2CE4\\u2CEC\\u2CEE\\u2CF3\\u2D00-\\u2D25\\u2D27\\u2D2D\\uA641\\uA643\\uA645\\uA647\\uA649\\uA64B\\uA64D\\uA64F\\uA651\\uA653\\uA655\\uA657\\uA659\\uA65B\\uA65D\\uA65F\\uA661\\uA663\\uA665\\uA667\\uA669\\uA66B\\uA66D\\uA681\\uA683\\uA685\\uA687\\uA689\\uA68B\\uA68D\\uA68F\\uA691\\uA693\\uA695\\uA697\\uA699\\uA69B\\uA723\\uA725\\uA727\\uA729\\uA72B\\uA72D\\uA72F-\\uA731\\uA733\\uA735\\uA737\\uA739\\uA73B\\uA73D\\uA73F\\uA741\\uA743\\uA745\\uA747\\uA749\\uA74B\\uA74D\\uA74F\\uA751\\uA753\\uA755\\uA757\\uA759\\uA75B\\uA75D\\uA75F\\uA761\\uA763\\uA765\\uA767\\uA769\\uA76B\\uA76D\\uA76F\\uA771-\\uA778\\uA77A\\uA77C\\uA77F\\uA781\\uA783\\uA785\\uA787\\uA78C\\uA78E\\uA791\\uA793-\\uA795\\uA797\\uA799\\uA79B\\uA79D\\uA79F\\uA7A1\\uA7A3\\uA7A5\\uA7A7\\uA7A9\\uA7B5\\uA7B7\\uA7FA\\uAB30-\\uAB5A\\uAB60-\\uAB65\\uAB70-\\uABBF\\uFB00-\\uFB06\\uFB13-\\uFB17\\uFF41-\\uFF5A0-9\\xB2\\xB3\\xB9\\xBC-\\xBE\\u0660-\\u0669\\u06F0-\\u06F9\\u07C0-\\u07C9\\u0966-\\u096F\\u09E6-\\u09EF\\u09F4-\\u09F9\\u0A66-\\u0A6F\\u0AE6-\\u0AEF\\u0B66-\\u0B6F\\u0B72-\\u0B77\\u0BE6-\\u0BF2\\u0C66-\\u0C6F\\u0C78-\\u0C7E\\u0CE6-\\u0CEF\\u0D66-\\u0D75\\u0DE6-\\u0DEF\\u0E50-\\u0E59\\u0ED0-\\u0ED9\\u0F20-\\u0F33\\u1040-\\u1049\\u1090-\\u1099\\u1369-\\u137C\\u16EE-\\u16F0\\u17E0-\\u17E9\\u17F0-\\u17F9\\u1810-\\u1819\\u1946-\\u194F\\u19D0-\\u19DA\\u1A80-\\u1A89\\u1A90-\\u1A99\\u1B50-\\u1B59\\u1BB0-\\u1BB9\\u1C40-\\u1C49\\u1C50-\\u1C59\\u2070\\u2074-\\u2079\\u2080-\\u2089\\u2150-\\u2182\\u2185-\\u2189\\u2460-\\u249B\\u24EA-\\u24FF\\u2776-\\u2793\\u2CFD\\u3007\\u3021-\\u3029\\u3038-\\u303A\\u3192-\\u3195\\u3220-\\u3229\\u3248-\\u324F\\u3251-\\u325F\\u3280-\\u3289\\u32B1-\\u32BF\\uA620-\\uA629\\uA6E6-\\uA6EF\\uA830-\\uA835\\uA8D0-\\uA8D9\\uA900-\\uA909\\uA9D0-\\uA9D9\\uA9F0-\\uA9F9\\uAA50-\\uAA59\\uABF0-\\uABF9\\uFF10-\\uFF19])([A-Z\\xC0-\\xD6\\xD8-\\xDE\\u0100\\u0102\\u0104\\u0106\\u0108\\u010A\\u010C\\u010E\\u0110\\u0112\\u0114\\u0116\\u0118\\u011A\\u011C\\u011E\\u0120\\u0122\\u0124\\u0126\\u0128\\u012A\\u012C\\u012E\\u0130\\u0132\\u0134\\u0136\\u0139\\u013B\\u013D\\u013F\\u0141\\u0143\\u0145\\u0147\\u014A\\u014C\\u014E\\u0150\\u0152\\u0154\\u0156\\u0158\\u015A\\u015C\\u015E\\u0160\\u0162\\u0164\\u0166\\u0168\\u016A\\u016C\\u016E\\u0170\\u0172\\u0174\\u0176\\u0178\\u0179\\u017B\\u017D\\u0181\\u0182\\u0184\\u0186\\u0187\\u0189-\\u018B\\u018E-\\u0191\\u0193\\u0194\\u0196-\\u0198\\u019C\\u019D\\u019F\\u01A0\\u01A2\\u01A4\\u01A6\\u01A7\\u01A9\\u01AC\\u01AE\\u01AF\\u01B1-\\u01B3\\u01B5\\u01B7\\u01B8\\u01BC\\u01C4\\u01C7\\u01CA\\u01CD\\u01CF\\u01D1\\u01D3\\u01D5\\u01D7\\u01D9\\u01DB\\u01DE\\u01E0\\u01E2\\u01E4\\u01E6\\u01E8\\u01EA\\u01EC\\u01EE\\u01F1\\u01F4\\u01F6-\\u01F8\\u01FA\\u01FC\\u01FE\\u0200\\u0202\\u0204\\u0206\\u0208\\u020A\\u020C\\u020E\\u0210\\u0212\\u0214\\u0216\\u0218\\u021A\\u021C\\u021E\\u0220\\u0222\\u0224\\u0226\\u0228\\u022A\\u022C\\u022E\\u0230\\u0232\\u023A\\u023B\\u023D\\u023E\\u0241\\u0243-\\u0246\\u0248\\u024A\\u024C\\u024E\\u0370\\u0372\\u0376\\u037F\\u0386\\u0388-\\u038A\\u038C\\u038E\\u038F\\u0391-\\u03A1\\u03A3-\\u03AB\\u03CF\\u03D2-\\u03D4\\u03D8\\u03DA\\u03DC\\u03DE\\u03E0\\u03E2\\u03E4\\u03E6\\u03E8\\u03EA\\u03EC\\u03EE\\u03F4\\u03F7\\u03F9\\u03FA\\u03FD-\\u042F\\u0460\\u0462\\u0464\\u0466\\u0468\\u046A\\u046C\\u046E\\u0470\\u0472\\u0474\\u0476\\u0478\\u047A\\u047C\\u047E\\u0480\\u048A\\u048C\\u048E\\u0490\\u0492\\u0494\\u0496\\u0498\\u049A\\u049C\\u049E\\u04A0\\u04A2\\u04A4\\u04A6\\u04A8\\u04AA\\u04AC\\u04AE\\u04B0\\u04B2\\u04B4\\u04B6\\u04B8\\u04BA\\u04BC\\u04BE\\u04C0\\u04C1\\u04C3\\u04C5\\u04C7\\u04C9\\u04CB\\u04CD\\u04D0\\u04D2\\u04D4\\u04D6\\u04D8\\u04DA\\u04DC\\u04DE\\u04E0\\u04E2\\u04E4\\u04E6\\u04E8\\u04EA\\u04EC\\u04EE\\u04F0\\u04F2\\u04F4\\u04F6\\u04F8\\u04FA\\u04FC\\u04FE\\u0500\\u0502\\u0504\\u0506\\u0508\\u050A\\u050C\\u050E\\u0510\\u0512\\u0514\\u0516\\u0518\\u051A\\u051C\\u051E\\u0520\\u0522\\u0524\\u0526\\u0528\\u052A\\u052C\\u052E\\u0531-\\u0556\\u10A0-\\u10C5\\u10C7\\u10CD\\u13A0-\\u13F5\\u1E00\\u1E02\\u1E04\\u1E06\\u1E08\\u1E0A\\u1E0C\\u1E0E\\u1E10\\u1E12\\u1E14\\u1E16\\u1E18\\u1E1A\\u1E1C\\u1E1E\\u1E20\\u1E22\\u1E24\\u1E26\\u1E28\\u1E2A\\u1E2C\\u1E2E\\u1E30\\u1E32\\u1E34\\u1E36\\u1E38\\u1E3A\\u1E3C\\u1E3E\\u1E40\\u1E42\\u1E44\\u1E46\\u1E48\\u1E4A\\u1E4C\\u1E4E\\u1E50\\u1E52\\u1E54\\u1E56\\u1E58\\u1E5A\\u1E5C\\u1E5E\\u1E60\\u1E62\\u1E64\\u1E66\\u1E68\\u1E6A\\u1E6C\\u1E6E\\u1E70\\u1E72\\u1E74\\u1E76\\u1E78\\u1E7A\\u1E7C\\u1E7E\\u1E80\\u1E82\\u1E84\\u1E86\\u1E88\\u1E8A\\u1E8C\\u1E8E\\u1E90\\u1E92\\u1E94\\u1E9E\\u1EA0\\u1EA2\\u1EA4\\u1EA6\\u1EA8\\u1EAA\\u1EAC\\u1EAE\\u1EB0\\u1EB2\\u1EB4\\u1EB6\\u1EB8\\u1EBA\\u1EBC\\u1EBE\\u1EC0\\u1EC2\\u1EC4\\u1EC6\\u1EC8\\u1ECA\\u1ECC\\u1ECE\\u1ED0\\u1ED2\\u1ED4\\u1ED6\\u1ED8\\u1EDA\\u1EDC\\u1EDE\\u1EE0\\u1EE2\\u1EE4\\u1EE6\\u1EE8\\u1EEA\\u1EEC\\u1EEE\\u1EF0\\u1EF2\\u1EF4\\u1EF6\\u1EF8\\u1EFA\\u1EFC\\u1EFE\\u1F08-\\u1F0F\\u1F18-\\u1F1D\\u1F28-\\u1F2F\\u1F38-\\u1F3F\\u1F48-\\u1F4D\\u1F59\\u1F5B\\u1F5D\\u1F5F\\u1F68-\\u1F6F\\u1FB8-\\u1FBB\\u1FC8-\\u1FCB\\u1FD8-\\u1FDB\\u1FE8-\\u1FEC\\u1FF8-\\u1FFB\\u2102\\u2107\\u210B-\\u210D\\u2110-\\u2112\\u2115\\u2119-\\u211D\\u2124\\u2126\\u2128\\u212A-\\u212D\\u2130-\\u2133\\u213E\\u213F\\u2145\\u2183\\u2C00-\\u2C2E\\u2C60\\u2C62-\\u2C64\\u2C67\\u2C69\\u2C6B\\u2C6D-\\u2C70\\u2C72\\u2C75\\u2C7E-\\u2C80\\u2C82\\u2C84\\u2C86\\u2C88\\u2C8A\\u2C8C\\u2C8E\\u2C90\\u2C92\\u2C94\\u2C96\\u2C98\\u2C9A\\u2C9C\\u2C9E\\u2CA0\\u2CA2\\u2CA4\\u2CA6\\u2CA8\\u2CAA\\u2CAC\\u2CAE\\u2CB0\\u2CB2\\u2CB4\\u2CB6\\u2CB8\\u2CBA\\u2CBC\\u2CBE\\u2CC0\\u2CC2\\u2CC4\\u2CC6\\u2CC8\\u2CCA\\u2CCC\\u2CCE\\u2CD0\\u2CD2\\u2CD4\\u2CD6\\u2CD8\\u2CDA\\u2CDC\\u2CDE\\u2CE0\\u2CE2\\u2CEB\\u2CED\\u2CF2\\uA640\\uA642\\uA644\\uA646\\uA648\\uA64A\\uA64C\\uA64E\\uA650\\uA652\\uA654\\uA656\\uA658\\uA65A\\uA65C\\uA65E\\uA660\\uA662\\uA664\\uA666\\uA668\\uA66A\\uA66C\\uA680\\uA682\\uA684\\uA686\\uA688\\uA68A\\uA68C\\uA68E\\uA690\\uA692\\uA694\\uA696\\uA698\\uA69A\\uA722\\uA724\\uA726\\uA728\\uA72A\\uA72C\\uA72E\\uA732\\uA734\\uA736\\uA738\\uA73A\\uA73C\\uA73E\\uA740\\uA742\\uA744\\uA746\\uA748\\uA74A\\uA74C\\uA74E\\uA750\\uA752\\uA754\\uA756\\uA758\\uA75A\\uA75C\\uA75E\\uA760\\uA762\\uA764\\uA766\\uA768\\uA76A\\uA76C\\uA76E\\uA779\\uA77B\\uA77D\\uA77E\\uA780\\uA782\\uA784\\uA786\\uA78B\\uA78D\\uA790\\uA792\\uA796\\uA798\\uA79A\\uA79C\\uA79E\\uA7A0\\uA7A2\\uA7A4\\uA7A6\\uA7A8\\uA7AA-\\uA7AD\\uA7B0-\\uA7B4\\uA7B6\\uFF21-\\uFF3A])/g;\n"
  },
  {
    "path": "src/util/helpers/camel-case-upper-regexp.ts",
    "content": "export const CAMEL_CASE_UPPER_REGEXP = /([A-Z\\xC0-\\xD6\\xD8-\\xDE\\u0100\\u0102\\u0104\\u0106\\u0108\\u010A\\u010C\\u010E\\u0110\\u0112\\u0114\\u0116\\u0118\\u011A\\u011C\\u011E\\u0120\\u0122\\u0124\\u0126\\u0128\\u012A\\u012C\\u012E\\u0130\\u0132\\u0134\\u0136\\u0139\\u013B\\u013D\\u013F\\u0141\\u0143\\u0145\\u0147\\u014A\\u014C\\u014E\\u0150\\u0152\\u0154\\u0156\\u0158\\u015A\\u015C\\u015E\\u0160\\u0162\\u0164\\u0166\\u0168\\u016A\\u016C\\u016E\\u0170\\u0172\\u0174\\u0176\\u0178\\u0179\\u017B\\u017D\\u0181\\u0182\\u0184\\u0186\\u0187\\u0189-\\u018B\\u018E-\\u0191\\u0193\\u0194\\u0196-\\u0198\\u019C\\u019D\\u019F\\u01A0\\u01A2\\u01A4\\u01A6\\u01A7\\u01A9\\u01AC\\u01AE\\u01AF\\u01B1-\\u01B3\\u01B5\\u01B7\\u01B8\\u01BC\\u01C4\\u01C7\\u01CA\\u01CD\\u01CF\\u01D1\\u01D3\\u01D5\\u01D7\\u01D9\\u01DB\\u01DE\\u01E0\\u01E2\\u01E4\\u01E6\\u01E8\\u01EA\\u01EC\\u01EE\\u01F1\\u01F4\\u01F6-\\u01F8\\u01FA\\u01FC\\u01FE\\u0200\\u0202\\u0204\\u0206\\u0208\\u020A\\u020C\\u020E\\u0210\\u0212\\u0214\\u0216\\u0218\\u021A\\u021C\\u021E\\u0220\\u0222\\u0224\\u0226\\u0228\\u022A\\u022C\\u022E\\u0230\\u0232\\u023A\\u023B\\u023D\\u023E\\u0241\\u0243-\\u0246\\u0248\\u024A\\u024C\\u024E\\u0370\\u0372\\u0376\\u037F\\u0386\\u0388-\\u038A\\u038C\\u038E\\u038F\\u0391-\\u03A1\\u03A3-\\u03AB\\u03CF\\u03D2-\\u03D4\\u03D8\\u03DA\\u03DC\\u03DE\\u03E0\\u03E2\\u03E4\\u03E6\\u03E8\\u03EA\\u03EC\\u03EE\\u03F4\\u03F7\\u03F9\\u03FA\\u03FD-\\u042F\\u0460\\u0462\\u0464\\u0466\\u0468\\u046A\\u046C\\u046E\\u0470\\u0472\\u0474\\u0476\\u0478\\u047A\\u047C\\u047E\\u0480\\u048A\\u048C\\u048E\\u0490\\u0492\\u0494\\u0496\\u0498\\u049A\\u049C\\u049E\\u04A0\\u04A2\\u04A4\\u04A6\\u04A8\\u04AA\\u04AC\\u04AE\\u04B0\\u04B2\\u04B4\\u04B6\\u04B8\\u04BA\\u04BC\\u04BE\\u04C0\\u04C1\\u04C3\\u04C5\\u04C7\\u04C9\\u04CB\\u04CD\\u04D0\\u04D2\\u04D4\\u04D6\\u04D8\\u04DA\\u04DC\\u04DE\\u04E0\\u04E2\\u04E4\\u04E6\\u04E8\\u04EA\\u04EC\\u04EE\\u04F0\\u04F2\\u04F4\\u04F6\\u04F8\\u04FA\\u04FC\\u04FE\\u0500\\u0502\\u0504\\u0506\\u0508\\u050A\\u050C\\u050E\\u0510\\u0512\\u0514\\u0516\\u0518\\u051A\\u051C\\u051E\\u0520\\u0522\\u0524\\u0526\\u0528\\u052A\\u052C\\u052E\\u0531-\\u0556\\u10A0-\\u10C5\\u10C7\\u10CD\\u13A0-\\u13F5\\u1E00\\u1E02\\u1E04\\u1E06\\u1E08\\u1E0A\\u1E0C\\u1E0E\\u1E10\\u1E12\\u1E14\\u1E16\\u1E18\\u1E1A\\u1E1C\\u1E1E\\u1E20\\u1E22\\u1E24\\u1E26\\u1E28\\u1E2A\\u1E2C\\u1E2E\\u1E30\\u1E32\\u1E34\\u1E36\\u1E38\\u1E3A\\u1E3C\\u1E3E\\u1E40\\u1E42\\u1E44\\u1E46\\u1E48\\u1E4A\\u1E4C\\u1E4E\\u1E50\\u1E52\\u1E54\\u1E56\\u1E58\\u1E5A\\u1E5C\\u1E5E\\u1E60\\u1E62\\u1E64\\u1E66\\u1E68\\u1E6A\\u1E6C\\u1E6E\\u1E70\\u1E72\\u1E74\\u1E76\\u1E78\\u1E7A\\u1E7C\\u1E7E\\u1E80\\u1E82\\u1E84\\u1E86\\u1E88\\u1E8A\\u1E8C\\u1E8E\\u1E90\\u1E92\\u1E94\\u1E9E\\u1EA0\\u1EA2\\u1EA4\\u1EA6\\u1EA8\\u1EAA\\u1EAC\\u1EAE\\u1EB0\\u1EB2\\u1EB4\\u1EB6\\u1EB8\\u1EBA\\u1EBC\\u1EBE\\u1EC0\\u1EC2\\u1EC4\\u1EC6\\u1EC8\\u1ECA\\u1ECC\\u1ECE\\u1ED0\\u1ED2\\u1ED4\\u1ED6\\u1ED8\\u1EDA\\u1EDC\\u1EDE\\u1EE0\\u1EE2\\u1EE4\\u1EE6\\u1EE8\\u1EEA\\u1EEC\\u1EEE\\u1EF0\\u1EF2\\u1EF4\\u1EF6\\u1EF8\\u1EFA\\u1EFC\\u1EFE\\u1F08-\\u1F0F\\u1F18-\\u1F1D\\u1F28-\\u1F2F\\u1F38-\\u1F3F\\u1F48-\\u1F4D\\u1F59\\u1F5B\\u1F5D\\u1F5F\\u1F68-\\u1F6F\\u1FB8-\\u1FBB\\u1FC8-\\u1FCB\\u1FD8-\\u1FDB\\u1FE8-\\u1FEC\\u1FF8-\\u1FFB\\u2102\\u2107\\u210B-\\u210D\\u2110-\\u2112\\u2115\\u2119-\\u211D\\u2124\\u2126\\u2128\\u212A-\\u212D\\u2130-\\u2133\\u213E\\u213F\\u2145\\u2183\\u2C00-\\u2C2E\\u2C60\\u2C62-\\u2C64\\u2C67\\u2C69\\u2C6B\\u2C6D-\\u2C70\\u2C72\\u2C75\\u2C7E-\\u2C80\\u2C82\\u2C84\\u2C86\\u2C88\\u2C8A\\u2C8C\\u2C8E\\u2C90\\u2C92\\u2C94\\u2C96\\u2C98\\u2C9A\\u2C9C\\u2C9E\\u2CA0\\u2CA2\\u2CA4\\u2CA6\\u2CA8\\u2CAA\\u2CAC\\u2CAE\\u2CB0\\u2CB2\\u2CB4\\u2CB6\\u2CB8\\u2CBA\\u2CBC\\u2CBE\\u2CC0\\u2CC2\\u2CC4\\u2CC6\\u2CC8\\u2CCA\\u2CCC\\u2CCE\\u2CD0\\u2CD2\\u2CD4\\u2CD6\\u2CD8\\u2CDA\\u2CDC\\u2CDE\\u2CE0\\u2CE2\\u2CEB\\u2CED\\u2CF2\\uA640\\uA642\\uA644\\uA646\\uA648\\uA64A\\uA64C\\uA64E\\uA650\\uA652\\uA654\\uA656\\uA658\\uA65A\\uA65C\\uA65E\\uA660\\uA662\\uA664\\uA666\\uA668\\uA66A\\uA66C\\uA680\\uA682\\uA684\\uA686\\uA688\\uA68A\\uA68C\\uA68E\\uA690\\uA692\\uA694\\uA696\\uA698\\uA69A\\uA722\\uA724\\uA726\\uA728\\uA72A\\uA72C\\uA72E\\uA732\\uA734\\uA736\\uA738\\uA73A\\uA73C\\uA73E\\uA740\\uA742\\uA744\\uA746\\uA748\\uA74A\\uA74C\\uA74E\\uA750\\uA752\\uA754\\uA756\\uA758\\uA75A\\uA75C\\uA75E\\uA760\\uA762\\uA764\\uA766\\uA768\\uA76A\\uA76C\\uA76E\\uA779\\uA77B\\uA77D\\uA77E\\uA780\\uA782\\uA784\\uA786\\uA78B\\uA78D\\uA790\\uA792\\uA796\\uA798\\uA79A\\uA79C\\uA79E\\uA7A0\\uA7A2\\uA7A4\\uA7A6\\uA7A8\\uA7AA-\\uA7AD\\uA7B0-\\uA7B4\\uA7B6\\uFF21-\\uFF3A]+)([A-Z\\xC0-\\xD6\\xD8-\\xDE\\u0100\\u0102\\u0104\\u0106\\u0108\\u010A\\u010C\\u010E\\u0110\\u0112\\u0114\\u0116\\u0118\\u011A\\u011C\\u011E\\u0120\\u0122\\u0124\\u0126\\u0128\\u012A\\u012C\\u012E\\u0130\\u0132\\u0134\\u0136\\u0139\\u013B\\u013D\\u013F\\u0141\\u0143\\u0145\\u0147\\u014A\\u014C\\u014E\\u0150\\u0152\\u0154\\u0156\\u0158\\u015A\\u015C\\u015E\\u0160\\u0162\\u0164\\u0166\\u0168\\u016A\\u016C\\u016E\\u0170\\u0172\\u0174\\u0176\\u0178\\u0179\\u017B\\u017D\\u0181\\u0182\\u0184\\u0186\\u0187\\u0189-\\u018B\\u018E-\\u0191\\u0193\\u0194\\u0196-\\u0198\\u019C\\u019D\\u019F\\u01A0\\u01A2\\u01A4\\u01A6\\u01A7\\u01A9\\u01AC\\u01AE\\u01AF\\u01B1-\\u01B3\\u01B5\\u01B7\\u01B8\\u01BC\\u01C4\\u01C7\\u01CA\\u01CD\\u01CF\\u01D1\\u01D3\\u01D5\\u01D7\\u01D9\\u01DB\\u01DE\\u01E0\\u01E2\\u01E4\\u01E6\\u01E8\\u01EA\\u01EC\\u01EE\\u01F1\\u01F4\\u01F6-\\u01F8\\u01FA\\u01FC\\u01FE\\u0200\\u0202\\u0204\\u0206\\u0208\\u020A\\u020C\\u020E\\u0210\\u0212\\u0214\\u0216\\u0218\\u021A\\u021C\\u021E\\u0220\\u0222\\u0224\\u0226\\u0228\\u022A\\u022C\\u022E\\u0230\\u0232\\u023A\\u023B\\u023D\\u023E\\u0241\\u0243-\\u0246\\u0248\\u024A\\u024C\\u024E\\u0370\\u0372\\u0376\\u037F\\u0386\\u0388-\\u038A\\u038C\\u038E\\u038F\\u0391-\\u03A1\\u03A3-\\u03AB\\u03CF\\u03D2-\\u03D4\\u03D8\\u03DA\\u03DC\\u03DE\\u03E0\\u03E2\\u03E4\\u03E6\\u03E8\\u03EA\\u03EC\\u03EE\\u03F4\\u03F7\\u03F9\\u03FA\\u03FD-\\u042F\\u0460\\u0462\\u0464\\u0466\\u0468\\u046A\\u046C\\u046E\\u0470\\u0472\\u0474\\u0476\\u0478\\u047A\\u047C\\u047E\\u0480\\u048A\\u048C\\u048E\\u0490\\u0492\\u0494\\u0496\\u0498\\u049A\\u049C\\u049E\\u04A0\\u04A2\\u04A4\\u04A6\\u04A8\\u04AA\\u04AC\\u04AE\\u04B0\\u04B2\\u04B4\\u04B6\\u04B8\\u04BA\\u04BC\\u04BE\\u04C0\\u04C1\\u04C3\\u04C5\\u04C7\\u04C9\\u04CB\\u04CD\\u04D0\\u04D2\\u04D4\\u04D6\\u04D8\\u04DA\\u04DC\\u04DE\\u04E0\\u04E2\\u04E4\\u04E6\\u04E8\\u04EA\\u04EC\\u04EE\\u04F0\\u04F2\\u04F4\\u04F6\\u04F8\\u04FA\\u04FC\\u04FE\\u0500\\u0502\\u0504\\u0506\\u0508\\u050A\\u050C\\u050E\\u0510\\u0512\\u0514\\u0516\\u0518\\u051A\\u051C\\u051E\\u0520\\u0522\\u0524\\u0526\\u0528\\u052A\\u052C\\u052E\\u0531-\\u0556\\u10A0-\\u10C5\\u10C7\\u10CD\\u13A0-\\u13F5\\u1E00\\u1E02\\u1E04\\u1E06\\u1E08\\u1E0A\\u1E0C\\u1E0E\\u1E10\\u1E12\\u1E14\\u1E16\\u1E18\\u1E1A\\u1E1C\\u1E1E\\u1E20\\u1E22\\u1E24\\u1E26\\u1E28\\u1E2A\\u1E2C\\u1E2E\\u1E30\\u1E32\\u1E34\\u1E36\\u1E38\\u1E3A\\u1E3C\\u1E3E\\u1E40\\u1E42\\u1E44\\u1E46\\u1E48\\u1E4A\\u1E4C\\u1E4E\\u1E50\\u1E52\\u1E54\\u1E56\\u1E58\\u1E5A\\u1E5C\\u1E5E\\u1E60\\u1E62\\u1E64\\u1E66\\u1E68\\u1E6A\\u1E6C\\u1E6E\\u1E70\\u1E72\\u1E74\\u1E76\\u1E78\\u1E7A\\u1E7C\\u1E7E\\u1E80\\u1E82\\u1E84\\u1E86\\u1E88\\u1E8A\\u1E8C\\u1E8E\\u1E90\\u1E92\\u1E94\\u1E9E\\u1EA0\\u1EA2\\u1EA4\\u1EA6\\u1EA8\\u1EAA\\u1EAC\\u1EAE\\u1EB0\\u1EB2\\u1EB4\\u1EB6\\u1EB8\\u1EBA\\u1EBC\\u1EBE\\u1EC0\\u1EC2\\u1EC4\\u1EC6\\u1EC8\\u1ECA\\u1ECC\\u1ECE\\u1ED0\\u1ED2\\u1ED4\\u1ED6\\u1ED8\\u1EDA\\u1EDC\\u1EDE\\u1EE0\\u1EE2\\u1EE4\\u1EE6\\u1EE8\\u1EEA\\u1EEC\\u1EEE\\u1EF0\\u1EF2\\u1EF4\\u1EF6\\u1EF8\\u1EFA\\u1EFC\\u1EFE\\u1F08-\\u1F0F\\u1F18-\\u1F1D\\u1F28-\\u1F2F\\u1F38-\\u1F3F\\u1F48-\\u1F4D\\u1F59\\u1F5B\\u1F5D\\u1F5F\\u1F68-\\u1F6F\\u1FB8-\\u1FBB\\u1FC8-\\u1FCB\\u1FD8-\\u1FDB\\u1FE8-\\u1FEC\\u1FF8-\\u1FFB\\u2102\\u2107\\u210B-\\u210D\\u2110-\\u2112\\u2115\\u2119-\\u211D\\u2124\\u2126\\u2128\\u212A-\\u212D\\u2130-\\u2133\\u213E\\u213F\\u2145\\u2183\\u2C00-\\u2C2E\\u2C60\\u2C62-\\u2C64\\u2C67\\u2C69\\u2C6B\\u2C6D-\\u2C70\\u2C72\\u2C75\\u2C7E-\\u2C80\\u2C82\\u2C84\\u2C86\\u2C88\\u2C8A\\u2C8C\\u2C8E\\u2C90\\u2C92\\u2C94\\u2C96\\u2C98\\u2C9A\\u2C9C\\u2C9E\\u2CA0\\u2CA2\\u2CA4\\u2CA6\\u2CA8\\u2CAA\\u2CAC\\u2CAE\\u2CB0\\u2CB2\\u2CB4\\u2CB6\\u2CB8\\u2CBA\\u2CBC\\u2CBE\\u2CC0\\u2CC2\\u2CC4\\u2CC6\\u2CC8\\u2CCA\\u2CCC\\u2CCE\\u2CD0\\u2CD2\\u2CD4\\u2CD6\\u2CD8\\u2CDA\\u2CDC\\u2CDE\\u2CE0\\u2CE2\\u2CEB\\u2CED\\u2CF2\\uA640\\uA642\\uA644\\uA646\\uA648\\uA64A\\uA64C\\uA64E\\uA650\\uA652\\uA654\\uA656\\uA658\\uA65A\\uA65C\\uA65E\\uA660\\uA662\\uA664\\uA666\\uA668\\uA66A\\uA66C\\uA680\\uA682\\uA684\\uA686\\uA688\\uA68A\\uA68C\\uA68E\\uA690\\uA692\\uA694\\uA696\\uA698\\uA69A\\uA722\\uA724\\uA726\\uA728\\uA72A\\uA72C\\uA72E\\uA732\\uA734\\uA736\\uA738\\uA73A\\uA73C\\uA73E\\uA740\\uA742\\uA744\\uA746\\uA748\\uA74A\\uA74C\\uA74E\\uA750\\uA752\\uA754\\uA756\\uA758\\uA75A\\uA75C\\uA75E\\uA760\\uA762\\uA764\\uA766\\uA768\\uA76A\\uA76C\\uA76E\\uA779\\uA77B\\uA77D\\uA77E\\uA780\\uA782\\uA784\\uA786\\uA78B\\uA78D\\uA790\\uA792\\uA796\\uA798\\uA79A\\uA79C\\uA79E\\uA7A0\\uA7A2\\uA7A4\\uA7A6\\uA7A8\\uA7AA-\\uA7AD\\uA7B0-\\uA7B4\\uA7B6\\uFF21-\\uFF3A][a-z\\xB5\\xDF-\\xF6\\xF8-\\xFF\\u0101\\u0103\\u0105\\u0107\\u0109\\u010B\\u010D\\u010F\\u0111\\u0113\\u0115\\u0117\\u0119\\u011B\\u011D\\u011F\\u0121\\u0123\\u0125\\u0127\\u0129\\u012B\\u012D\\u012F\\u0131\\u0133\\u0135\\u0137\\u0138\\u013A\\u013C\\u013E\\u0140\\u0142\\u0144\\u0146\\u0148\\u0149\\u014B\\u014D\\u014F\\u0151\\u0153\\u0155\\u0157\\u0159\\u015B\\u015D\\u015F\\u0161\\u0163\\u0165\\u0167\\u0169\\u016B\\u016D\\u016F\\u0171\\u0173\\u0175\\u0177\\u017A\\u017C\\u017E-\\u0180\\u0183\\u0185\\u0188\\u018C\\u018D\\u0192\\u0195\\u0199-\\u019B\\u019E\\u01A1\\u01A3\\u01A5\\u01A8\\u01AA\\u01AB\\u01AD\\u01B0\\u01B4\\u01B6\\u01B9\\u01BA\\u01BD-\\u01BF\\u01C6\\u01C9\\u01CC\\u01CE\\u01D0\\u01D2\\u01D4\\u01D6\\u01D8\\u01DA\\u01DC\\u01DD\\u01DF\\u01E1\\u01E3\\u01E5\\u01E7\\u01E9\\u01EB\\u01ED\\u01EF\\u01F0\\u01F3\\u01F5\\u01F9\\u01FB\\u01FD\\u01FF\\u0201\\u0203\\u0205\\u0207\\u0209\\u020B\\u020D\\u020F\\u0211\\u0213\\u0215\\u0217\\u0219\\u021B\\u021D\\u021F\\u0221\\u0223\\u0225\\u0227\\u0229\\u022B\\u022D\\u022F\\u0231\\u0233-\\u0239\\u023C\\u023F\\u0240\\u0242\\u0247\\u0249\\u024B\\u024D\\u024F-\\u0293\\u0295-\\u02AF\\u0371\\u0373\\u0377\\u037B-\\u037D\\u0390\\u03AC-\\u03CE\\u03D0\\u03D1\\u03D5-\\u03D7\\u03D9\\u03DB\\u03DD\\u03DF\\u03E1\\u03E3\\u03E5\\u03E7\\u03E9\\u03EB\\u03ED\\u03EF-\\u03F3\\u03F5\\u03F8\\u03FB\\u03FC\\u0430-\\u045F\\u0461\\u0463\\u0465\\u0467\\u0469\\u046B\\u046D\\u046F\\u0471\\u0473\\u0475\\u0477\\u0479\\u047B\\u047D\\u047F\\u0481\\u048B\\u048D\\u048F\\u0491\\u0493\\u0495\\u0497\\u0499\\u049B\\u049D\\u049F\\u04A1\\u04A3\\u04A5\\u04A7\\u04A9\\u04AB\\u04AD\\u04AF\\u04B1\\u04B3\\u04B5\\u04B7\\u04B9\\u04BB\\u04BD\\u04BF\\u04C2\\u04C4\\u04C6\\u04C8\\u04CA\\u04CC\\u04CE\\u04CF\\u04D1\\u04D3\\u04D5\\u04D7\\u04D9\\u04DB\\u04DD\\u04DF\\u04E1\\u04E3\\u04E5\\u04E7\\u04E9\\u04EB\\u04ED\\u04EF\\u04F1\\u04F3\\u04F5\\u04F7\\u04F9\\u04FB\\u04FD\\u04FF\\u0501\\u0503\\u0505\\u0507\\u0509\\u050B\\u050D\\u050F\\u0511\\u0513\\u0515\\u0517\\u0519\\u051B\\u051D\\u051F\\u0521\\u0523\\u0525\\u0527\\u0529\\u052B\\u052D\\u052F\\u0561-\\u0587\\u13F8-\\u13FD\\u1D00-\\u1D2B\\u1D6B-\\u1D77\\u1D79-\\u1D9A\\u1E01\\u1E03\\u1E05\\u1E07\\u1E09\\u1E0B\\u1E0D\\u1E0F\\u1E11\\u1E13\\u1E15\\u1E17\\u1E19\\u1E1B\\u1E1D\\u1E1F\\u1E21\\u1E23\\u1E25\\u1E27\\u1E29\\u1E2B\\u1E2D\\u1E2F\\u1E31\\u1E33\\u1E35\\u1E37\\u1E39\\u1E3B\\u1E3D\\u1E3F\\u1E41\\u1E43\\u1E45\\u1E47\\u1E49\\u1E4B\\u1E4D\\u1E4F\\u1E51\\u1E53\\u1E55\\u1E57\\u1E59\\u1E5B\\u1E5D\\u1E5F\\u1E61\\u1E63\\u1E65\\u1E67\\u1E69\\u1E6B\\u1E6D\\u1E6F\\u1E71\\u1E73\\u1E75\\u1E77\\u1E79\\u1E7B\\u1E7D\\u1E7F\\u1E81\\u1E83\\u1E85\\u1E87\\u1E89\\u1E8B\\u1E8D\\u1E8F\\u1E91\\u1E93\\u1E95-\\u1E9D\\u1E9F\\u1EA1\\u1EA3\\u1EA5\\u1EA7\\u1EA9\\u1EAB\\u1EAD\\u1EAF\\u1EB1\\u1EB3\\u1EB5\\u1EB7\\u1EB9\\u1EBB\\u1EBD\\u1EBF\\u1EC1\\u1EC3\\u1EC5\\u1EC7\\u1EC9\\u1ECB\\u1ECD\\u1ECF\\u1ED1\\u1ED3\\u1ED5\\u1ED7\\u1ED9\\u1EDB\\u1EDD\\u1EDF\\u1EE1\\u1EE3\\u1EE5\\u1EE7\\u1EE9\\u1EEB\\u1EED\\u1EEF\\u1EF1\\u1EF3\\u1EF5\\u1EF7\\u1EF9\\u1EFB\\u1EFD\\u1EFF-\\u1F07\\u1F10-\\u1F15\\u1F20-\\u1F27\\u1F30-\\u1F37\\u1F40-\\u1F45\\u1F50-\\u1F57\\u1F60-\\u1F67\\u1F70-\\u1F7D\\u1F80-\\u1F87\\u1F90-\\u1F97\\u1FA0-\\u1FA7\\u1FB0-\\u1FB4\\u1FB6\\u1FB7\\u1FBE\\u1FC2-\\u1FC4\\u1FC6\\u1FC7\\u1FD0-\\u1FD3\\u1FD6\\u1FD7\\u1FE0-\\u1FE7\\u1FF2-\\u1FF4\\u1FF6\\u1FF7\\u210A\\u210E\\u210F\\u2113\\u212F\\u2134\\u2139\\u213C\\u213D\\u2146-\\u2149\\u214E\\u2184\\u2C30-\\u2C5E\\u2C61\\u2C65\\u2C66\\u2C68\\u2C6A\\u2C6C\\u2C71\\u2C73\\u2C74\\u2C76-\\u2C7B\\u2C81\\u2C83\\u2C85\\u2C87\\u2C89\\u2C8B\\u2C8D\\u2C8F\\u2C91\\u2C93\\u2C95\\u2C97\\u2C99\\u2C9B\\u2C9D\\u2C9F\\u2CA1\\u2CA3\\u2CA5\\u2CA7\\u2CA9\\u2CAB\\u2CAD\\u2CAF\\u2CB1\\u2CB3\\u2CB5\\u2CB7\\u2CB9\\u2CBB\\u2CBD\\u2CBF\\u2CC1\\u2CC3\\u2CC5\\u2CC7\\u2CC9\\u2CCB\\u2CCD\\u2CCF\\u2CD1\\u2CD3\\u2CD5\\u2CD7\\u2CD9\\u2CDB\\u2CDD\\u2CDF\\u2CE1\\u2CE3\\u2CE4\\u2CEC\\u2CEE\\u2CF3\\u2D00-\\u2D25\\u2D27\\u2D2D\\uA641\\uA643\\uA645\\uA647\\uA649\\uA64B\\uA64D\\uA64F\\uA651\\uA653\\uA655\\uA657\\uA659\\uA65B\\uA65D\\uA65F\\uA661\\uA663\\uA665\\uA667\\uA669\\uA66B\\uA66D\\uA681\\uA683\\uA685\\uA687\\uA689\\uA68B\\uA68D\\uA68F\\uA691\\uA693\\uA695\\uA697\\uA699\\uA69B\\uA723\\uA725\\uA727\\uA729\\uA72B\\uA72D\\uA72F-\\uA731\\uA733\\uA735\\uA737\\uA739\\uA73B\\uA73D\\uA73F\\uA741\\uA743\\uA745\\uA747\\uA749\\uA74B\\uA74D\\uA74F\\uA751\\uA753\\uA755\\uA757\\uA759\\uA75B\\uA75D\\uA75F\\uA761\\uA763\\uA765\\uA767\\uA769\\uA76B\\uA76D\\uA76F\\uA771-\\uA778\\uA77A\\uA77C\\uA77F\\uA781\\uA783\\uA785\\uA787\\uA78C\\uA78E\\uA791\\uA793-\\uA795\\uA797\\uA799\\uA79B\\uA79D\\uA79F\\uA7A1\\uA7A3\\uA7A5\\uA7A7\\uA7A9\\uA7B5\\uA7B7\\uA7FA\\uAB30-\\uAB5A\\uAB60-\\uAB65\\uAB70-\\uABBF\\uFB00-\\uFB06\\uFB13-\\uFB17\\uFF41-\\uFF5A])/g;\n"
  },
  {
    "path": "src/util/helpers/non-word-regexp.ts",
    "content": "export const NON_WORD_REGEXP = /[^A-Za-z\\xAA\\xB5\\xBA\\xC0-\\xD6\\xD8-\\xF6\\xF8-\\u02C1\\u02C6-\\u02D1\\u02E0-\\u02E4\\u02EC\\u02EE\\u0370-\\u0374\\u0376\\u0377\\u037A-\\u037D\\u037F\\u0386\\u0388-\\u038A\\u038C\\u038E-\\u03A1\\u03A3-\\u03F5\\u03F7-\\u0481\\u048A-\\u052F\\u0531-\\u0556\\u0559\\u0561-\\u0587\\u05D0-\\u05EA\\u05F0-\\u05F2\\u0620-\\u064A\\u066E\\u066F\\u0671-\\u06D3\\u06D5\\u06E5\\u06E6\\u06EE\\u06EF\\u06FA-\\u06FC\\u06FF\\u0710\\u0712-\\u072F\\u074D-\\u07A5\\u07B1\\u07CA-\\u07EA\\u07F4\\u07F5\\u07FA\\u0800-\\u0815\\u081A\\u0824\\u0828\\u0840-\\u0858\\u08A0-\\u08B4\\u0904-\\u0939\\u093D\\u0950\\u0958-\\u0961\\u0971-\\u0980\\u0985-\\u098C\\u098F\\u0990\\u0993-\\u09A8\\u09AA-\\u09B0\\u09B2\\u09B6-\\u09B9\\u09BD\\u09CE\\u09DC\\u09DD\\u09DF-\\u09E1\\u09F0\\u09F1\\u0A05-\\u0A0A\\u0A0F\\u0A10\\u0A13-\\u0A28\\u0A2A-\\u0A30\\u0A32\\u0A33\\u0A35\\u0A36\\u0A38\\u0A39\\u0A59-\\u0A5C\\u0A5E\\u0A72-\\u0A74\\u0A85-\\u0A8D\\u0A8F-\\u0A91\\u0A93-\\u0AA8\\u0AAA-\\u0AB0\\u0AB2\\u0AB3\\u0AB5-\\u0AB9\\u0ABD\\u0AD0\\u0AE0\\u0AE1\\u0AF9\\u0B05-\\u0B0C\\u0B0F\\u0B10\\u0B13-\\u0B28\\u0B2A-\\u0B30\\u0B32\\u0B33\\u0B35-\\u0B39\\u0B3D\\u0B5C\\u0B5D\\u0B5F-\\u0B61\\u0B71\\u0B83\\u0B85-\\u0B8A\\u0B8E-\\u0B90\\u0B92-\\u0B95\\u0B99\\u0B9A\\u0B9C\\u0B9E\\u0B9F\\u0BA3\\u0BA4\\u0BA8-\\u0BAA\\u0BAE-\\u0BB9\\u0BD0\\u0C05-\\u0C0C\\u0C0E-\\u0C10\\u0C12-\\u0C28\\u0C2A-\\u0C39\\u0C3D\\u0C58-\\u0C5A\\u0C60\\u0C61\\u0C85-\\u0C8C\\u0C8E-\\u0C90\\u0C92-\\u0CA8\\u0CAA-\\u0CB3\\u0CB5-\\u0CB9\\u0CBD\\u0CDE\\u0CE0\\u0CE1\\u0CF1\\u0CF2\\u0D05-\\u0D0C\\u0D0E-\\u0D10\\u0D12-\\u0D3A\\u0D3D\\u0D4E\\u0D5F-\\u0D61\\u0D7A-\\u0D7F\\u0D85-\\u0D96\\u0D9A-\\u0DB1\\u0DB3-\\u0DBB\\u0DBD\\u0DC0-\\u0DC6\\u0E01-\\u0E30\\u0E32\\u0E33\\u0E40-\\u0E46\\u0E81\\u0E82\\u0E84\\u0E87\\u0E88\\u0E8A\\u0E8D\\u0E94-\\u0E97\\u0E99-\\u0E9F\\u0EA1-\\u0EA3\\u0EA5\\u0EA7\\u0EAA\\u0EAB\\u0EAD-\\u0EB0\\u0EB2\\u0EB3\\u0EBD\\u0EC0-\\u0EC4\\u0EC6\\u0EDC-\\u0EDF\\u0F00\\u0F40-\\u0F47\\u0F49-\\u0F6C\\u0F88-\\u0F8C\\u1000-\\u102A\\u103F\\u1050-\\u1055\\u105A-\\u105D\\u1061\\u1065\\u1066\\u106E-\\u1070\\u1075-\\u1081\\u108E\\u10A0-\\u10C5\\u10C7\\u10CD\\u10D0-\\u10FA\\u10FC-\\u1248\\u124A-\\u124D\\u1250-\\u1256\\u1258\\u125A-\\u125D\\u1260-\\u1288\\u128A-\\u128D\\u1290-\\u12B0\\u12B2-\\u12B5\\u12B8-\\u12BE\\u12C0\\u12C2-\\u12C5\\u12C8-\\u12D6\\u12D8-\\u1310\\u1312-\\u1315\\u1318-\\u135A\\u1380-\\u138F\\u13A0-\\u13F5\\u13F8-\\u13FD\\u1401-\\u166C\\u166F-\\u167F\\u1681-\\u169A\\u16A0-\\u16EA\\u16F1-\\u16F8\\u1700-\\u170C\\u170E-\\u1711\\u1720-\\u1731\\u1740-\\u1751\\u1760-\\u176C\\u176E-\\u1770\\u1780-\\u17B3\\u17D7\\u17DC\\u1820-\\u1877\\u1880-\\u18A8\\u18AA\\u18B0-\\u18F5\\u1900-\\u191E\\u1950-\\u196D\\u1970-\\u1974\\u1980-\\u19AB\\u19B0-\\u19C9\\u1A00-\\u1A16\\u1A20-\\u1A54\\u1AA7\\u1B05-\\u1B33\\u1B45-\\u1B4B\\u1B83-\\u1BA0\\u1BAE\\u1BAF\\u1BBA-\\u1BE5\\u1C00-\\u1C23\\u1C4D-\\u1C4F\\u1C5A-\\u1C7D\\u1CE9-\\u1CEC\\u1CEE-\\u1CF1\\u1CF5\\u1CF6\\u1D00-\\u1DBF\\u1E00-\\u1F15\\u1F18-\\u1F1D\\u1F20-\\u1F45\\u1F48-\\u1F4D\\u1F50-\\u1F57\\u1F59\\u1F5B\\u1F5D\\u1F5F-\\u1F7D\\u1F80-\\u1FB4\\u1FB6-\\u1FBC\\u1FBE\\u1FC2-\\u1FC4\\u1FC6-\\u1FCC\\u1FD0-\\u1FD3\\u1FD6-\\u1FDB\\u1FE0-\\u1FEC\\u1FF2-\\u1FF4\\u1FF6-\\u1FFC\\u2071\\u207F\\u2090-\\u209C\\u2102\\u2107\\u210A-\\u2113\\u2115\\u2119-\\u211D\\u2124\\u2126\\u2128\\u212A-\\u212D\\u212F-\\u2139\\u213C-\\u213F\\u2145-\\u2149\\u214E\\u2183\\u2184\\u2C00-\\u2C2E\\u2C30-\\u2C5E\\u2C60-\\u2CE4\\u2CEB-\\u2CEE\\u2CF2\\u2CF3\\u2D00-\\u2D25\\u2D27\\u2D2D\\u2D30-\\u2D67\\u2D6F\\u2D80-\\u2D96\\u2DA0-\\u2DA6\\u2DA8-\\u2DAE\\u2DB0-\\u2DB6\\u2DB8-\\u2DBE\\u2DC0-\\u2DC6\\u2DC8-\\u2DCE\\u2DD0-\\u2DD6\\u2DD8-\\u2DDE\\u2E2F\\u3005\\u3006\\u3031-\\u3035\\u303B\\u303C\\u3041-\\u3096\\u309D-\\u309F\\u30A1-\\u30FA\\u30FC-\\u30FF\\u3105-\\u312D\\u3131-\\u318E\\u31A0-\\u31BA\\u31F0-\\u31FF\\u3400-\\u4DB5\\u4E00-\\u9FD5\\uA000-\\uA48C\\uA4D0-\\uA4FD\\uA500-\\uA60C\\uA610-\\uA61F\\uA62A\\uA62B\\uA640-\\uA66E\\uA67F-\\uA69D\\uA6A0-\\uA6E5\\uA717-\\uA71F\\uA722-\\uA788\\uA78B-\\uA7AD\\uA7B0-\\uA7B7\\uA7F7-\\uA801\\uA803-\\uA805\\uA807-\\uA80A\\uA80C-\\uA822\\uA840-\\uA873\\uA882-\\uA8B3\\uA8F2-\\uA8F7\\uA8FB\\uA8FD\\uA90A-\\uA925\\uA930-\\uA946\\uA960-\\uA97C\\uA984-\\uA9B2\\uA9CF\\uA9E0-\\uA9E4\\uA9E6-\\uA9EF\\uA9FA-\\uA9FE\\uAA00-\\uAA28\\uAA40-\\uAA42\\uAA44-\\uAA4B\\uAA60-\\uAA76\\uAA7A\\uAA7E-\\uAAAF\\uAAB1\\uAAB5\\uAAB6\\uAAB9-\\uAABD\\uAAC0\\uAAC2\\uAADB-\\uAADD\\uAAE0-\\uAAEA\\uAAF2-\\uAAF4\\uAB01-\\uAB06\\uAB09-\\uAB0E\\uAB11-\\uAB16\\uAB20-\\uAB26\\uAB28-\\uAB2E\\uAB30-\\uAB5A\\uAB5C-\\uAB65\\uAB70-\\uABE2\\uAC00-\\uD7A3\\uD7B0-\\uD7C6\\uD7CB-\\uD7FB\\uF900-\\uFA6D\\uFA70-\\uFAD9\\uFB00-\\uFB06\\uFB13-\\uFB17\\uFB1D\\uFB1F-\\uFB28\\uFB2A-\\uFB36\\uFB38-\\uFB3C\\uFB3E\\uFB40\\uFB41\\uFB43\\uFB44\\uFB46-\\uFBB1\\uFBD3-\\uFD3D\\uFD50-\\uFD8F\\uFD92-\\uFDC7\\uFDF0-\\uFDFB\\uFE70-\\uFE74\\uFE76-\\uFEFC\\uFF21-\\uFF3A\\uFF41-\\uFF5A\\uFF66-\\uFFBE\\uFFC2-\\uFFC7\\uFFCA-\\uFFCF\\uFFD2-\\uFFD7\\uFFDA-\\uFFDC0-9\\xB2\\xB3\\xB9\\xBC-\\xBE\\u0660-\\u0669\\u06F0-\\u06F9\\u07C0-\\u07C9\\u0966-\\u096F\\u09E6-\\u09EF\\u09F4-\\u09F9\\u0A66-\\u0A6F\\u0AE6-\\u0AEF\\u0B66-\\u0B6F\\u0B72-\\u0B77\\u0BE6-\\u0BF2\\u0C66-\\u0C6F\\u0C78-\\u0C7E\\u0CE6-\\u0CEF\\u0D66-\\u0D75\\u0DE6-\\u0DEF\\u0E50-\\u0E59\\u0ED0-\\u0ED9\\u0F20-\\u0F33\\u1040-\\u1049\\u1090-\\u1099\\u1369-\\u137C\\u16EE-\\u16F0\\u17E0-\\u17E9\\u17F0-\\u17F9\\u1810-\\u1819\\u1946-\\u194F\\u19D0-\\u19DA\\u1A80-\\u1A89\\u1A90-\\u1A99\\u1B50-\\u1B59\\u1BB0-\\u1BB9\\u1C40-\\u1C49\\u1C50-\\u1C59\\u2070\\u2074-\\u2079\\u2080-\\u2089\\u2150-\\u2182\\u2185-\\u2189\\u2460-\\u249B\\u24EA-\\u24FF\\u2776-\\u2793\\u2CFD\\u3007\\u3021-\\u3029\\u3038-\\u303A\\u3192-\\u3195\\u3220-\\u3229\\u3248-\\u324F\\u3251-\\u325F\\u3280-\\u3289\\u32B1-\\u32BF\\uA620-\\uA629\\uA6E6-\\uA6EF\\uA830-\\uA835\\uA8D0-\\uA8D9\\uA900-\\uA909\\uA9D0-\\uA9D9\\uA9F0-\\uA9F9\\uAA50-\\uAA59\\uABF0-\\uABF9\\uFF10-\\uFF19]+/g;\n"
  },
  {
    "path": "src/util/helpers.spec.ts",
    "content": "import { BuildError } from './errors';\nimport * as helpers from './helpers';\n\nlet originalEnv: any = null;\ndescribe('helpers', () => {\n\n  beforeEach(() => {\n    originalEnv = process.env;\n    process.env = {};\n  });\n\n  afterEach(() => {\n    process.env = originalEnv;\n  });\n\n  describe('getIntPropertyValue', () => {\n    it('should return an int', () => {\n      // arrange\n      const propertyName = 'test';\n      const propertyValue = '3000';\n      process.env[propertyName] = propertyValue;\n\n      // act\n      const result = helpers.getIntPropertyValue(propertyName);\n\n      // assert\n      expect(result).toEqual(3000);\n    });\n\n    it('should round to an int', () => {\n      // arrange\n      const propertyName = 'test';\n      const propertyValue = '3000.03';\n      process.env[propertyName] = propertyValue;\n\n      // act\n      const result = helpers.getIntPropertyValue(propertyName);\n\n      // assert\n      expect(result).toEqual(3000);\n    });\n\n    it('should round to a NaN', () => {\n      // arrange\n      const propertyName = 'test';\n      const propertyValue = 'tacos';\n      process.env[propertyName] = propertyValue;\n\n      // act\n      const result = helpers.getIntPropertyValue(propertyName);\n\n      // assert\n      expect(result).toEqual(NaN);\n    });\n  });\n\n  describe('getBooleanPropertyValue', () => {\n\n    beforeEach(() => {\n      originalEnv = process.env;\n      process.env = {};\n    });\n\n    afterEach(() => {\n      process.env = originalEnv;\n    });\n\n    it('should return true when value is \"true\"', () => {\n      // arrange\n      const propertyName = 'test';\n      const propertyValue = 'true';\n      process.env[propertyName] = propertyValue;\n      // act\n      const result = helpers.getBooleanPropertyValue(propertyName);\n      // assert\n      expect(result).toEqual(true);\n    });\n\n    it('should return false when value is undefined/null', () => {\n      // arrange\n      const propertyName = 'test';\n      // act\n      const result = helpers.getBooleanPropertyValue(propertyName);\n      // assert\n      expect(result).toEqual(false);\n    });\n\n    it('should return false when value is not \"true\"', () => {\n      // arrange\n      const propertyName = 'test';\n      const propertyValue = 'taco';\n      process.env[propertyName] = propertyValue;\n      // act\n      const result = helpers.getBooleanPropertyValue(propertyName);\n      // assert\n      expect(result).toEqual(false);\n    });\n  });\n\n  describe('processStatsImpl', () => {\n    it('should convert object graph to known module map', () => {\n      // arrange\n      const moduleOne = '/Users/noone/myModuleOne.js';\n      const moduleTwo = '/Users/noone/myModuleTwo.js';\n      const moduleThree = '/Users/noone/myModuleThree.js';\n      const moduleFour = '/Users/noone/myModuleFour.js';\n      const objectGraph: any = {\n        modules: [\n          {\n            identifier: moduleOne,\n            reasons: [\n              {\n                moduleIdentifier: moduleTwo\n              },\n              {\n                moduleIdentifier: moduleThree\n              }\n            ]\n          },\n          {\n            identifier: moduleTwo,\n            reasons: [\n              {\n                moduleIdentifier: moduleThree\n              }\n            ]\n          },\n          {\n            identifier: moduleThree,\n            reasons: [\n              {\n                moduleIdentifier: moduleOne\n              }\n            ]\n          },\n          {\n            identifier: moduleFour,\n            reasons: []\n          }\n        ]\n      };\n      // act\n      const result = helpers.processStatsImpl(objectGraph);\n\n      // assert\n      const setOne = result.get(moduleOne);\n      expect(setOne.has(moduleTwo)).toBeTruthy();\n      expect(setOne.has(moduleThree)).toBeTruthy();\n\n      const setTwo = result.get(moduleTwo);\n      expect(setTwo.has(moduleThree)).toBeTruthy();\n\n      const setThree = result.get(moduleThree);\n      expect(setThree.has(moduleOne)).toBeTruthy();\n\n      const setFour = result.get(moduleFour);\n      expect(setFour.size).toEqual(0);\n    });\n  });\n\n  describe('ensureSuffix', () => {\n    it('should not include the suffix of a string that already has the suffix', () => {\n      expect(helpers.ensureSuffix('dan dan the sunshine man', ' man')).toEqual('dan dan the sunshine man');\n    });\n\n    it('should ensure the suffix of a string without the suffix', () => {\n      expect(helpers.ensureSuffix('dan dan the sunshine', ' man')).toEqual('dan dan the sunshine man');\n    });\n  });\n\n  describe('removeSuffix', () => {\n    it('should remove the suffix of a string that has the suffix', () => {\n      expect(helpers.removeSuffix('dan dan the sunshine man', ' man')).toEqual('dan dan the sunshine');\n    });\n\n    it('should do nothing if the string does not have the suffix', () => {\n      expect(helpers.removeSuffix('dan dan the sunshine man', ' woman')).toEqual('dan dan the sunshine man');\n    });\n  });\n\n  describe('replaceAll', () => {\n    it('should replace a variable', () => {\n      expect(helpers.replaceAll('hello $VAR world', '$VAR', 'my')).toEqual('hello my world');\n    });\n\n    it('should replace a variable with newlines', () => {\n      expect(helpers.replaceAll('hello\\n $VARMORETEXT\\n world', '$VAR', 'NO')).toEqual('hello\\n NOMORETEXT\\n world');\n    });\n\n    it('should replace a variable and handle undefined', () => {\n      expect(helpers.replaceAll('hello $VAR world', '$VAR', undefined)).toEqual('hello  world');\n    });\n  });\n\n  describe('buildErrorToJson', () => {\n    it('should return a pojo', () => {\n      const buildError = new BuildError('message1');\n      buildError.name = 'name1';\n      buildError.stack = 'stack1';\n      buildError.isFatal = true;\n      buildError.hasBeenLogged = false;\n\n      const object = helpers.buildErrorToJson(buildError);\n      expect(object.message).toEqual('message1');\n      expect(object.name).toEqual(buildError.name);\n      expect(object.stack).toEqual(buildError.stack);\n      expect(object.isFatal).toEqual(buildError.isFatal);\n      expect(object.hasBeenLogged).toEqual(buildError.hasBeenLogged);\n    });\n  });\n\n  describe('upperCaseFirst', () => {\n    it('should capitalize a one character string', () => {\n      const result = helpers.upperCaseFirst('t');\n      expect(result).toEqual('T');\n    });\n\n    it('should capitalize the first character of string', () => {\n      const result = helpers.upperCaseFirst('taco');\n      expect(result).toEqual('Taco');\n    });\n  });\n\n  describe('removeCaseFromString', () => {\n    const map = new Map<string, string>();\n    map.set('test', 'test');\n    map.set('TEST', 'test');\n    map.set('testString', 'test string');\n    map.set('testString123', 'test string123');\n    map.set('testString_1_2_3', 'test string 1 2 3');\n    map.set('x_256', 'x 256');\n    map.set('anHTMLTag', 'an html tag');\n    map.set('ID123String', 'id123 string');\n    map.set('Id123String', 'id123 string');\n    map.set('foo bar123', 'foo bar123');\n    map.set('a1bStar', 'a1b star');\n    map.set('CONSTANT_CASE', 'constant case');\n    map.set('CONST123_FOO', 'const123 foo');\n    map.set('FOO_bar', 'foo bar');\n    map.set('dot.case', 'dot case');\n    map.set('path/case', 'path case');\n    map.set('snake_case', 'snake case');\n    map.set('snake_case123', 'snake case123');\n    map.set('snake_case_123', 'snake case 123');\n    map.set('\"quotes\"', 'quotes');\n    map.set('version 0.45.0', 'version 0 45 0');\n    map.set('version 0..78..9', 'version 0 78 9');\n    map.set('version 4_99/4', 'version 4 99 4');\n    map.set('amazon s3 data', 'amazon s3 data');\n    map.set('foo_13_bar', 'foo 13 bar');\n\n    map.forEach((value: string, key: string) => {\n      const result = helpers.removeCaseFromString(key);\n      expect(result).toEqual(value);\n    });\n  });\n\n  describe('sentenceCase', () => {\n    it('should lower case a single word', () => {\n      const resultOne = helpers.sentenceCase('test');\n      const resultTwo = helpers.sentenceCase('TEST');\n      expect(resultOne).toEqual('Test');\n      expect(resultTwo).toEqual('Test');\n    });\n\n    it('should sentence case regular sentence cased strings', () => {\n      const resultOne = helpers.sentenceCase('test string');\n      const resultTwo = helpers.sentenceCase('Test String');\n\n      expect(resultOne).toEqual('Test string');\n      expect(resultTwo).toEqual('Test string');\n    });\n\n    it('should sentence case non-alphanumeric separators', () => {\n      const resultOne = helpers.sentenceCase('dot.case');\n      const resultTwo = helpers.sentenceCase('path/case');\n      expect(resultOne).toEqual('Dot case');\n      expect(resultTwo).toEqual('Path case');\n    });\n  });\n\n  describe('camelCase', () => {\n    it('should lower case a single word', () => {\n      const resultOne = helpers.camelCase('test');\n      const resultTwo = helpers.camelCase('TEST');\n      expect(resultOne).toEqual('test');\n      expect(resultTwo).toEqual('test');\n    });\n\n    it('should camel case regular sentence cased strings', () => {\n      expect(helpers.camelCase('test string')).toEqual('testString');\n      expect(helpers.camelCase('Test String')).toEqual('testString');\n    });\n\n    it('should camel case non-alphanumeric separators', () => {\n      expect(helpers.camelCase('dot.case')).toEqual('dotCase');\n      expect(helpers.camelCase('path/case')).toEqual('pathCase');\n    });\n\n    it('should underscore periods inside numbers', () => {\n      expect(helpers.camelCase('version 1.2.10')).toEqual('version_1_2_10');\n      expect(helpers.camelCase('version 1.21.0')).toEqual('version_1_21_0');\n    });\n\n    it('should camel case pascal cased strings', () => {\n      expect(helpers.camelCase('TestString')).toEqual('testString');\n    });\n\n    it('should camel case non-latin strings', () => {\n      expect(helpers.camelCase('simple éxample')).toEqual('simpleÉxample');\n    });\n  });\n\n  describe('paramCase', () => {\n    it('should param case a single word', () => {\n      expect(helpers.paramCase('test')).toEqual('test');\n      expect(helpers.paramCase('TEST')).toEqual('test');\n    });\n\n    it('should param case regular sentence cased strings', () => {\n      expect(helpers.paramCase('test string')).toEqual('test-string');\n      expect(helpers.paramCase('Test String')).toEqual('test-string');\n    });\n\n    it('should param case non-alphanumeric separators', () => {\n      expect(helpers.paramCase('dot.case')).toEqual('dot-case');\n      expect(helpers.paramCase('path/case')).toEqual('path-case');\n    });\n\n    it('should param case param cased strings', () => {\n      expect(helpers.paramCase('TestString')).toEqual('test-string');\n      expect(helpers.paramCase('testString1_2_3')).toEqual('test-string1-2-3');\n      expect(helpers.paramCase('testString_1_2_3')).toEqual('test-string-1-2-3');\n    });\n\n    it('should param case non-latin strings', () => {\n      expect(helpers.paramCase('My Entrée')).toEqual('my-entrée');\n    });\n  });\n\n  describe('pascalCase', () => {\n    it('should pascal case a single word', () => {\n      expect(helpers.pascalCase('test')).toEqual('Test');\n      expect(helpers.pascalCase('TEST')).toEqual('Test');\n    });\n\n    it('should pascal case regular sentence cased strings', () => {\n      expect(helpers.pascalCase('test string')).toEqual('TestString');\n      expect(helpers.pascalCase('Test String')).toEqual('TestString');\n    });\n\n    it('should pascal case non-alphanumeric separators', () => {\n      expect(helpers.pascalCase('dot.case')).toEqual('DotCase');\n      expect(helpers.pascalCase('path/case')).toEqual('PathCase');\n    });\n\n    it('should pascal case pascal cased strings', () => {\n      expect(helpers.pascalCase('TestString')).toEqual('TestString');\n    });\n  });\n\n  describe('snakeCase', () => {\n    it('should convert the phrase to use underscores', () => {\n      expect(helpers.snakeCase('taco bell')).toEqual('taco_bell');\n    });\n  });\n\n  describe('constantCase', () => {\n    it('should capitalize and separate words by underscore', () => {\n      expect(helpers.constantCase('taco bell')).toEqual('TACO_BELL');\n    });\n\n    it('should convert camel case to correct case', () => {\n      expect(helpers.constantCase('TacoBell')).toEqual('TACO_BELL');\n    });\n  });\n});\n"
  },
  {
    "path": "src/util/helpers.ts",
    "content": "import { randomBytes } from 'crypto';\nimport { basename, dirname, extname, join } from 'path';\nimport { createReadStream, createWriteStream, ensureDir, readdir, readFile, readFileSync, readJson, readJsonSync, remove, unlink, writeFile } from 'fs-extra';\nimport * as osName from 'os-name';\n\nimport * as Constants from './constants';\nimport { BuildError } from './errors';\nimport { BuildContext, DeepLinkConfigEntry, File, WebpackStats, SemverVersion } from './interfaces';\nimport { Logger } from '../logger/logger';\nimport { CAMEL_CASE_REGEXP } from './helpers/camel-case-regexp';\nimport { CAMEL_CASE_UPPER_REGEXP } from './helpers/camel-case-upper-regexp';\nimport { NON_WORD_REGEXP } from './helpers/non-word-regexp';\n\nlet _context: BuildContext;\nlet _deepLinkConfigEntriesMap: Map<string, DeepLinkConfigEntry>;\n\nlet cachedAppScriptsPackageJson: any;\nexport function getAppScriptsPackageJson() {\n  if (!cachedAppScriptsPackageJson) {\n    try {\n      cachedAppScriptsPackageJson = readJsonSync(join(__dirname, '..', '..', 'package.json'));\n    } catch (e) {}\n  }\n  return cachedAppScriptsPackageJson;\n}\n\nexport function getAppScriptsVersion(): string {\n  const appScriptsPackageJson = getAppScriptsPackageJson();\n  return (appScriptsPackageJson && appScriptsPackageJson.version) ? appScriptsPackageJson.version : '';\n}\n\nfunction getUserPackageJson(userRootDir: string) {\n  try {\n    return readJsonSync(join(userRootDir, 'package.json'));\n  } catch (e) {}\n  return null;\n}\n\nexport function getSystemText(userRootDir: string) {\n  const systemData = getSystemData(userRootDir);\n  const d: string[] = [];\n\n  d.push(`Ionic Framework: ${systemData.ionicFramework}`);\n  if (systemData.ionicNative) {\n    d.push(`Ionic Native: ${systemData.ionicNative}`);\n  }\n  d.push(`Ionic App Scripts: ${systemData.ionicAppScripts}`);\n  d.push(`Angular Core: ${systemData.angularCore}`);\n  d.push(`Angular Compiler CLI: ${systemData.angularCompilerCli}`);\n  d.push(`Node: ${systemData.node}`);\n  d.push(`OS Platform: ${systemData.osName}`);\n\n  return d;\n}\n\n\nexport function getSystemData(userRootDir: string) {\n  const d = {\n    ionicAppScripts: getAppScriptsVersion(),\n    ionicFramework: '',\n    ionicNative: '',\n    angularCore: '',\n    angularCompilerCli: '',\n    node: process.version.replace('v', ''),\n    osName: osName()\n  };\n\n  try {\n    const userPackageJson = getUserPackageJson(userRootDir);\n    if (userPackageJson) {\n      const userDependencies = userPackageJson.dependencies;\n      if (userDependencies) {\n        d.ionicFramework = userDependencies['ionic-angular'];\n        d.ionicNative = userDependencies['ionic-native'];\n        d.angularCore = userDependencies['@angular/core'];\n        d.angularCompilerCli = userDependencies['@angular/compiler-cli'];\n      }\n    }\n  } catch (e) {}\n\n  return d;\n}\n\n\nexport function splitLineBreaks(sourceText: string) {\n  if (!sourceText) return [];\n  sourceText = sourceText.replace(/\\\\r/g, '\\n');\n  return sourceText.split('\\n');\n}\n\n\nexport const objectAssign = (Object.assign) ? Object.assign : function (target: any, source: any) {\n  const output = Object(target);\n\n  for (var index = 1; index < arguments.length; index++) {\n    source = arguments[index];\n    if (source !== undefined && source !== null) {\n      for (var key in source) {\n        if (source.hasOwnProperty(key)) {\n          output[key] = source[key];\n        }\n      }\n    }\n  }\n\n  return output;\n};\n\n\nexport function titleCase(str: string) {\n  return str.charAt(0).toUpperCase() + str.substr(1);\n}\n\n\nexport function writeFileAsync(filePath: string, content: string) {\n  return new Promise((resolve, reject) => {\n    writeFile(filePath, content, (err) => {\n      if (err) {\n        return reject(err);\n      }\n      return resolve();\n    });\n  });\n}\n\nexport function readFileAsync(filePath: string) {\n  return new Promise<string>((resolve, reject) => {\n    readFile(filePath, 'utf-8', (err, buffer) => {\n      if (err) {\n        return reject(err);\n      }\n      return resolve(buffer);\n    });\n  });\n}\n\nexport function readJsonAsync(filePath: string): Promise<any> {\n  return new Promise((resolve, reject) => {\n    readJson(filePath, {}, (err, object) => {\n      if (err) {\n        return reject(err);\n      }\n      return resolve(object);\n    });\n  });\n}\n\n\nexport function readAndCacheFile(filePath: string, purge: boolean = false): Promise<string> {\n  const file = _context.fileCache.get(filePath);\n  if (file && !purge) {\n    return Promise.resolve(file.content);\n  }\n  return readFileAsync(filePath).then((fileContent: string) => {\n    _context.fileCache.set(filePath, { path: filePath, content: fileContent});\n    return fileContent;\n  });\n}\n\nexport function unlinkAsync(filePath: string|string[]): Promise<any> {\n  let filePaths: string[];\n\n  if (typeof filePath === 'string') {\n    filePaths = [filePath];\n  } else if (Array.isArray(filePath)) {\n    filePaths = filePath;\n  } else {\n    return Promise.reject('unlinkAsync, invalid filePath type');\n  }\n\n  let promises = filePaths.map(filePath => {\n    return new Promise<void>((resolve, reject) => {\n      unlink(filePath, (err: Error) => {\n        if (err) {\n          return reject(err);\n        }\n        return resolve();\n      });\n    });\n  });\n\n  return Promise.all(promises);\n}\n\nexport function rimRafAsync(directoryPath: string) {\n  return new Promise<void>((resolve, reject) => {\n    remove(directoryPath, (err: Error) => {\n      if (err) {\n        return reject(err);\n      }\n      return resolve();\n    });\n  });\n}\n\nexport function copyFileAsync(srcPath: string, destPath: string) {\n  return new Promise<void>((resolve, reject) => {\n    const writeStream = createWriteStream(destPath);\n\n    writeStream.on('error', (err: Error) => {\n      reject(err);\n    });\n\n    writeStream.on('close', () => {\n      resolve();\n    });\n\n    createReadStream(srcPath).pipe(writeStream);\n  });\n}\n\nexport function mkDirpAsync(directoryPath: string) {\n  return new Promise((resolve, reject) => {\n    ensureDir(directoryPath, (err: Error) => {\n      if (err) {\n        return reject(err);\n      }\n      return resolve();\n    });\n  });\n}\n\nexport function readDirAsync(pathToDir: string) {\n  return new Promise<string[]>((resolve, reject) => {\n    readdir(pathToDir, (err: Error, fileNames: string[]) => {\n      if (err) {\n        return reject(err);\n      }\n      resolve(fileNames);\n    });\n  });\n}\n\nexport function setContext(context: BuildContext) {\n  _context = context;\n}\n\nexport function getContext() {\n  return _context;\n}\n\nexport function setParsedDeepLinkConfig(map: Map<string, DeepLinkConfigEntry>) {\n  _deepLinkConfigEntriesMap = map;\n}\n\nexport function getParsedDeepLinkConfig(): Map<string, DeepLinkConfigEntry> {\n  return _deepLinkConfigEntriesMap;\n}\n\nexport function transformSrcPathToTmpPath(originalPath: string, context: BuildContext) {\n  return originalPath.replace(context.srcDir, context.tmpDir);\n}\n\nexport function transformTmpPathToSrcPath(originalPath: string, context: BuildContext) {\n  return originalPath.replace(context.tmpDir, context.srcDir);\n}\n\nexport function changeExtension(filePath: string, newExtension: string) {\n  const dir = dirname(filePath);\n  const extension = extname(filePath);\n  const extensionlessfileName = basename(filePath, extension);\n  const newFileName = extensionlessfileName + newExtension;\n  return join(dir, newFileName);\n}\n\nexport function escapeHtml(unsafe: string) {\n  return unsafe\n         .replace(/&/g, '&amp;')\n         .replace(/</g, '&lt;')\n         .replace(/>/g, '&gt;')\n         .replace(/\"/g, '&quot;')\n         .replace(/'/g, '&#039;');\n}\n\nexport function escapeStringForRegex(input: string) {\n  return input.replace(/[\\-\\[\\]\\/\\{\\}\\(\\)\\*\\+\\?\\.\\\\\\^\\$\\|]/g, '\\\\$&');\n}\n\nexport function rangeReplace(source: string, startIndex: number, endIndex: number, newContent: string) {\n  return source.substring(0, startIndex) + newContent + source.substring(endIndex);\n}\n\nexport function stringSplice(source: string, startIndex: number, numToDelete: number, newContent: string) {\n  return source.slice(0, startIndex) + newContent + source.slice(startIndex + Math.abs(numToDelete));\n}\n\nexport function toUnixPath(filePath: string) {\n  return filePath.replace(/\\\\/g, '/');\n}\n\nexport function generateRandomHexString(numCharacters: number) {\n  return randomBytes(Math.ceil(numCharacters / 2)).toString('hex').slice(0, numCharacters);\n}\n\nexport function getStringPropertyValue(propertyName: string): string {\n  const result = process.env[propertyName];\n  return result;\n}\n\nexport function getIntPropertyValue(propertyName: string): number {\n  const result = process.env[propertyName];\n  return parseInt(result, 0);\n}\n\nexport function getBooleanPropertyValue(propertyName: string): boolean {\n  const result = process.env[propertyName];\n  return result === 'true';\n}\n\nexport function convertFilePathToNgFactoryPath(filePath: string) {\n  const directory = dirname(filePath);\n  const extension = extname(filePath);\n  const extensionlessFileName = basename(filePath, extension);\n  const ngFactoryFileName = extensionlessFileName + '.ngfactory' + extension;\n  return join(directory, ngFactoryFileName);\n}\n\nexport function printDependencyMap(map: Map<string, Set<string>>) {\n  map.forEach((dependencySet: Set<string>, filePath: string) => {\n    Logger.unformattedDebug('\\n\\n');\n    Logger.unformattedDebug(`${filePath} is imported by the following files:`);\n    dependencySet.forEach((importeePath: string) => {\n      Logger.unformattedDebug(`   ${importeePath}`);\n    });\n  });\n}\n\nexport function webpackStatsToDependencyMap(context: BuildContext, stats: any) {\n  const statsObj = stats.toJson({\n    source: false,\n    timings: false,\n    version: false,\n    errorDetails: false,\n    chunks: false,\n    chunkModules: false\n  });\n  return processStatsImpl(statsObj);\n}\n\nexport function processStatsImpl(webpackStats: WebpackStats) {\n  const dependencyMap = new Map<string, Set<string>>();\n  if (webpackStats && webpackStats.modules) {\n      webpackStats.modules.forEach(webpackModule => {\n      const moduleId = purgeWebpackPrefixFromPath(webpackModule.identifier);\n      const dependencySet = new Set<string>();\n      webpackModule.reasons.forEach(webpackDependency => {\n        const depId = purgeWebpackPrefixFromPath(webpackDependency.moduleIdentifier);\n        dependencySet.add(depId);\n      });\n      dependencyMap.set(moduleId, dependencySet);\n    });\n  }\n\n  return dependencyMap;\n}\n\nexport function purgeWebpackPrefixFromPath(filePath: string) {\n  return filePath.replace(process.env[Constants.ENV_WEBPACK_LOADER], '').replace('!', '');\n}\n\nexport function replaceAll(input: string, toReplace: string, replacement: string) {\n  if (!replacement) {\n    replacement = '';\n  }\n\n  return input.split(toReplace).join(replacement);\n}\n\nexport function ensureSuffix(input: string, suffix: string) {\n  if (!input.endsWith(suffix)) {\n    input += suffix;\n  }\n\n  return input;\n}\n\nexport function removeSuffix(input: string, suffix: string) {\n  if (input.endsWith(suffix)) {\n    input = input.substring(0, input.length - suffix.length);\n  }\n\n  return input;\n}\n\nexport function buildErrorToJson(buildError: BuildError) {\n  return {\n    message: buildError.message,\n    name: buildError.name,\n    stack: buildError.stack,\n    hasBeenLogged: buildError.hasBeenLogged,\n    isFatal: buildError.isFatal\n  };\n}\n\nexport function jsonToBuildError(nonTypedBuildError: any) {\n  const error = new BuildError(new Error(nonTypedBuildError.message));\n  error.name = nonTypedBuildError.name;\n  error.stack = nonTypedBuildError.stack;\n  error.hasBeenLogged = nonTypedBuildError.hasBeenLogged;\n  error.isFatal = nonTypedBuildError.isFatal;\n  return error;\n}\n\nexport function upperCaseFirst(input: string) {\n  if (input.length > 1) {\n    return input.charAt(0).toUpperCase() + input.substr(1);\n  }\n  return input.toUpperCase();\n}\n\nexport function sentenceCase(input: string) {\n  const noCase = removeCaseFromString(input);\n  return upperCaseFirst(noCase);\n}\n\nexport function snakeCase(input: string) {\n  return removeCaseFromString(input, '_');\n}\n\nexport function constantCase(input: string) {\n  return snakeCase(input).toUpperCase();\n}\n\nexport function camelCase(input: string) {\n  input = removeCaseFromString(input);\n  input = input.replace(/ (?=\\d)/g, '_');\n  return input.replace(/ (.)/g, (m: string, arg: string) => {\n    return arg.toUpperCase();\n  });\n}\n\nexport function paramCase(input: string) {\n  return removeCaseFromString(input, '-');\n}\n\nexport function pascalCase(input: string) {\n  return upperCaseFirst(camelCase(input));\n}\n\nexport function removeCaseFromString(input: string, inReplacement?: string) {\n  const replacement = inReplacement && inReplacement.length > 0 ? inReplacement : ' ';\n\n  function replace (match: string, index: number, value: string) {\n    if (index === 0 || index === (value.length - match.length)) {\n      return '';\n    }\n\n    return replacement;\n  }\n\n  const modified = input\n    // Support camel case (\"camelCase\" -> \"camel Case\").\n    .replace(CAMEL_CASE_REGEXP, '$1 $2')\n    // Support odd camel case (\"CAMELCase\" -> \"CAMEL Case\").\n    .replace(CAMEL_CASE_UPPER_REGEXP, '$1 $2')\n    // Remove all non-word characters and replace with a single space.\n    .replace(NON_WORD_REGEXP, replace);\n\n  return modified.toLowerCase();\n}\n\nexport function semverStringToObject(semverString: string): SemverVersion {\n  const versionArray = semverString.split('.');\n  return {\n    major: parseInt(versionArray[0], 10),\n    minor: parseInt(versionArray[1], 10),\n    patch: parseInt(versionArray[2], 10)\n  };\n}\n"
  },
  {
    "path": "src/util/hybrid-file-system-factory.ts",
    "content": "import { HybridFileSystem } from './hybrid-file-system';\nimport { getContext } from './helpers';\n\nlet instance: HybridFileSystem = null;\n\nexport function getInstance(writeToDisk: boolean) {\n  if (!instance) {\n    instance = new HybridFileSystem(getContext().fileCache);\n  }\n  instance.setWriteToDisk(writeToDisk);\n  return instance;\n}\n"
  },
  {
    "path": "src/util/hybrid-file-system.ts",
    "content": "import { basename, dirname, join } from 'path';\nimport { FileSystem, VirtualFileSystem } from './interfaces';\nimport { FileCache } from './file-cache';\nimport { VirtualDirStats, VirtualFileStats } from './virtual-file-utils';\n\nexport class HybridFileSystem implements FileSystem, VirtualFileSystem {\n\n  private filesStats: { [filePath: string]: VirtualFileStats } = {};\n  private directoryStats: { [filePath: string]: VirtualDirStats } = {};\n  private inputFileSystem: FileSystem;\n  private outputFileSystem: FileSystem;\n  private writeToDisk: boolean;\n\n  constructor(private fileCache: FileCache) {\n  }\n\n  setInputFileSystem(fs: FileSystem) {\n    this.inputFileSystem = fs;\n  }\n\n  setOutputFileSystem(fs: FileSystem) {\n    this.outputFileSystem = fs;\n  }\n\n  setWriteToDisk(write: boolean) {\n    this.writeToDisk = write;\n  }\n\n  isSync() {\n    return this.inputFileSystem.isSync();\n  }\n\n  stat(path: string, callback: Function): any {\n    // first check the fileStats\n    const fileStat = this.filesStats[path];\n    if (fileStat) {\n      return callback(null, fileStat);\n    }\n    // then check the directory stats\n    const directoryStat = this.directoryStats[path];\n    if (directoryStat) {\n      return callback(null, directoryStat);\n    }\n    // fallback to list\n    return this.inputFileSystem.stat(path, callback);\n  }\n\n  readdir(path: string, callback: Function): any {\n    return this.inputFileSystem.readdir(path, callback);\n  }\n\n  readJson(path: string, callback: Function): any {\n    return this.inputFileSystem.readJson(path, callback);\n  }\n\n  readlink(path: string, callback: Function): any {\n    return this.inputFileSystem.readlink(path, (err: Error, response: any) => {\n      callback(err, response);\n    });\n  }\n\n  purge(pathsToPurge: string[]): void {\n    if (this.fileCache) {\n      for (const path of pathsToPurge) {\n        this.fileCache.remove(path);\n      }\n    }\n  }\n\n  readFile(path: string, callback: Function): any {\n    const file = this.fileCache.get(path);\n    if (file) {\n      callback(null, new Buffer(file.content));\n      return;\n    }\n    return this.inputFileSystem.readFile(path, callback);\n  }\n\n  addVirtualFile(filePath: string, fileContent: string) {\n    this.fileCache.set(filePath, { path: filePath, content: fileContent });\n    const fileStats = new VirtualFileStats(filePath, fileContent);\n    this.filesStats[filePath] = fileStats;\n    const directoryPath = dirname(filePath);\n    const directoryStats = new VirtualDirStats(directoryPath);\n    this.directoryStats[directoryPath] = directoryStats;\n  }\n\n  getFileContent(filePath: string) {\n    const file = this.fileCache.get(filePath);\n    if (file) {\n      return file.content;\n    }\n    return null;\n  }\n\n  getDirectoryStats(path: string): VirtualDirStats {\n    return this.directoryStats[path];\n  }\n\n  getSubDirs(directoryPath: string): string[] {\n    return Object.keys(this.directoryStats)\n      .filter(filePath => dirname(filePath) === directoryPath)\n      .map(filePath => basename(directoryPath));\n  }\n\n  getFileNamesInDirectory(directoryPath: string): string[] {\n    return Object.keys(this.filesStats).filter(filePath => dirname(filePath) === directoryPath).map(filePath => basename(filePath));\n  }\n\n  getAllFileStats(): { [filePath: string]: VirtualFileStats } {\n    return this.filesStats;\n  }\n\n  getAllDirStats():  { [filePath: string]: VirtualDirStats } {\n    return this.directoryStats;\n  }\n\n  mkdirp(filePath: string, callback: Function) {\n    if (this.writeToDisk) {\n      return this.outputFileSystem.mkdirp(filePath, callback);\n    }\n    callback();\n  }\n\n  mkdir(filePath: string, callback: Function) {\n    if (this.writeToDisk) {\n      return this.outputFileSystem.mkdir(filePath, callback);\n    }\n    callback();\n  }\n\n  rmdir(filePath: string, callback: Function) {\n    if (this.writeToDisk) {\n      return this.outputFileSystem.rmdir(filePath, callback);\n    }\n    callback();\n  }\n\n  unlink(filePath: string, callback: Function) {\n    if (this.writeToDisk) {\n      return this.outputFileSystem.unlink(filePath, callback);\n    }\n    callback();\n  }\n\n  join(dirPath: string, fileName: string) {\n    return join(dirPath, fileName);\n  }\n\n  writeFile(filePath: string, fileContent: Buffer, callback: Function) {\n    const stringContent = fileContent.toString();\n    this.addVirtualFile(filePath, stringContent);\n    if (this.writeToDisk) {\n      return this.outputFileSystem.writeFile(filePath, fileContent, callback);\n    }\n    callback();\n  }\n}\n"
  },
  {
    "path": "src/util/interfaces.ts",
    "content": "import * as CompilerCLI from '@angular/compiler-cli';\nimport { CompilerHost, CompilerOptions, Program } from 'typescript';\n\nimport { FileCache } from './file-cache';\nimport { VirtualDirStats, VirtualFileStats } from './virtual-file-utils';\n\nexport interface SemverVersion {\n  major: number;\n  minor: number;\n  patch: number;\n}\n\nexport interface BuildContext {\n  rootDir?: string;\n  tmpDir?: string;\n  srcDir?: string;\n  pagesDir?: string;\n  componentsDir?: string;\n  directivesDir?: string;\n  pipesDir?: string;\n  providersDir?: string;\n  wwwDir?: string;\n  wwwIndex?: string;\n  buildDir?: string;\n  outputJsFileName?: string;\n  outputCssFileName?: string;\n  nodeModulesDir?: string;\n  angularCoreDir?: string;\n  typescriptDir?: string;\n  ionicAngularDir?: string;\n  coreCompilerFilePath?: string;\n  coreDir?: string;\n  bundledFilePaths?: string[];\n  moduleFiles?: string[];\n  appNgModulePath?: string;\n  componentsNgModulePath?: string;\n  pipesNgModulePath?: string;\n  directivesNgModulePath?: string;\n  isProd?: boolean;\n  isWatch?: boolean;\n  runAot?: boolean;\n  runMinifyJs?: boolean;\n  runMinifyCss?: boolean;\n  optimizeJs?: boolean;\n  bundler?: string;\n  fileCache?: FileCache;\n  inlineTemplates?: boolean;\n  webpackWatch?: any;\n  ionicGlobal?: any;\n  sourcemapDir?: string;\n\n  sassState?: BuildState;\n  transpileState?: BuildState;\n  templateState?: BuildState;\n  bundleState?: BuildState;\n  deepLinkState?: BuildState;\n\n  // target examples: cordova, browser, electron\n  target?: string;\n\n  // platform examples: ios, android, windows\n  platform?: string;\n\n  angularVersion?: SemverVersion;\n  ionicAngularVersion?: SemverVersion;\n  typescriptVersion?: SemverVersion;\n}\n\n\nexport enum BuildState {\n  SuccessfulBuild,\n  RequiresUpdate,\n  RequiresBuild\n}\n\n\nexport interface WorkerMessage {\n  taskModule?: string;\n  taskWorker?: string;\n  context?: BuildContext;\n  workerConfig?: any;\n  resolve?: any;\n  reject?: any;\n  error?: any;\n  pid?: number;\n}\n\n\nexport interface WorkerProcess {\n  task: string;\n  worker: any;\n}\n\n\nexport interface TaskInfo {\n  fullArg: string;\n  shortArg: string;\n  envVar: string;\n  packageConfig: string;\n  defaultConfigFile: string;\n}\n\n\nexport interface File {\n  path: string;\n  content: string;\n  timestamp?: number;\n}\n\n\nexport interface Diagnostic {\n  level: string;\n  type: string;\n  language: string;\n  header: string;\n  code: string;\n  messageText: string;\n  absFileName: string;\n  relFileName: string;\n  lines: PrintLine[];\n}\n\n\nexport interface PrintLine {\n  lineIndex: number;\n  lineNumber: number;\n  text: string;\n  html: string;\n  errorCharStart: number;\n  errorLength: number;\n}\n\n\nexport interface WsMessage {\n  category: string;\n  type: string;\n  data: any;\n}\n\n\nexport interface BuildUpdateMessage {\n  buildId: number;\n  reloadApp: boolean;\n}\n\n\nexport interface ChangedFile {\n  event: string;\n  filePath: string;\n  ext: string;\n}\n\n\nexport interface FileSystem {\n  isSync(): boolean;\n  stat(path: string, callback: Function): any;\n  readdir(path: string, callback: Function): any;\n  readFile(path: string, callback: Function): any;\n  readJson(path: string, callback: Function): any;\n  readlink(path: string, callback: Function): any;\n  purge(what: any): void;\n  writeFile(filePath: string, fileContent: Buffer, callback: Function): void;\n  mkdirp(filePath: string, callback: Function): void;\n  mkdir(filePath: string, callback: Function): void;\n  rmdir(filePath: string, callback: Function): void;\n  unlink(filePath: string, callback: Function): void;\n}\n\n\nexport interface VirtualFileSystem {\n  addVirtualFile(filePath: string, fileContent: string): void;\n  getFileContent(filePath: string): string;\n  getDirectoryStats(path: string): VirtualDirStats;\n  getSubDirs(directoryPath: string): string[];\n  getFileNamesInDirectory(directoryPath: string): string[];\n  getAllFileStats():  { [filePath: string]: VirtualFileStats };\n  getAllDirStats():  { [filePath: string]: VirtualDirStats };\n}\n\nexport interface DeepLinkDecoratorAndClass {\n  name: string;\n  segment: string;\n  defaultHistory: string[];\n  priority: string;\n  rawString: string;\n  className: string;\n}\n\nexport interface DeepLinkPathInfo {\n  absolutePath: string;\n  userlandModulePath: string;\n  className: string;\n}\n\nexport interface DeepLinkConfigEntry extends DeepLinkDecoratorAndClass, DeepLinkPathInfo {\n}\n\nexport interface AppNgModuleInfo {\n  absolutePath: string;\n  className: string;\n}\n\nexport interface CodegenOptions {\n  angularCompilerOptions: any;\n  cliOptions: any;\n  program: Program;\n  compilerHost: CompilerHost;\n  compilerOptions: CompilerOptions;\n}\n\nexport interface TreeShakeCalcResults {\n  updatedDependencyMap: Map<string, Set<string>>;\n  purgedModules: Map<string, Set<string>>;\n}\n\nexport interface WebpackStats {\n  modules: WebpackModule[];\n}\n\nexport interface WebpackModule {\n  identifier: string;\n  reasons: WebpackDependency[];\n}\n\nexport interface WebpackDependency {\n  moduleIdentifier: string;\n}\n\nexport interface MagicString {\n  overwrite(startIndex: number, endIndex: number, newContent: string): void;\n  toString(): string;\n  prependLeft(index: number, contentToPrepend: string): string;\n}\n\n\nexport interface CoreCompiler {\n  bundle: {\n    (config: {\n      srcDir: string;\n      destDir: string;\n      packages: Packages;\n      debug?: boolean;\n    }): Promise<any>;\n  };\n}\n\n\nexport interface Packages {\n  path?: any;\n  fs?: any;\n  typescript?: any;\n  nodeSass?: any;\n  rollup?: any;\n  uglify?: any;\n}\n"
  },
  {
    "path": "src/util/ionic-project.ts",
    "content": "import * as path from 'path';\nimport * as fs from 'fs';\nimport { promisify } from './promisify';\n\nexport interface IonicProject {\n  name: string;\n  email: string;\n  app_id: string;\n  proxies: {\n    path: string,\n    proxyUrl: string,\n    proxyNoAgent: boolean,\n    rejectUnauthorized: boolean,\n    cookieRewrite: string | boolean,\n  }[];\n}\n\nconst readFilePromise = promisify<Buffer, string>(fs.readFile);\n\nexport function getProjectJson(): Promise<IonicProject> {\n  const projectFile = path.join(process.cwd(), 'ionic.config.json');\n\n  return readFilePromise(projectFile).then(function(textString) {\n    return JSON.parse(textString.toString());\n  });\n}\n"
  },
  {
    "path": "src/util/network.ts",
    "content": "import * as net from 'net';\n\nexport function findClosestOpenPorts(host: string, ports: number[]): Promise<number[]> {\n  const promises = ports.map(port => findClosestOpenPort(host, port));\n  return Promise.all(promises);\n}\n\nexport function findClosestOpenPort(host: string, port: number): Promise<number> {\n  function t(portToCheck: number): Promise<number> {\n    return isPortTaken(host, portToCheck).then(isTaken => {\n      if (!isTaken) {\n        return portToCheck;\n      }\n      return t(portToCheck + 1);\n    });\n  }\n\n  return t(port);\n}\n\nexport function isPortTaken(host: string, port: number): Promise<boolean> {\n  return new Promise((resolve, reject) => {\n    const tester = net.createServer()\n    .once('error', (err: any) => {\n      if (err.code !== 'EADDRINUSE') {\n        return resolve(true);\n      }\n      resolve(true);\n    })\n    .once('listening', () => {\n      tester.once('close', () => {\n        resolve(false);\n      })\n      .close();\n    })\n    .listen(port, host);\n  });\n}\n"
  },
  {
    "path": "src/util/open.ts",
    "content": "import * as childProcess from 'child_process';\n\n\n/**\n * open a file or uri using the default application for the file type.\n *\n * @return {ChildProcess} - the child process object.\n * @param {string} target - the file/uri to open.\n * @param {string} appName - (optional) the application to be used to open the\n *      file (for example, \"chrome\", \"firefox\")\n * @param {function(Error)} callback - called with null on success, or\n *      an error object that contains a property 'code' with the exit\n *      code of the process.\n */\n\nexport default function (target: string, appName: string | Function, callback?: any) {\n  var opener: string;\n\n  if (typeof appName === 'function') {\n    callback = appName;\n    appName = null;\n  }\n\n  switch (process.platform) {\n  case 'darwin':\n    if (typeof appName === 'string') {\n      opener = 'open -a \"' + escape(appName) + '\"';\n    } else {\n      opener = 'open';\n    }\n    break;\n  case 'win32':\n    // if the first parameter to start is quoted, it uses that as the title\n    // so we pass a blank title so we can quote the file we are opening\n    if (typeof appName === 'string') {\n      opener = 'start \"\" \"' + escape(appName) + '\"';\n    } else {\n      opener = 'start \"\"';\n    }\n    break;\n  default:\n    if (typeof appName === 'string') {\n      opener = escape(appName);\n    } else {\n      // use system installed Portlands xdg-open everywhere else\n      opener = 'xdg-open';\n    }\n    break;\n  }\n\n  if (process.env.SUDO_USER) {\n    opener = 'sudo -u ' + process.env.SUDO_USER + ' ' + opener;\n  }\n  return childProcess.exec(opener + ' \"' + escape(target) + '\"', callback);\n}\n\nfunction escape(s: string) {\n  return s.replace(/\"/g, '\\\\\\\"');\n}\n"
  },
  {
    "path": "src/util/promisify.ts",
    "content": "export interface Promisify {\n  <T>(func: (callback: (err: any, result: T) => void) => void): () => Promise<T>;\n  <T, A1>(func: (arg1: A1, callback: (err: any, result: T) => void) => void): (arg1: A1) => Promise<T>;\n  <T, A1, A2>(func: (arg1: A1, arg2: A2, callback: (err: any, result: T) => void) => void): (arg1: A1, arg2: A2) => Promise<T>;\n  <T, A1, A2, A3>(func: (arg1: A1, arg2: A2, arg3: A3, callback: (err: any, result: T) => void) => void): (arg1: A1, arg2: A2, arg3: A3) => Promise<T>;\n  <T, A1, A2, A3, A4>(func: (arg1: A1, arg2: A2, arg3: A3, arg4: A4, callback: (err: any, result: T) => void) => void): (arg1: A1, arg2: A2, arg3: A3, arg4: A4) => Promise<T>;\n  <T, A1, A2, A3, A4, A5>(func: (arg1: A1, arg2: A2, arg3: A3, arg4: A4, arg5: A5, callback: (err: any, result: T) => void) => void): (arg1: A1, arg2: A2, arg3: A3, arg4: A4, arg5: A5) => Promise<T>;\n}\n/**\n * @example: const rReadFile = promisify<Buffer, string>(fs.readFile);\n */\nexport const promisify: Promisify = function(func: any) {\n  return (...args: any[]) => {\n    return new Promise((resolve, reject) => {\n      func(...args, (err: any, response: any) => {\n        if (err) {\n          return reject(err);\n        }\n        resolve(response);\n      });\n    });\n  };\n};\n"
  },
  {
    "path": "src/util/source-maps.spec.ts",
    "content": "import { join } from 'path';\nimport * as Constants from './constants';\nimport * as sourceMaps from './source-maps';\nimport * as helpers from './helpers';\n\ndescribe('source maps', () => {\n  describe('purgeSourceMapsIfNeeded', () => {\n    it('should copy files first, then purge the files', async () => {\n      spyOn(helpers, helpers.getBooleanPropertyValue.name).and.callFake((argument: string) => {\n        if (argument === Constants.ENV_VAR_MOVE_SOURCE_MAPS) {\n          return true;\n        }\n      });\n\n      spyOn(helpers, helpers.mkDirpAsync.name).and.returnValue(Promise.resolve());\n\n      const knownFileNames = ['0.js', '0.js.map', '1.js', '1.js.map', 'main.js', 'main.js.map', 'vendor.js', 'vendor.js.map', 'main.css', 'polyfills.js', 'sw-toolbox.js', 'main.css', 'main.css.map'];\n\n      spyOn(helpers, helpers.readDirAsync.name).and.returnValue(Promise.resolve(knownFileNames));\n\n      const context = {\n        sourcemapDir: join(process.cwd(), 'sourceMapDir'),\n        buildDir: join(process.cwd(), 'www', 'build')\n      };\n\n      const copyFileSpy = spyOn(helpers, helpers.copyFileAsync.name).and.returnValue(Promise.resolve());\n      const unlinkFileSpy = spyOn(helpers, helpers.unlinkAsync.name).and.returnValue(Promise.resolve());\n\n      const result = await sourceMaps.copySourcemaps(context, true);\n      expect(helpers.mkDirpAsync).toHaveBeenCalledTimes(1);\n      expect(helpers.mkDirpAsync).toHaveBeenCalledWith(context.sourcemapDir);\n\n      expect(helpers.readDirAsync).toHaveBeenCalledTimes(1);\n      expect(helpers.readDirAsync).toHaveBeenLastCalledWith(context.buildDir);\n\n      expect(helpers.copyFileAsync).toHaveBeenCalledTimes(3);\n      expect(copyFileSpy.calls.all()[0].args[0]).toEqual(join(context.buildDir, '0.js.map'));\n      expect(copyFileSpy.calls.all()[0].args[1]).toEqual(join(context.sourcemapDir, '0.js.map'));\n      expect(copyFileSpy.calls.all()[1].args[0]).toEqual(join(context.buildDir, '1.js.map'));\n      expect(copyFileSpy.calls.all()[1].args[1]).toEqual(join(context.sourcemapDir, '1.js.map'));\n      expect(copyFileSpy.calls.all()[2].args[0]).toEqual(join(context.buildDir, 'main.js.map'));\n      expect(copyFileSpy.calls.all()[2].args[1]).toEqual(join(context.sourcemapDir, 'main.js.map'));\n\n      expect(helpers.unlinkAsync).toHaveBeenCalledTimes(5);\n      expect(unlinkFileSpy.calls.all()[0].args[0]).toEqual(join(context.buildDir, '0.js.map'));\n      expect(unlinkFileSpy.calls.all()[1].args[0]).toEqual(join(context.buildDir, '1.js.map'));\n      expect(unlinkFileSpy.calls.all()[2].args[0]).toEqual(join(context.buildDir, 'main.js.map'));\n      expect(unlinkFileSpy.calls.all()[3].args[0]).toEqual(join(context.buildDir, 'vendor.js.map'));\n      expect(unlinkFileSpy.calls.all()[4].args[0]).toEqual(join(context.buildDir, 'main.css.map'));\n    });\n\n    it('should copy the files but not purge them after', async () => {\n      spyOn(helpers, helpers.getBooleanPropertyValue.name).and.callFake((argument: string) => {\n        if (argument === Constants.ENV_VAR_MOVE_SOURCE_MAPS) {\n          return true;\n        }\n      });\n\n      spyOn(helpers, helpers.mkDirpAsync.name).and.returnValue(Promise.resolve());\n\n      const knownFileNames = ['0.js', '0.js.map', '1.js', '1.js.map', 'main.js', 'main.js.map', 'vendor.js', 'vendor.js.map', 'main.css', 'polyfills.js', 'sw-toolbox.js', 'main.css', 'main.css.map'];\n\n      spyOn(helpers, helpers.readDirAsync.name).and.returnValue(Promise.resolve(knownFileNames));\n\n      const context = {\n        sourcemapDir: join(process.cwd(), 'sourceMapDir'),\n        buildDir: join(process.cwd(), 'www', 'build')\n      };\n\n      const copyFileSpy = spyOn(helpers, helpers.copyFileAsync.name).and.returnValue(Promise.resolve());\n      const unlinkFileSpy = spyOn(helpers, helpers.unlinkAsync.name).and.returnValue(Promise.resolve());\n\n      const result = await sourceMaps.copySourcemaps(context, false);\n      expect(helpers.mkDirpAsync).toHaveBeenCalledTimes(1);\n      expect(helpers.mkDirpAsync).toHaveBeenCalledWith(context.sourcemapDir);\n\n      expect(helpers.readDirAsync).toHaveBeenCalledTimes(1);\n      expect(helpers.readDirAsync).toHaveBeenLastCalledWith(context.buildDir);\n\n      expect(helpers.copyFileAsync).toHaveBeenCalledTimes(3);\n      expect(copyFileSpy.calls.all()[0].args[0]).toEqual(join(context.buildDir, '0.js.map'));\n      expect(copyFileSpy.calls.all()[0].args[1]).toEqual(join(context.sourcemapDir, '0.js.map'));\n      expect(copyFileSpy.calls.all()[1].args[0]).toEqual(join(context.buildDir, '1.js.map'));\n      expect(copyFileSpy.calls.all()[1].args[1]).toEqual(join(context.sourcemapDir, '1.js.map'));\n      expect(copyFileSpy.calls.all()[2].args[0]).toEqual(join(context.buildDir, 'main.js.map'));\n      expect(copyFileSpy.calls.all()[2].args[1]).toEqual(join(context.sourcemapDir, 'main.js.map'));\n\n      expect(helpers.unlinkAsync).toHaveBeenCalledTimes(0);\n    });\n  });\n});\n"
  },
  {
    "path": "src/util/source-maps.ts",
    "content": "import { join, relative, basename } from 'path';\nimport { ensureDir, mkdirpSync } from 'fs-extra';\nimport * as Constants from './constants';\nimport { copyFileAsync, getBooleanPropertyValue, mkDirpAsync, readDirAsync, unlinkAsync } from './helpers';\nimport { BuildContext } from './interfaces';\n\nexport async function copySourcemaps(context: BuildContext, shouldPurge: boolean) {\n  const copyBeforePurge = getBooleanPropertyValue(Constants.ENV_VAR_MOVE_SOURCE_MAPS);\n  if (copyBeforePurge) {\n    await mkDirpAsync(context.sourcemapDir);\n  }\n\n  const fileNames = await readDirAsync(context.buildDir);\n\n  // only include js source maps\n  const sourceMaps = fileNames.filter(fileName => fileName.endsWith('.map'));\n\n  const toCopy = sourceMaps.filter(fileName => fileName.indexOf('vendor.js') < 0 && fileName.endsWith('.js.map'));\n  const toCopyFullPaths = toCopy.map(fileName => join(context.buildDir, fileName));\n\n  const toPurge = sourceMaps.map(sourceMap => join(context.buildDir, sourceMap));\n\n  const copyFilePromises: Promise<any>[] = [];\n  if (copyBeforePurge) {\n    for (const fullPath of toCopyFullPaths) {\n      const fileName = basename(fullPath);\n      copyFilePromises.push(copyFileAsync(fullPath, join(context.sourcemapDir, fileName)));\n    }\n  }\n\n  await Promise.all(copyFilePromises);\n\n  // okay cool, all of the files have been copied over, so go ahead and blow them all away\n  const purgeFilePromises: Promise<any>[] = [];\n  if (shouldPurge) {\n    for (const fullPath of toPurge) {\n      purgeFilePromises.push(unlinkAsync(fullPath));\n    }\n  }\n\n  return await Promise.all(purgeFilePromises);\n}\n\nexport function purgeSourceMapsIfNeeded(context: BuildContext): Promise<any> {\n  if (getBooleanPropertyValue(Constants.ENV_VAR_GENERATE_SOURCE_MAP)) {\n    // keep the source maps and just return\n    return copySourcemaps(context, false);\n  }\n  return copySourcemaps(context, true);\n}\n"
  },
  {
    "path": "src/util/typescript-utils.spec.ts",
    "content": "import * as tsUtils from './typescript-utils';\n\ndescribe('typescript-utils', () => {\n  describe('getNgModuleClassName', () => {\n    it('should return the NgModule class name', () => {\n      const knownContent = `\nimport { NgModule } from '@angular/core';\nimport { DeepLinkModule } from 'ionic-angular';\n\nimport { HomePage } from './home';\n\n@NgModule({\n  declarations: [\n    HomePage,\n  ],\n  imports: [\n    DeepLinkModule.forChild(HomePage),\n  ]\n})\nexport class HomePageModule {}\n      `;\n\n      const knownPath = '/Users/noone/idk/some-path.module.ts';\n\n      const result = tsUtils.getNgModuleClassName(knownPath, knownContent);\n      expect(result).toEqual('HomePageModule');\n    });\n\n    it('should return the NgModule class name when there are multiple class declarations but only one is decorated', () => {\n      const knownContent = `\nimport { NgModule } from '@angular/core';\nimport { DeepLinkModule } from 'ionic-angular';\n\nimport { HomePage } from './home';\n\n@NgModule({\n  declarations: [\n    HomePage,\n  ],\n  imports: [\n    DeepLinkModule.forChild(HomePage),\n  ]\n})\nexport class HomePageModule {}\n\nexport class TacoBell {\n  constructor() {\n  }\n\n  ionViewDidEnter() {\n    console.log('tacos yo');\n  }\n}\n      `;\n\n      const knownPath = '/Users/noone/idk/some-path.module.ts';\n\n      const result = tsUtils.getNgModuleClassName(knownPath, knownContent);\n      expect(result).toEqual('HomePageModule');\n    });\n\n    it('should throw an error an NgModule isn\\'t found', () => {\n      const knownContent = `\nimport { NgModule } from '@angular/core';\nimport { DeepLinkModule } from 'ionic-angular';\n\nimport { HomePage } from './home';\n\nexport class HomePageModule {}\n\n      `;\n\n      const knownPath = '/Users/noone/idk/some-path.module.ts';\n\n      const knownError = 'Should never happen';\n      try {\n        tsUtils.getNgModuleClassName(knownPath, knownContent);\n        throw new Error(knownError);\n      } catch (ex) {\n        expect(ex.message).not.toEqual(knownError);\n      }\n    });\n\n    it('should throw an error an multiple NgModules are found', () => {\n      const knownContent = `\nimport { NgModule } from '@angular/core';\nimport { DeepLinkModule } from 'ionic-angular';\n\nimport { HomePage } from './home';\n\n@NgModule({\n  declarations: [\n    HomePage,\n  ],\n  imports: [\n    DeepLinkModule.forChild(HomePage),\n  ]\n})\nexport class HomePageModule {}\n\n@NgModule({\n  declarations: [\n    HomePage,\n  ],\n  imports: [\n    DeepLinkModule.forChild(HomePage),\n  ]\n})\nexport class TacoBellModule {}\n\n      `;\n\n      const knownPath = '/Users/noone/idk/some-path.module.ts';\n\n      const knownError = 'Should never happen';\n      try {\n        tsUtils.getNgModuleClassName(knownPath, knownContent);\n        throw new Error(knownError);\n      } catch (ex) {\n        expect(ex.message).not.toEqual(knownError);\n      }\n    });\n  });\n\n  describe('insertNamedImportIfNeeded', () => {\n    it('should return modified file content, which is a string', () => {\n      const filePath = '/path/to/my/file';\n      const fileContent = 'import {A, B, C} from modulePath';\n      const namedImport = 'NamedImport';\n      const fromModule = 'CoolModule';\n\n      const result = tsUtils.insertNamedImportIfNeeded(filePath, fileContent, namedImport, fromModule);\n\n      // TODO: figure out how to match the exact string\n      expect(result).toEqual(jasmine.any(String));\n    });\n\n    it('should return the same file content as the import is already in the file', () => {\n      const filePath = '/path/to/my/file';\n      const fileContent = 'import { A } from \"modulePath\"';\n      const namedImport = 'A';\n      const fromModule = `modulePath`;\n\n      const result = tsUtils.insertNamedImportIfNeeded(filePath, fileContent, namedImport, fromModule);\n\n      expect(result).toEqual(fileContent);\n    });\n  });\n\n  describe('getNgModuleDecorator', () => {\n    it('should return an object', () => {\n      const knownContent = `\nimport { NgModule } from '@angular/core';\nimport { BrowserModule } from '@angular/platform-browser';\nimport { IonicApp, IonicModule } from '../../../../..';\n\nimport { AppComponent } from './app.component';\nimport { RootPageModule } from '../pages/root-page/root-page.module';\n\n@NgModule({\n  declarations: [\n    AppComponent\n  ],\n  imports: [\n    BrowserModule,\n    IonicModule.forRoot(AppComponent),\n    RootPageModule\n  ],\n  bootstrap: [IonicApp],\n})\nexport class AppModule {}\n\n      `;\n\n      const knownPath = '/some/fake/path';\n      const sourceFile = tsUtils.getTypescriptSourceFile(knownPath, knownContent);\n      const result = tsUtils.getNgModuleDecorator('coolFile.ts', sourceFile);\n\n      expect(result).toEqual(jasmine.any(Object));\n    });\n\n    it('should throw an error', () => {\n      const messedUpContent = `\nimport { NgModule } from '@angular/core';\nimport { BrowserModule } from '@angular/platform-browser';\nimport { IonicApp, IonicModule } from '../../../../..';\n\nimport { AppComponent } from './app.component';\nimport { RootPageModule } from '../pages/root-page/root-page.module';\n\n({\n  declarations: [\n    AppComponent\n  ],\n  imports: [\n    BrowserModule,\n    IonicModule.forRoot(AppComponent),\n    RootPageModule\n  ],\n  bootstrap: [IonicApp],\n})\nexport class AppModule {}\n\n      `;\n      const knownPath = '/some/fake/path';\n      const sourceFile = tsUtils.getTypescriptSourceFile(knownPath, messedUpContent);\n\n      expect(() => tsUtils.getNgModuleDecorator('coolFile.ts', sourceFile)).toThrowError('Could not find an \"NgModule\" decorator in coolFile.ts');\n    });\n  });\n});\n\ndescribe('appendNgModuleDeclaration', () => {\n  it('should return a modified file content', () => {\n    const knownContent = `\nimport { NgModule } from '@angular/core';\nimport { BrowserModule } from '@angular/platform-browser';\nimport { IonicApp, IonicModule } from '../../../../..';\n\nimport { AppComponent } from './app.component';\nimport { RootPageModule } from '../pages/root-page/root-page.module';\n\n@NgModule({\n  declarations: [\n    AppComponent\n  ],\n  imports: [\n    BrowserModule,\n    IonicModule.forRoot(AppComponent),\n    RootPageModule\n  ],\n  bootstrap: [IonicApp],\n})\nexport class AppModule {}\n`;\n\n    const knownPath = '/some/fake/path';\n\n    const expectedContent = `\nimport { NgModule } from \\'@angular/core\\';\nimport { BrowserModule } from \\'@angular/platform-browser\\';\nimport { IonicApp, IonicModule } from \\'../../../../..\\';\n\nimport { AppComponent } from \\'./app.component\\';\nimport { RootPageModule } from \\'../pages/root-page/root-page.module\\';\n\n@NgModule({\n  declarations: [\n    AppComponent,\n    CoolComponent\n  ],\n  imports: [\n    BrowserModule,\n    IonicModule.forRoot(AppComponent),\n    RootPageModule\n  ],\n  bootstrap: [IonicApp],\n})\nexport class AppModule {}\n`;\n\n    const result = tsUtils.appendNgModuleDeclaration(knownPath, knownContent, 'CoolComponent');\n    expect(result).toEqual(expectedContent);\n  });\n\n  it('should return a modified file content for providers', () => {\n    const knownContent = `\nimport { NgModule } from '@angular/core';\nimport { BrowserModule } from '@angular/platform-browser';\nimport { IonicApp, IonicModule } from '../../../../..';\n\nimport { AppComponent } from './app.component';\nimport { RootPageModule } from '../pages/root-page/root-page.module';\n\n@NgModule({\n  declarations: [\n    AppComponent\n  ],\n  imports: [\n    BrowserModule,\n    IonicModule.forRoot(AppComponent),\n    RootPageModule\n  ],\n  bootstrap: [IonicApp],\n  providers: []\n})\nexport class AppModule {}\n`;\n\n  const knownPath = '/some/fake/path';\n\n  const expectedContent = `\nimport { NgModule } from \\'@angular/core\\';\nimport { BrowserModule } from \\'@angular/platform-browser\\';\nimport { IonicApp, IonicModule } from \\'../../../../..\\';\n\nimport { AppComponent } from \\'./app.component\\';\nimport { RootPageModule } from \\'../pages/root-page/root-page.module\\';\n\n@NgModule({\n  declarations: [\n    AppComponent\n  ],\n  imports: [\n    BrowserModule,\n    IonicModule.forRoot(AppComponent),\n    RootPageModule\n  ],\n  bootstrap: [IonicApp],\n  providers: [CoolProvider]\n})\nexport class AppModule {}\n`;\n\n  const result = tsUtils.appendNgModuleProvider(knownPath, knownContent, 'CoolProvider');\n  expect(result).toEqual(expectedContent);\n});\n\n  it('should return a modified file content for providers that already has one provider', () => {\n    const knownContent = `\nimport { NgModule } from '@angular/core';\nimport { BrowserModule } from '@angular/platform-browser';\nimport { IonicApp, IonicModule } from '../../../../..';\n\nimport { AppComponent } from './app.component';\nimport { RootPageModule } from '../pages/root-page/root-page.module';\n\n@NgModule({\n  declarations: [\n    AppComponent\n  ],\n  imports: [\n    BrowserModule,\n    IonicModule.forRoot(AppComponent),\n    RootPageModule\n  ],\n  bootstrap: [IonicApp],\n  providers: [AwesomeProvider]\n})\nexport class AppModule {}\n`;\n\n  const knownPath = '/some/fake/path';\n\n  const expectedContent = `\nimport { NgModule } from '@angular/core';\nimport { BrowserModule } from '@angular/platform-browser';\nimport { IonicApp, IonicModule } from '../../../../..';\n\nimport { AppComponent } from './app.component';\nimport { RootPageModule } from '../pages/root-page/root-page.module';\n\n@NgModule({\n  declarations: [\n    AppComponent\n  ],\n  imports: [\n    BrowserModule,\n    IonicModule.forRoot(AppComponent),\n    RootPageModule\n  ],\n  bootstrap: [IonicApp],\n  providers: [AwesomeProvider,\n    CoolProvider]\n})\nexport class AppModule {}\n`;\n\n\n  const result = tsUtils.appendNgModuleProvider(knownPath, knownContent, 'CoolProvider');\n  expect(result).toEqual(expectedContent);\n  });\n});\n\n"
  },
  {
    "path": "src/util/typescript-utils.ts",
    "content": "import * as path from 'path';\n\nimport {\n  CallExpression,\n  ClassDeclaration,\n  Decorator,\n  Identifier,\n  ImportClause,\n  ImportDeclaration,\n  ImportSpecifier,\n  NamedImports,\n  Node,\n  NodeArray,\n  ObjectLiteralElement, // tslint:disable-line: no-unused-variable\n  ObjectLiteralExpression,\n  PropertyAssignment,\n  ArrayLiteralExpression,\n  ScriptTarget,\n  SourceFile,\n  StringLiteral,\n  SyntaxKind,\n  createSourceFile,\n} from 'typescript';\n\nimport { rangeReplace, stringSplice } from './helpers';\n\nexport function getTypescriptSourceFile(filePath: string, fileContent: string, languageVersion: ScriptTarget = ScriptTarget.Latest, setParentNodes: boolean = false): SourceFile {\n  return createSourceFile(filePath, fileContent, languageVersion, setParentNodes);\n}\n\nexport function removeDecorators(fileName: string, source: string): string {\n  const sourceFile = createSourceFile(fileName, source, ScriptTarget.Latest);\n  const decorators = findNodes(sourceFile, sourceFile, SyntaxKind.Decorator, true);\n  decorators.sort((a, b) => b.pos - a.pos);\n  decorators.forEach(d => {\n    source = source.slice(0, d.pos) + source.slice(d.end);\n  });\n\n  return source;\n}\n\nexport function findNodes(sourceFile: SourceFile, node: Node, kind: SyntaxKind, keepGoing = false): Node[] {\n  if (node.kind === kind && !keepGoing) {\n    return [node];\n  }\n\n  return node.getChildren(sourceFile).reduce((result, n) => {\n    return result.concat(findNodes(sourceFile, n, kind, keepGoing));\n  }, node.kind === kind ? [node] : []);\n}\n\nexport function replaceNode(filePath: string, fileContent: string, node: Node, replacement: string): string {\n  const sourceFile = getTypescriptSourceFile(filePath, fileContent, ScriptTarget.Latest, false);\n  const startIndex = node.getStart(sourceFile);\n  const endIndex = node.getEnd();\n  const modifiedContent = rangeReplace(fileContent, startIndex, endIndex, replacement);\n  return modifiedContent;\n}\n\nexport function removeNode(filePath: string, fileContent: string, node: Node) {\n  const sourceFile = getTypescriptSourceFile(filePath, fileContent, ScriptTarget.Latest, false);\n  const startIndex = node.getStart(sourceFile);\n  const endIndex = node.getEnd();\n  const modifiedContent = rangeReplace(fileContent, startIndex, endIndex, '');\n  return modifiedContent;\n}\n\nexport function getNodeStringContent(sourceFile: SourceFile, node: Node) {\n  return sourceFile.getFullText().substring(node.getStart(sourceFile), node.getEnd());\n}\n\nexport function appendAfter(source: string, node: Node, toAppend: string): string {\n  return stringSplice(source, node.getEnd(), 0, toAppend);\n}\n\nexport function appendEmpty(source: string, position: number, toAppend: string): string {\n  return stringSplice(source, position, 0, toAppend);\n}\n\nexport function appendBefore(filePath: string, fileContent: string, node: Node, toAppend: string): string {\n  const sourceFile = getTypescriptSourceFile(filePath, fileContent, ScriptTarget.Latest, false);\n  return stringSplice(fileContent, node.getStart(sourceFile), 0, toAppend);\n}\n\nexport function insertNamedImportIfNeeded(filePath: string, fileContent: string, namedImport: string, fromModule: string) {\n  const sourceFile = getTypescriptSourceFile(filePath, fileContent, ScriptTarget.Latest, false);\n  const allImports = findNodes(sourceFile, sourceFile, SyntaxKind.ImportDeclaration);\n  const maybeImports = allImports.filter((node: ImportDeclaration) => {\n    return node.moduleSpecifier.kind === SyntaxKind.StringLiteral\n      && (node.moduleSpecifier as StringLiteral).text === fromModule;\n  }).filter((node: ImportDeclaration) => {\n    // Remove import statements that are either `import 'XYZ'` or `import * as X from 'XYZ'`.\n    const clause = node.importClause as ImportClause;\n    if (!clause || clause.name || !clause.namedBindings) {\n      return false;\n    }\n    return clause.namedBindings.kind === SyntaxKind.NamedImports;\n  }).map((node: ImportDeclaration) => {\n    return (node.importClause as ImportClause).namedBindings as NamedImports;\n  });\n\n  if (maybeImports.length) {\n    // There's an `import {A, B, C} from 'modulePath'`.\n    // Find if it's in either imports. If so, just return; nothing to do.\n    const hasImportAlready = maybeImports.some((node: NamedImports) => {\n      return node.elements.some((element: ImportSpecifier) => {\n        return element.name.text === namedImport;\n      });\n    });\n    if (hasImportAlready) {\n      // it's already imported, so just return the original text\n      return fileContent;\n    }\n\n    // Just pick the first one and insert at the end of its identifier list.\n    fileContent = appendAfter(fileContent, maybeImports[0].elements[maybeImports[0].elements.length - 1], `, ${namedImport}`);\n  } else {\n    // Find the last import and insert after.\n    fileContent = appendAfter(fileContent, allImports[allImports.length - 1],\n      `\\nimport { ${namedImport} } from '${fromModule}';`);\n  }\n\n  return fileContent;\n}\n\nexport function replaceNamedImport(filePath: string, fileContent: string, namedImportOriginal: string, namedImportReplacement: string) {\n  const sourceFile = getTypescriptSourceFile(filePath, fileContent, ScriptTarget.Latest, false);\n  const allImports = findNodes(sourceFile, sourceFile, SyntaxKind.ImportDeclaration);\n  let modifiedContent = fileContent;\n  allImports.filter((node: ImportDeclaration) => {\n    if (node.importClause && node.importClause.namedBindings) {\n      return node.importClause.namedBindings.kind === SyntaxKind.NamedImports;\n    }\n  }).map((importDeclaration: ImportDeclaration) => {\n    return (importDeclaration.importClause as ImportClause).namedBindings as NamedImports;\n  }).forEach((namedImport: NamedImports) => {\n    return namedImport.elements.forEach((element: ImportSpecifier) => {\n      if (element.name.text === namedImportOriginal) {\n        modifiedContent = replaceNode(filePath, modifiedContent, element, namedImportReplacement);\n      }\n    });\n  });\n\n  return modifiedContent;\n}\n\nexport function replaceImportModuleSpecifier(filePath: string, fileContent: string, moduleSpecifierOriginal: string, moduleSpecifierReplacement: string) {\n  const sourceFile = getTypescriptSourceFile(filePath, fileContent, ScriptTarget.Latest, false);\n  const allImports = findNodes(sourceFile, sourceFile, SyntaxKind.ImportDeclaration);\n  let modifiedContent = fileContent;\n  allImports.forEach((node: ImportDeclaration) => {\n    if (node.moduleSpecifier.kind === SyntaxKind.StringLiteral && (node.moduleSpecifier as StringLiteral).text === moduleSpecifierOriginal) {\n      modifiedContent = replaceNode(filePath, modifiedContent, node.moduleSpecifier, `'${moduleSpecifierReplacement}'`);\n    }\n  });\n  return modifiedContent;\n}\n\nexport function checkIfFunctionIsCalled(filePath: string, fileContent: string, functionName: string) {\n  const sourceFile = getTypescriptSourceFile(filePath, fileContent, ScriptTarget.Latest, false);\n  const allCalls = findNodes(sourceFile, sourceFile, SyntaxKind.CallExpression, true) as CallExpression[];\n  const functionCallList = allCalls.filter(call => call.expression && call.expression.kind === SyntaxKind.Identifier && (call.expression as Identifier).text === functionName);\n  return functionCallList.length > 0;\n}\n\nexport function getClassDeclarations(sourceFile: SourceFile) {\n  return findNodes(sourceFile, sourceFile, SyntaxKind.ClassDeclaration, true) as ClassDeclaration[];\n}\n\nexport function getNgModuleClassName(filePath: string, fileContent: string) {\n  const ngModuleSourceFile = getTypescriptSourceFile(filePath, fileContent);\n  const classDeclarations = getClassDeclarations(ngModuleSourceFile);\n  // find the class with NgModule decorator;\n  const classNameList: string[] = [];\n  classDeclarations.forEach(classDeclaration => {\n    if (classDeclaration && classDeclaration.decorators) {\n      classDeclaration.decorators.forEach(decorator => {\n        if (decorator.expression && (decorator.expression as CallExpression).expression && ((decorator.expression as CallExpression).expression as Identifier).text === NG_MODULE_DECORATOR_TEXT) {\n          const className = (classDeclaration.name as Identifier).text;\n          classNameList.push(className);\n        }\n      });\n    }\n  });\n\n  if (classNameList.length === 0) {\n    throw new Error(`Could not find a class declaration in ${filePath}`);\n  }\n\n  if (classNameList.length > 1) {\n    throw new Error(`Multiple class declarations with NgModule in ${filePath}. The correct class to use could not be determined.`);\n  }\n\n  return classNameList[0];\n}\n\nexport function getNgModuleDecorator(fileName: string, sourceFile: SourceFile) {\n  const ngModuleDecorators: Decorator[] = [];\n  const classDeclarations = getClassDeclarations(sourceFile);\n  classDeclarations.forEach(classDeclaration => {\n    if (classDeclaration && classDeclaration.decorators) {\n      classDeclaration.decorators.forEach(decorator => {\n        if (decorator.expression && (decorator.expression as CallExpression).expression && ((decorator.expression as CallExpression).expression as Identifier).text === NG_MODULE_DECORATOR_TEXT) {\n          ngModuleDecorators.push(decorator);\n        }\n      });\n    }\n  });\n\n  if (ngModuleDecorators.length === 0) {\n    throw new Error(`Could not find an \"NgModule\" decorator in ${fileName}`);\n  }\n\n  if (ngModuleDecorators.length > 1) {\n    throw new Error(`Multiple \"NgModule\" decorators found in ${fileName}. The correct one to use could not be determined`);\n  }\n\n  return ngModuleDecorators[0];\n}\n\nexport function getNgModuleObjectLiteralArg(decorator: Decorator) {\n  const ngModuleArgs = (decorator.expression as CallExpression).arguments;\n  if (!ngModuleArgs || ngModuleArgs.length === 0 || ngModuleArgs.length > 1) {\n    throw new Error(`Invalid NgModule Argument`);\n  }\n  return ngModuleArgs[0] as ObjectLiteralExpression;\n}\n\nexport function findObjectLiteralElementByName(properties: NodeArray<ObjectLiteralElement>, identifierToLookFor: string) {\n  return properties.filter((propertyNode) => {\n    return propertyNode && (propertyNode as PropertyAssignment).name && ((propertyNode as PropertyAssignment).name as Identifier).text === identifierToLookFor;\n  })[0];\n}\n\nexport function appendNgModuleDeclaration(filePath: string, fileContent: string, declaration: string, ): string {\n  const sourceFile = getTypescriptSourceFile(filePath, fileContent, ScriptTarget.Latest, false);\n  const decorator = getNgModuleDecorator(path.basename(filePath), sourceFile);\n  const obj = getNgModuleObjectLiteralArg(decorator);\n  const properties = (findObjectLiteralElementByName(obj.properties, 'declarations') as PropertyAssignment);\n  const declarations = (properties.initializer as ArrayLiteralExpression).elements;\n  if (declarations.length === 0) {\n    return appendEmpty(fileContent, declarations['end'], declaration);\n  } else {\n    return appendAfter(fileContent, declarations[declarations.length - 1], `,\\n    ${declaration}`);\n  }\n}\n\nexport function appendNgModuleProvider(filePath: string, fileContent: string, declaration: string): string {\n  const sourceFile = getTypescriptSourceFile(filePath, fileContent, ScriptTarget.Latest, false);\n  const decorator = getNgModuleDecorator(path.basename(filePath), sourceFile);\n  const obj = getNgModuleObjectLiteralArg(decorator);\n  const properties = (findObjectLiteralElementByName(obj.properties, 'providers') as PropertyAssignment);\n  const providers = (properties.initializer as ArrayLiteralExpression).elements;\n  if (providers.length === 0) {\n    return appendEmpty(fileContent, providers['end'], declaration);\n  } else {\n    return appendAfter(fileContent, providers[providers.length - 1], `,\\n    ${declaration}`);\n  }\n\n}\n\nexport function appendNgModuleExports(filePath: string, fileContent: string, declaration: string): string {\n  const sourceFile = getTypescriptSourceFile(filePath, fileContent, ScriptTarget.Latest, false);\n  const decorator = getNgModuleDecorator(path.basename(filePath), sourceFile);\n  const obj = getNgModuleObjectLiteralArg(decorator);\n  const properties = (findObjectLiteralElementByName(obj.properties, 'exports') as PropertyAssignment);\n  const exportsProp = (properties.initializer as ArrayLiteralExpression).elements;\n  if (exportsProp.length === 0) {\n    return appendEmpty(fileContent, exportsProp['end'], declaration);\n  } else {\n    return appendAfter(fileContent, exportsProp[exportsProp.length - 1], `,\\n    ${declaration}`);\n  }\n\n}\n\nexport const NG_MODULE_DECORATOR_TEXT = 'NgModule';\n"
  },
  {
    "path": "src/util/virtual-file-utils.ts",
    "content": "import { Stats } from 'fs';\n\nconst dev = Math.floor(Math.random() * 10000);\n\nexport class VirtualStats {\n  protected _ctime = new Date();\n  protected _mtime = new Date();\n  protected _atime = new Date();\n  protected _btime = new Date();\n  protected _dev = dev;\n  protected _ino = Math.floor(Math.random() * 100000);\n  protected _mode = parseInt('777', 8);  // RWX for everyone.\n  protected _uid = process.env['UID'] ? parseInt(process.env['UID'], 0) : 0;\n  protected _gid = process.env['GID'] ? parseInt(process.env['GID'], 0) : 0;\n\n  constructor(protected _path: string) {}\n\n  isFile() { return false; }\n  isDirectory() { return false; }\n  isBlockDevice() { return false; }\n  isCharacterDevice() { return false; }\n  isSymbolicLink() { return false; }\n  isFIFO() { return false; }\n  isSocket() { return false; }\n\n  get dev() { return this._dev; }\n  get ino() { return this._ino; }\n  get mode() { return this._mode; }\n  get nlink() { return 1; }  // Default to 1 hard link.\n  get uid() { return this._uid; }\n  get gid() { return this._gid; }\n  get rdev() { return 0; }\n  get size() { return 0; }\n  get blksize() { return 512; }\n  get blocks() { return Math.ceil(this.size / this.blksize); }\n  get atime() { return this._atime; }\n  get mtime() { return this._mtime; }\n  get ctime() { return this._ctime; }\n  get birthtime() { return this._btime; }\n}\n\nexport class VirtualDirStats extends VirtualStats {\n  constructor(_fileName: string) {\n    super(_fileName);\n  }\n\n  isDirectory() { return true; }\n\n  get size() { return 1024; }\n}\n\nexport class VirtualFileStats extends VirtualStats {\n\n  constructor(_fileName: string, private _content: string) {\n    super(_fileName);\n  }\n\n  get content() { return this._content; }\n  set content(v: string) {\n    this._content = v;\n    this._mtime = new Date();\n  }\n\n  isFile() { return true; }\n\n  get size() { return this._content.length; }\n}\n"
  },
  {
    "path": "src/watch.spec.ts",
    "content": "import { join, resolve } from 'path';\n\nimport * as build from './build';\nimport { BuildContext, BuildState, ChangedFile } from './util/interfaces';\nimport { FileCache } from './util/file-cache';\nimport * as watch from './watch';\n\n\ndescribe('watch', () => {\n\n  describe('runBuildUpdate', () => {\n\n    it('should require transpile full build for html file add', () => {\n      const files: ChangedFile[] = [{\n        event: 'add',\n        filePath: 'file1.html',\n        ext: '.html'\n      }];\n      watch.runBuildUpdate(context, files);\n      expect(context.transpileState).toEqual(BuildState.RequiresBuild);\n      expect(context.deepLinkState).toEqual(BuildState.RequiresBuild);\n    });\n\n    it('should require transpile full build for html file change and not already successful bundle', () => {\n      const files: ChangedFile[] = [{\n        event: 'change',\n        filePath: 'file1.html',\n        ext: '.html'\n      }];\n      watch.runBuildUpdate(context, files);\n      expect(context.transpileState).toEqual(BuildState.RequiresBuild);\n      expect(context.deepLinkState).toEqual(BuildState.RequiresBuild);\n    });\n\n    it('should require template update for html file change and already successful bundle', () => {\n      const files: ChangedFile[] = [{\n        event: 'change',\n        filePath: 'file1.html',\n        ext: '.html'\n      }];\n      context.bundleState = BuildState.SuccessfulBuild;\n      watch.runBuildUpdate(context, files);\n      expect(context.templateState).toEqual(BuildState.RequiresUpdate);\n    });\n\n    it('should require sass update for ts file unlink', () => {\n      const files: ChangedFile[] = [{\n        event: 'unlink',\n        filePath: 'file1.ts',\n        ext: '.ts'\n      }];\n      watch.runBuildUpdate(context, files);\n      expect(context.sassState).toEqual(BuildState.RequiresUpdate);\n    });\n\n    it('should require sass update for ts file add', () => {\n      const files: ChangedFile[] = [{\n        event: 'add',\n        filePath: 'file1.ts',\n        ext: '.ts'\n      }];\n      watch.runBuildUpdate(context, files);\n      expect(context.sassState).toEqual(BuildState.RequiresUpdate);\n    });\n\n    it('should require sass update for scss file add', () => {\n      const files: ChangedFile[] = [{\n        event: 'add',\n        filePath: 'file1.scss',\n        ext: '.scss'\n      }];\n      watch.runBuildUpdate(context, files);\n      expect(context.sassState).toEqual(BuildState.RequiresUpdate);\n    });\n\n    it('should require sass update for scss file change', () => {\n      const files: ChangedFile[] = [{\n        event: 'change',\n        filePath: 'file1.scss',\n        ext: '.scss'\n      }];\n      watch.runBuildUpdate(context, files);\n      expect(context.sassState).toEqual(BuildState.RequiresUpdate);\n    });\n\n    it('should require transpile full build for single ts add, but only bundle update when already successful bundle', () => {\n      const files: ChangedFile[] = [{\n        event: 'add',\n        filePath: 'file1.ts',\n        ext: '.ts'\n      }];\n      context.bundleState = BuildState.SuccessfulBuild;\n      watch.runBuildUpdate(context, files);\n      expect(context.transpileState).toEqual(BuildState.RequiresBuild);\n      expect(context.deepLinkState).toEqual(BuildState.RequiresBuild);\n      expect(context.bundleState).toEqual(BuildState.RequiresUpdate);\n    });\n\n    it('should require transpile full build for single ts add', () => {\n      const files: ChangedFile[] = [{\n        event: 'add',\n        filePath: 'file1.ts',\n        ext: '.ts'\n      }];\n      watch.runBuildUpdate(context, files);\n      expect(context.transpileState).toEqual(BuildState.RequiresBuild);\n      expect(context.deepLinkState).toEqual(BuildState.RequiresBuild);\n      expect(context.bundleState).toEqual(BuildState.RequiresBuild);\n    });\n\n    it('should require transpile full build for single ts change and not in file cache', () => {\n      const files: ChangedFile[] = [{\n        event: 'change',\n        filePath: 'file1.ts',\n        ext: '.ts'\n      }];\n      watch.runBuildUpdate(context, files);\n      expect(context.transpileState).toEqual(BuildState.RequiresBuild);\n      expect(context.deepLinkState).toEqual(BuildState.RequiresBuild);\n      expect(context.bundleState).toEqual(BuildState.RequiresBuild);\n    });\n\n    it('should require transpile update only and full bundle build for single ts change and already in file cache and hasnt already had successful bundle', () => {\n      const files: ChangedFile[] = [{\n        event: 'change',\n        filePath: 'file1.ts',\n        ext: '.ts'\n      }];\n      context.bundleState = BuildState.SuccessfulBuild;\n      const resolvedFilePath = resolve('file1.ts');\n      context.fileCache.set(resolvedFilePath, { path: 'file1.ts', content: 'content' });\n      watch.runBuildUpdate(context, files);\n      expect(context.transpileState).toEqual(BuildState.RequiresUpdate);\n      expect(context.deepLinkState).toEqual(BuildState.RequiresUpdate);\n      expect(context.bundleState).toEqual(BuildState.RequiresUpdate);\n    });\n\n    it('should require transpile update only and bundle update for single ts change and already in file cache and bundle already successful', () => {\n      const files: ChangedFile[] = [{\n        event: 'change',\n        filePath: 'file1.ts',\n        ext: '.ts'\n      }];\n      const resolvedFilePath = resolve('file1.ts');\n      context.fileCache.set(resolvedFilePath, { path: 'file1.ts', content: 'content' });\n      watch.runBuildUpdate(context, files);\n      expect(context.transpileState).toEqual(BuildState.RequiresUpdate);\n      expect(context.deepLinkState).toEqual(BuildState.RequiresUpdate);\n      expect(context.bundleState).toEqual(BuildState.RequiresBuild);\n    });\n\n    it('should require transpile full build for multiple ts changes', () => {\n      const files: ChangedFile[] = [{\n        event: 'change',\n        filePath: 'file1.ts',\n        ext: '.ts'\n      }, {\n        event: 'change',\n        filePath: 'file2.ts',\n        ext: '.ts'\n      }];\n      watch.runBuildUpdate(context, files);\n      expect(context.transpileState).toEqual(BuildState.RequiresBuild);\n      expect(context.deepLinkState).toEqual(BuildState.RequiresBuild);\n      expect(context.bundleState).toEqual(BuildState.RequiresBuild);\n    });\n\n    it('should not update bundle state if no transpile changes', () => {\n      const files: ChangedFile[] = [{\n        event: 'change',\n        filePath: 'file1.scss',\n        ext: '.scss'\n      }];\n      watch.runBuildUpdate(context, files);\n      expect(context.bundleState).toEqual(undefined);\n    });\n\n    it('should do nothing if there are no changed files', () => {\n      expect(watch.runBuildUpdate(context, [])).toEqual(null);\n      expect(watch.runBuildUpdate(context, null)).toEqual(null);\n    });\n\n\n    let context: BuildContext;\n    beforeEach(() => {\n      context = {\n        fileCache: new FileCache()\n      };\n    });\n\n  });\n\n  describe('prepareWatcher', () => {\n\n    it('should do nothing when options.ignored is a function', () => {\n      const ignoreFn = function(){};\n      const watcher: watch.Watcher = { options: { ignored: ignoreFn } };\n      const context: BuildContext = { srcDir: '/some/src/' };\n      watch.prepareWatcher(context, watcher);\n      expect(watcher.options.ignored).toBe(ignoreFn);\n    });\n\n    it('should set replacePathVars when options.ignored is a string', () => {\n      const watcher: watch.Watcher = { options: { ignored: join('{{SRC}}', '**', '*.spec.ts') } };\n      const context: BuildContext = { srcDir: join(process.cwd(), 'some', 'src')};\n      watch.prepareWatcher(context, watcher);\n      expect(watcher.options.ignored).toEqual(join(process.cwd(), 'some', 'src', '**', '*.spec.ts'));\n    });\n\n    it('should set replacePathVars when options.ignored is an array of strings', () => {\n      const watcher: watch.Watcher = { options: { ignored: [join('{{SRC}}', '**', '*.spec.ts'), join('{{SRC}}', 'index.html')] } };\n      const context: BuildContext = { srcDir: join(process.cwd(), 'some', 'src')};\n      watch.prepareWatcher(context, watcher);\n      expect((watcher.options.ignored as string[])[0]).toEqual(join(process.cwd(), 'some', 'src', '**', '*.spec.ts'));\n      expect((watcher.options.ignored as string[])[1]).toEqual(join(process.cwd(), 'some', 'src', 'index.html'));\n    });\n\n    it('should set replacePathVars when paths is an array', () => {\n      const watcher: watch.Watcher = { paths: [\n        join('{{SRC}}', 'some', 'path1'),\n        join('{{SRC}}', 'some', 'path2')\n      ] };\n      const context: BuildContext = { srcDir: join(process.cwd(), 'some', 'src')};\n      watch.prepareWatcher(context, watcher);\n      expect(watcher.paths.length).toEqual(2);\n      expect(watcher.paths[0]).toEqual(join(process.cwd(), 'some', 'src', 'some', 'path1'));\n      expect(watcher.paths[1]).toEqual(join(process.cwd(), 'some', 'src', 'some', 'path2'));\n    });\n\n    it('should set replacePathVars when paths is a string', () => {\n      const watcher: watch.Watcher = { paths: join('{{SRC}}', 'some', 'path')};\n      const context: BuildContext = { srcDir: join(process.cwd(), 'some', 'src')};\n      watch.prepareWatcher(context, watcher);\n      expect(watcher.paths).toEqual(join(process.cwd(), 'some', 'src', 'some', 'path'));\n    });\n\n    it('should not set options.ignoreInitial if it was provided', () => {\n      const watcher: watch.Watcher = { options: { ignoreInitial: false } };\n      const context: BuildContext = {};\n      watch.prepareWatcher(context, watcher);\n      expect(watcher.options.ignoreInitial).toEqual(false);\n    });\n\n    it('should set options.ignoreInitial to true if it wasnt provided', () => {\n      const watcher: watch.Watcher = { options: {} };\n      const context: BuildContext = {};\n      watch.prepareWatcher(context, watcher);\n      expect(watcher.options.ignoreInitial).toEqual(true);\n    });\n\n    it('should not set options.cwd from context.rootDir if it was provided', () => {\n      const watcher: watch.Watcher = { options: { cwd: '/my/cwd/' } };\n      const context: BuildContext = { rootDir: '/my/root/dir/' };\n      watch.prepareWatcher(context, watcher);\n      expect(watcher.options.cwd).toEqual('/my/cwd/');\n    });\n\n    it('should set options.cwd from context.rootDir if it wasnt provided', () => {\n      const watcher: watch.Watcher = {};\n      const context: BuildContext = { rootDir: '/my/root/dir/' };\n      watch.prepareWatcher(context, watcher);\n      expect(watcher.options.cwd).toEqual(context.rootDir);\n    });\n\n    it('should create watcher options when not provided', () => {\n      const watcher: watch.Watcher = {};\n      const context: BuildContext = {};\n      watch.prepareWatcher(context, watcher);\n      expect(watcher.options).toBeDefined();\n    });\n\n  });\n\n  describe('queueOrRunBuildUpdate', () => {\n    it('should not queue a build when there isnt an active build', () => {\n      const changedFileOne: ChangedFile = {\n        event: 'change',\n        ext: '.ts',\n        filePath: '/some/fake/path/that/doesnt/matter.ts'\n      };\n      const changedFileTwo: ChangedFile = {\n        event: 'change',\n        ext: '.ts',\n        filePath: '/some/fake/path/that/doesnt/matter2.ts'\n      };\n      const changedFiles = [changedFileOne, changedFileTwo];\n      const context = {};\n\n      spyOn(watch, watch.queueOrRunBuildUpdate.name).and.callThrough();\n      spyOn(build, build.buildUpdate.name).and.returnValue(Promise.resolve());\n\n      const promise = watch.queueOrRunBuildUpdate(changedFiles, context);\n\n      return promise.then(() => {\n        expect(watch.queueOrRunBuildUpdate).toHaveBeenCalledTimes(1);\n        expect(build.buildUpdate).toHaveBeenCalledWith(changedFiles, context);\n        expect(watch.buildUpdatePromise).toEqual(null);\n        expect(watch.queuedChangedFileMap.size).toEqual(0);\n      });\n    });\n\n    it('should not queue changes when subsequent build is called after the first build', () => {\n       const changedFileOne: ChangedFile = {\n        event: 'change',\n        ext: '.ts',\n        filePath: '/some/fake/path/that/doesnt/matter.ts'\n      };\n      const changedFileTwo: ChangedFile = {\n        event: 'change',\n        ext: '.ts',\n        filePath: '/some/fake/path/that/doesnt/matter2.ts'\n      };\n      const changedFiles = [changedFileOne, changedFileTwo];\n      const context = {};\n\n      spyOn(watch, watch.queueOrRunBuildUpdate.name).and.callThrough();\n      spyOn(build, build.buildUpdate.name).and.returnValue(Promise.resolve());\n\n      const promise = watch.queueOrRunBuildUpdate(changedFiles, context);\n      return promise.then(() => {\n        expect(watch.queueOrRunBuildUpdate).toHaveBeenCalledTimes(1);\n        expect(build.buildUpdate).toHaveBeenCalledWith(changedFiles, context);\n        expect(watch.buildUpdatePromise).toEqual(null);\n        expect(watch.queuedChangedFileMap.size).toEqual(0);\n        return watch.queueOrRunBuildUpdate(changedFiles, context);\n      }).then(() => {\n        expect(watch.queueOrRunBuildUpdate).toHaveBeenCalledTimes(2);\n        expect(build.buildUpdate).toHaveBeenCalledWith(changedFiles, context);\n        expect(watch.buildUpdatePromise).toEqual(null);\n        expect(watch.queuedChangedFileMap.size).toEqual(0);\n        return watch.queueOrRunBuildUpdate(changedFiles, context);\n      }).then(() => {\n        expect(watch.queueOrRunBuildUpdate).toHaveBeenCalledTimes(3);\n        expect(build.buildUpdate).toHaveBeenCalledWith(changedFiles, context);\n        expect(watch.buildUpdatePromise).toEqual(null);\n        expect(watch.queuedChangedFileMap.size).toEqual(0);\n      });\n    });\n\n\n    it('should queue up changes when a build is active', () => {\n      const changedFileOne: ChangedFile = {\n        event: 'change',\n        ext: '.ts',\n        filePath: '/some/fake/path/that/doesnt/matter.ts'\n      };\n      const changedFileTwo: ChangedFile = {\n        event: 'change',\n        ext: '.ts',\n        filePath: '/some/fake/path/that/doesnt/matter2.ts'\n      };\n\n      const changedFileThree: ChangedFile = {\n        event: 'change',\n        ext: '.ts',\n        filePath: '/some/fake/path/that/doesnt/matter3.ts'\n      };\n      const changedFileFour: ChangedFile = {\n        event: 'change',\n        ext: '.ts',\n        filePath: '/some/fake/path/that/doesnt/matter4.ts'\n      };\n\n      const changedFileFive: ChangedFile = {\n        event: 'change',\n        ext: '.ts',\n        filePath: '/some/fake/path/that/doesnt/matter5.ts'\n      };\n      const changedFileSix: ChangedFile = {\n        event: 'change',\n        ext: '.ts',\n        filePath: '/some/fake/path/that/doesnt/matter6.ts'\n      };\n\n      const originalChangedFiles = [changedFileOne, changedFileTwo];\n      const secondSetOfChangedFiles = [changedFileThree, changedFileFour];\n      const ThirdSetOfChangedFiles = [changedFileTwo, changedFileFour, changedFileFive, changedFileSix];\n      const context = {};\n\n      let firstPromiseResolve: Function = null;\n      const firstPromise = new Promise((resolve, reject) => {\n        firstPromiseResolve = resolve;\n      });\n      spyOn(watch, watch.queueOrRunBuildUpdate.name).and.callThrough();\n      const buildUpdateSpy = spyOn(build, build.buildUpdate.name).and.callFake((changedFiles: ChangedFile[], context: BuildContext) => {\n        if (changedFiles === originalChangedFiles) {\n          return firstPromise;\n        } else {\n          return Promise.resolve();\n        }\n      });\n\n      // call the original\n      expect(watch.buildUpdatePromise).toBeFalsy();\n      const promise = watch.queueOrRunBuildUpdate(originalChangedFiles, context);\n      expect(watch.buildUpdatePromise).toBeTruthy();\n      expect(watch.queuedChangedFileMap.size).toEqual(0);\n      expect(build.buildUpdate).toHaveBeenCalledTimes(1);\n\n      // okay, call again and it should be queued now\n      watch.queueOrRunBuildUpdate(secondSetOfChangedFiles, context);\n      expect(watch.buildUpdatePromise).toBeTruthy();\n      expect(watch.queuedChangedFileMap.size).toEqual(2);\n      expect(watch.queuedChangedFileMap.get(changedFileThree.filePath)).toEqual(changedFileThree);\n      expect(watch.queuedChangedFileMap.get(changedFileFour.filePath)).toEqual(changedFileFour);\n      expect(build.buildUpdate).toHaveBeenCalledTimes(1);\n\n      // okay, let's queue some more\n      watch.queueOrRunBuildUpdate(ThirdSetOfChangedFiles, context);\n      expect(watch.buildUpdatePromise).toBeTruthy();\n      expect(watch.queuedChangedFileMap.size).toEqual(5);\n      expect(watch.queuedChangedFileMap.get(changedFileTwo.filePath)).toEqual(changedFileTwo);\n      expect(watch.queuedChangedFileMap.get(changedFileThree.filePath)).toEqual(changedFileThree);\n      expect(watch.queuedChangedFileMap.get(changedFileFour.filePath)).toEqual(changedFileFour);\n      expect(watch.queuedChangedFileMap.get(changedFileFive.filePath)).toEqual(changedFileFive);\n      expect(watch.queuedChangedFileMap.get(changedFileSix.filePath)).toEqual(changedFileSix);\n      expect(build.buildUpdate).toHaveBeenCalledTimes(1);\n\n      firstPromiseResolve();\n      return promise.then(() => {\n        expect(watch.buildUpdatePromise).toBeFalsy();\n        expect(watch.queuedChangedFileMap.size).toEqual(0);\n        expect(build.buildUpdate).toHaveBeenCalledTimes(2);\n        expect(buildUpdateSpy.calls.first().args[0]).toEqual(originalChangedFiles);\n        expect(buildUpdateSpy.calls.first().args[1]).toEqual(context);\n        expect(buildUpdateSpy.calls.mostRecent().args[0].length).toEqual(5);\n        // make sure the array contains the elements that we expect it to\n        expect(buildUpdateSpy.calls.mostRecent().args[0].concat().filter((changedFile: ChangedFile) => changedFile === changedFileTwo)[0]).toEqual(changedFileTwo);\n        expect(buildUpdateSpy.calls.mostRecent().args[0].concat().filter((changedFile: ChangedFile) => changedFile === changedFileThree)[0]).toEqual(changedFileThree);\n        expect(buildUpdateSpy.calls.mostRecent().args[0].concat().filter((changedFile: ChangedFile) => changedFile === changedFileFour)[0]).toEqual(changedFileFour);\n        expect(buildUpdateSpy.calls.mostRecent().args[0].concat().filter((changedFile: ChangedFile) => changedFile === changedFileFive)[0]).toEqual(changedFileFive);\n        expect(buildUpdateSpy.calls.mostRecent().args[0].concat().filter((changedFile: ChangedFile) => changedFile === changedFileSix)[0]).toEqual(changedFileSix);\n        expect(buildUpdateSpy.calls.mostRecent().args[1]).toEqual(context);\n      });\n\n    });\n\n    it('should run buildUpdate on the queued files even if the first build update fails', () => {\n      const changedFileOne: ChangedFile = {\n        event: 'change',\n        ext: '.ts',\n        filePath: '/some/fake/path/that/doesnt/matter.ts'\n      };\n      const changedFileTwo: ChangedFile = {\n        event: 'change',\n        ext: '.ts',\n        filePath: '/some/fake/path/that/doesnt/matter2.ts'\n      };\n\n      const changedFileThree: ChangedFile = {\n        event: 'change',\n        ext: '.ts',\n        filePath: '/some/fake/path/that/doesnt/matter3.ts'\n      };\n      const changedFileFour: ChangedFile = {\n        event: 'change',\n        ext: '.ts',\n        filePath: '/some/fake/path/that/doesnt/matter4.ts'\n      };\n\n      const changedFileFive: ChangedFile = {\n        event: 'change',\n        ext: '.ts',\n        filePath: '/some/fake/path/that/doesnt/matter5.ts'\n      };\n      const changedFileSix: ChangedFile = {\n        event: 'change',\n        ext: '.ts',\n        filePath: '/some/fake/path/that/doesnt/matter6.ts'\n      };\n\n      const originalChangedFiles = [changedFileOne, changedFileTwo];\n      const secondSetOfChangedFiles = [changedFileThree, changedFileFour];\n      const ThirdSetOfChangedFiles = [changedFileTwo, changedFileFour, changedFileFive, changedFileSix];\n      const context = {};\n\n      let firstPromiseReject: Function = null;\n      const firstPromise = new Promise((resolve, reject) => {\n        firstPromiseReject = reject;\n      });\n      spyOn(watch, watch.queueOrRunBuildUpdate.name).and.callThrough();\n      const buildUpdateSpy = spyOn(build, build.buildUpdate.name).and.callFake((changedFiles: ChangedFile[], context: BuildContext) => {\n        if (changedFiles === originalChangedFiles) {\n          return firstPromise;\n        } else {\n          return Promise.resolve();\n        }\n      });\n\n      // call the original\n      expect(watch.buildUpdatePromise).toBeFalsy();\n      const promise = watch.queueOrRunBuildUpdate(originalChangedFiles, context);\n      expect(watch.buildUpdatePromise).toBeTruthy();\n      expect(watch.queuedChangedFileMap.size).toEqual(0);\n      expect(build.buildUpdate).toHaveBeenCalledTimes(1);\n\n      // okay, call again and it should be queued now\n      watch.queueOrRunBuildUpdate(secondSetOfChangedFiles, context);\n      expect(watch.buildUpdatePromise).toBeTruthy();\n      expect(watch.queuedChangedFileMap.size).toEqual(2);\n      expect(watch.queuedChangedFileMap.get(changedFileThree.filePath)).toEqual(changedFileThree);\n      expect(watch.queuedChangedFileMap.get(changedFileFour.filePath)).toEqual(changedFileFour);\n      expect(build.buildUpdate).toHaveBeenCalledTimes(1);\n\n      // okay, let's queue some more\n      watch.queueOrRunBuildUpdate(ThirdSetOfChangedFiles, context);\n      expect(watch.buildUpdatePromise).toBeTruthy();\n      expect(watch.queuedChangedFileMap.size).toEqual(5);\n      expect(watch.queuedChangedFileMap.get(changedFileTwo.filePath)).toEqual(changedFileTwo);\n      expect(watch.queuedChangedFileMap.get(changedFileThree.filePath)).toEqual(changedFileThree);\n      expect(watch.queuedChangedFileMap.get(changedFileFour.filePath)).toEqual(changedFileFour);\n      expect(watch.queuedChangedFileMap.get(changedFileFive.filePath)).toEqual(changedFileFive);\n      expect(watch.queuedChangedFileMap.get(changedFileSix.filePath)).toEqual(changedFileSix);\n      expect(build.buildUpdate).toHaveBeenCalledTimes(1);\n\n      firstPromiseReject();\n      return promise.then(() => {\n        expect(watch.buildUpdatePromise).toBeFalsy();\n        expect(watch.queuedChangedFileMap.size).toEqual(0);\n        expect(build.buildUpdate).toHaveBeenCalledTimes(2);\n        expect(buildUpdateSpy.calls.first().args[0]).toEqual(originalChangedFiles);\n        expect(buildUpdateSpy.calls.first().args[1]).toEqual(context);\n        expect(buildUpdateSpy.calls.mostRecent().args[0].length).toEqual(5);\n        // make sure the array contains the elements that we expect it to\n        expect(buildUpdateSpy.calls.mostRecent().args[0].concat().filter((changedFile: ChangedFile) => changedFile === changedFileTwo)[0]).toEqual(changedFileTwo);\n        expect(buildUpdateSpy.calls.mostRecent().args[0].concat().filter((changedFile: ChangedFile) => changedFile === changedFileThree)[0]).toEqual(changedFileThree);\n        expect(buildUpdateSpy.calls.mostRecent().args[0].concat().filter((changedFile: ChangedFile) => changedFile === changedFileFour)[0]).toEqual(changedFileFour);\n        expect(buildUpdateSpy.calls.mostRecent().args[0].concat().filter((changedFile: ChangedFile) => changedFile === changedFileFive)[0]).toEqual(changedFileFive);\n        expect(buildUpdateSpy.calls.mostRecent().args[0].concat().filter((changedFile: ChangedFile) => changedFile === changedFileSix)[0]).toEqual(changedFileSix);\n        expect(buildUpdateSpy.calls.mostRecent().args[1]).toEqual(context);\n      });\n    });\n\n    it('should handle multiple queueing and unqueuing events aka advanced test', () => {\n      const changedFileOne: ChangedFile = {\n        event: 'change',\n        ext: '.ts',\n        filePath: '/some/fake/path/that/doesnt/matter.ts'\n      };\n      const changedFileTwo: ChangedFile = {\n        event: 'change',\n        ext: '.ts',\n        filePath: '/some/fake/path/that/doesnt/matter2.ts'\n      };\n\n      const changedFileThree: ChangedFile = {\n        event: 'change',\n        ext: '.ts',\n        filePath: '/some/fake/path/that/doesnt/matter3.ts'\n      };\n      const changedFileFour: ChangedFile = {\n        event: 'change',\n        ext: '.ts',\n        filePath: '/some/fake/path/that/doesnt/matter4.ts'\n      };\n\n      const changedFileFive: ChangedFile = {\n        event: 'change',\n        ext: '.ts',\n        filePath: '/some/fake/path/that/doesnt/matter5.ts'\n      };\n      const changedFileSix: ChangedFile = {\n        event: 'change',\n        ext: '.ts',\n        filePath: '/some/fake/path/that/doesnt/matter6.ts'\n      };\n\n      const originalChangedFiles = [changedFileOne, changedFileTwo];\n      const secondSetOfChangedFiles = [changedFileThree, changedFileFour];\n      const thirdSetOfChangedFiles = [changedFileTwo, changedFileFour, changedFileFive, changedFileSix];\n      const fourthSetOfChangedFiles = [changedFileOne, changedFileThree];\n      const fifthSetOfChangedFiles = [changedFileFour, changedFileFive, changedFileSix];\n\n      const context = {};\n\n      let firstPromiseResolve: Function = null;\n      let secondPromiseResolve: Function = null;\n      let thirdPromiseResolve: Function = null;\n      const firstPromise = new Promise((resolve, reject) => {\n        firstPromiseResolve = resolve;\n      });\n      const secondPromise = new Promise((resolve, reject) => {\n        secondPromiseResolve = resolve;\n      });\n      const thirdPromise = new Promise((resolve, reject) => {\n        thirdPromiseResolve = resolve;\n      });\n\n      spyOn(watch, watch.queueOrRunBuildUpdate.name).and.callThrough();\n      const buildUpdateSpy = spyOn(build, build.buildUpdate.name).and.callFake((changedFiles: ChangedFile[], context: BuildContext) => {\n        if (changedFiles === originalChangedFiles) {\n          return firstPromise;\n        } else if (changedFiles.length === 5) {\n          // hardcode the length for now as it's easier to detect which array it'll be\n          return secondPromise;\n        } else {\n          return thirdPromise;\n        }\n      });\n\n      // call the original\n      expect(watch.buildUpdatePromise).toBeFalsy();\n      const promise = watch.queueOrRunBuildUpdate(originalChangedFiles, context);\n      expect(watch.buildUpdatePromise).toBeTruthy();\n      expect(watch.queuedChangedFileMap.size).toEqual(0);\n      expect(build.buildUpdate).toHaveBeenCalledTimes(1);\n      expect(buildUpdateSpy.calls.first().args[0]).toEqual(originalChangedFiles);\n      expect(buildUpdateSpy.calls.first().args[1]).toEqual(context);\n\n      // okay, call again and it should be queued now\n      watch.queueOrRunBuildUpdate(secondSetOfChangedFiles, context);\n      expect(watch.buildUpdatePromise).toBeTruthy();\n      expect(watch.queuedChangedFileMap.size).toEqual(2);\n      expect(watch.queuedChangedFileMap.get(changedFileThree.filePath)).toEqual(changedFileThree);\n      expect(watch.queuedChangedFileMap.get(changedFileFour.filePath)).toEqual(changedFileFour);\n      expect(build.buildUpdate).toHaveBeenCalledTimes(1);\n\n      // okay, let's queue some more\n      watch.queueOrRunBuildUpdate(thirdSetOfChangedFiles, context);\n      expect(watch.buildUpdatePromise).toBeTruthy();\n      expect(watch.queuedChangedFileMap.size).toEqual(5);\n      expect(watch.queuedChangedFileMap.get(changedFileTwo.filePath)).toEqual(changedFileTwo);\n      expect(watch.queuedChangedFileMap.get(changedFileThree.filePath)).toEqual(changedFileThree);\n      expect(watch.queuedChangedFileMap.get(changedFileFour.filePath)).toEqual(changedFileFour);\n      expect(watch.queuedChangedFileMap.get(changedFileFive.filePath)).toEqual(changedFileFive);\n      expect(watch.queuedChangedFileMap.get(changedFileSix.filePath)).toEqual(changedFileSix);\n      expect(build.buildUpdate).toHaveBeenCalledTimes(1);\n\n      firstPromiseResolve();\n\n      return firstPromise.then(() => {\n        expect(build.buildUpdate).toHaveBeenCalledTimes(2);\n        expect(buildUpdateSpy.calls.mostRecent().args[0].length).toEqual(5);\n        expect(buildUpdateSpy.calls.mostRecent().args[0].concat().filter((changedFile: ChangedFile) => changedFile === changedFileTwo)[0]).toEqual(changedFileTwo);\n        expect(buildUpdateSpy.calls.mostRecent().args[0].concat().filter((changedFile: ChangedFile) => changedFile === changedFileThree)[0]).toEqual(changedFileThree);\n        expect(buildUpdateSpy.calls.mostRecent().args[0].concat().filter((changedFile: ChangedFile) => changedFile === changedFileFour)[0]).toEqual(changedFileFour);\n        expect(buildUpdateSpy.calls.mostRecent().args[0].concat().filter((changedFile: ChangedFile) => changedFile === changedFileFive)[0]).toEqual(changedFileFive);\n        expect(buildUpdateSpy.calls.mostRecent().args[0].concat().filter((changedFile: ChangedFile) => changedFile === changedFileSix)[0]).toEqual(changedFileSix);\n        expect(buildUpdateSpy.calls.mostRecent().args[1]).toEqual(context);\n\n        // okay, give it more changes so it queues that stuff up\n        // also do some assertions homie\n        watch.queueOrRunBuildUpdate(fourthSetOfChangedFiles, context);\n        expect(watch.buildUpdatePromise).toBeTruthy();\n        expect(watch.queuedChangedFileMap.size).toEqual(2);\n        expect(watch.queuedChangedFileMap.get(changedFileOne.filePath)).toEqual(changedFileOne);\n        expect(watch.queuedChangedFileMap.get(changedFileThree.filePath)).toEqual(changedFileThree);\n\n        // cool beans yo, go ahead and resolve another promise\n        secondPromiseResolve();\n        return secondPromise;\n      }).then(() => {\n        expect(build.buildUpdate).toHaveBeenCalledTimes(3);\n        expect(buildUpdateSpy.calls.mostRecent().args[0].length).toEqual(2);\n        expect(buildUpdateSpy.calls.mostRecent().args[0].concat().filter((changedFile: ChangedFile) => changedFile === changedFileOne)[0]).toEqual(changedFileOne);\n        expect(buildUpdateSpy.calls.mostRecent().args[0].concat().filter((changedFile: ChangedFile) => changedFile === changedFileThree)[0]).toEqual(changedFileThree);\n\n        // okay, give it more changes so it queues that stuff up\n        // also do some assertions homie\n        watch.queueOrRunBuildUpdate(fifthSetOfChangedFiles, context);\n        expect(watch.buildUpdatePromise).toBeTruthy();\n        expect(watch.queuedChangedFileMap.size).toEqual(3);\n        expect(watch.queuedChangedFileMap.get(changedFileFour.filePath)).toEqual(changedFileFour);\n        expect(watch.queuedChangedFileMap.get(changedFileFive.filePath)).toEqual(changedFileFive);\n        expect(watch.queuedChangedFileMap.get(changedFileSix.filePath)).toEqual(changedFileSix);\n\n         // cool beans yo, go ahead and resolve another promise\n        thirdPromiseResolve();\n        return thirdPromise;\n\n      }).then(() => {\n        // return the original promise just to make sure everything is chained together\n        return promise;\n      });\n    });\n  });\n});\n"
  },
  {
    "path": "src/watch.ts",
    "content": "import { extname, join, normalize, resolve as pathResolve } from 'path';\n\nimport * as chokidar from 'chokidar';\n\nimport * as buildTask from './build';\nimport { copyUpdate as copyUpdateHandler} from './copy';\nimport { Logger } from './logger/logger';\nimport { canRunTranspileUpdate } from './transpile';\nimport { fillConfigDefaults, getUserConfigFile, replacePathVars } from './util/config';\nimport * as Constants from './util/constants';\nimport { BuildError } from './util/errors';\nimport { getIntPropertyValue } from './util/helpers';\nimport { BuildContext, BuildState, ChangedFile, TaskInfo } from './util/interfaces';\n\n\n// https://github.com/paulmillr/chokidar\n\nexport function watch(context?: BuildContext, configFile?: string) {\n  configFile = getUserConfigFile(context, taskInfo, configFile);\n\n  // Override all build options if watch is ran.\n  context.isProd = false;\n  context.optimizeJs = false;\n  context.runMinifyJs = false;\n  context.runMinifyCss = false;\n  context.runAot = false;\n\n  // Ensure that watch is true in context\n  context.isWatch = true;\n\n  context.sassState = BuildState.RequiresBuild;\n  context.transpileState = BuildState.RequiresBuild;\n  context.bundleState = BuildState.RequiresBuild;\n  context.deepLinkState = BuildState.RequiresBuild;\n\n  const logger = new Logger('watch');\n\n  function buildDone() {\n    return startWatchers(context, configFile).then(() => {\n      logger.ready();\n    });\n  }\n\n  return buildTask.build(context)\n    .then(buildDone, (err: BuildError) => {\n      if (err && err.isFatal) {\n        throw err;\n      } else {\n        buildDone();\n      }\n    })\n    .catch(err => {\n      throw logger.fail(err);\n    });\n}\n\n\nfunction startWatchers(context: BuildContext, configFile: string) {\n  const watchConfig: WatchConfig = fillConfigDefaults(configFile, taskInfo.defaultConfigFile);\n\n  const promises: Promise<any>[] = [];\n  Object.keys(watchConfig).forEach((key) => {\n    promises.push(startWatcher(key, watchConfig[key], context));\n  });\n\n  return Promise.all(promises);\n}\n\n\nfunction startWatcher(name: string, watcher: Watcher, context: BuildContext) {\n  return new Promise((resolve, reject) => {\n\n    // If a file isn't found (probably other scenarios too),\n    // Chokidar watches don't always trigger the ready or error events\n    // so set a timeout, and clear it if they do fire\n    // otherwise, just reject the promise and log an error\n    const timeoutId = setTimeout(() => {\n      let filesWatchedString: string = null;\n      if (typeof watcher.paths === 'string') {\n        filesWatchedString = watcher.paths;\n      } else if (Array.isArray(watcher.paths)) {\n        filesWatchedString = watcher.paths.join(', ');\n      }\n      reject(new BuildError(`A watch configured to watch the following paths failed to start. It likely that a file referenced does not exist: ${filesWatchedString}`));\n    }, getIntPropertyValue(Constants.ENV_START_WATCH_TIMEOUT));\n\n    prepareWatcher(context, watcher);\n\n    if (!watcher.paths) {\n      Logger.error(`watcher config, entry ${name}: missing \"paths\"`);\n      resolve();\n      return;\n    }\n\n    if (!watcher.callback) {\n      Logger.error(`watcher config, entry ${name}: missing \"callback\"`);\n      resolve();\n      return;\n    }\n\n    const chokidarWatcher = chokidar.watch(<any>watcher.paths, watcher.options);\n\n    let eventName = 'all';\n    if (watcher.eventName) {\n      eventName = watcher.eventName;\n    }\n\n    chokidarWatcher.on(eventName, (event: string, filePath: string) => {\n      // if you're listening for a specific event vs 'all',\n      // the event is not included and the first param is the filePath\n      // go ahead and adjust it if filePath is null so it's uniform\n      if (!filePath) {\n        filePath = event;\n        event = watcher.eventName;\n      }\n\n      filePath = normalize(pathResolve(join(context.rootDir, filePath)));\n\n      Logger.debug(`watch callback start, id: ${watchCount}, isProd: ${context.isProd}, event: ${event}, path: ${filePath}`);\n\n      const callbackToExecute = function(event: string, filePath: string, context: BuildContext, watcher: Watcher) {\n        return watcher.callback(event, filePath, context);\n      };\n\n      callbackToExecute(event, filePath, context, watcher)\n        .then(() => {\n          Logger.debug(`watch callback complete, id: ${watchCount}, isProd: ${context.isProd}, event: ${event}, path: ${filePath}`);\n          watchCount++;\n        })\n        .catch(err => {\n          Logger.debug(`watch callback error, id: ${watchCount}, isProd: ${context.isProd}, event: ${event}, path: ${filePath}`);\n          Logger.debug(`${err}`);\n          watchCount++;\n        });\n    });\n\n    chokidarWatcher.on('ready', () => {\n      clearTimeout(timeoutId);\n      Logger.debug(`watcher ready: ${watcher.options.cwd}${watcher.paths}`);\n      resolve();\n    });\n\n    chokidarWatcher.on('error', (err: any) => {\n      clearTimeout(timeoutId);\n      reject(new BuildError(`watcher error: ${watcher.options.cwd}${watcher.paths}: ${err}`));\n    });\n\n  });\n}\n\nexport function prepareWatcher(context: BuildContext, watcher: Watcher) {\n  watcher.options = watcher.options || {};\n\n  if (!watcher.options.cwd) {\n    watcher.options.cwd = context.rootDir;\n  }\n\n  if (typeof watcher.options.ignoreInitial !== 'boolean') {\n    watcher.options.ignoreInitial = true;\n  }\n\n  if (watcher.options.ignored) {\n    if (Array.isArray(watcher.options.ignored)) {\n      watcher.options.ignored = watcher.options.ignored.map(p => normalize(replacePathVars(context, p)));\n    } else if (typeof watcher.options.ignored === 'string') {\n      // it's a string, so just do it once and leave it\n      watcher.options.ignored = normalize(replacePathVars(context, watcher.options.ignored));\n    }\n  }\n\n  if (watcher.paths) {\n    if (Array.isArray(watcher.paths)) {\n      watcher.paths = watcher.paths.map(p => normalize(replacePathVars(context, p)));\n    } else {\n      watcher.paths = normalize(replacePathVars(context, watcher.paths));\n    }\n  }\n}\n\n\nlet queuedWatchEventsMap = new Map<string, ChangedFile>();\nlet queuedWatchEventsTimerId: any;\n\nexport function buildUpdate(event: string, filePath: string, context: BuildContext) {\n  return queueWatchUpdatesForBuild(event, filePath, context);\n}\n\nexport function queueWatchUpdatesForBuild(event: string, filePath: string, context: BuildContext) {\n  const changedFile: ChangedFile = {\n    event: event,\n    filePath: filePath,\n    ext: extname(filePath).toLowerCase()\n  };\n\n  queuedWatchEventsMap.set(filePath, changedFile);\n\n  // debounce our build update incase there are multiple files\n  clearTimeout(queuedWatchEventsTimerId);\n\n  // run this code in a few milliseconds if another hasn't come in behind it\n  queuedWatchEventsTimerId = setTimeout(() => {\n\n    // figure out what actually needs to be rebuilt\n    const queuedChangeFileList: ChangedFile[] = [];\n    queuedWatchEventsMap.forEach(changedFile => queuedChangeFileList.push(changedFile));\n\n    const changedFiles = runBuildUpdate(context, queuedChangeFileList);\n\n    // clear out all the files that are queued up for the build update\n    queuedWatchEventsMap.clear();\n\n    if (changedFiles && changedFiles.length) {\n      // cool, we've got some build updating to do ;)\n      queueOrRunBuildUpdate(changedFiles, context);\n    }\n  }, BUILD_UPDATE_DEBOUNCE_MS);\n\n  return Promise.resolve();\n}\n\n// exported just for use in unit testing\nexport let buildUpdatePromise: Promise<any> = null;\nexport let queuedChangedFileMap = new Map<string, ChangedFile>();\nexport function queueOrRunBuildUpdate(changedFiles: ChangedFile[], context: BuildContext) {\n  if (buildUpdatePromise) {\n    // there is an active build going on, so queue our changes and run\n    // another build when this one finishes\n\n    // in the event this is called multiple times while queued, we are following a \"last event wins\" pattern\n    // so if someone makes an edit, and then deletes a file, the last \"ChangedFile\" is the one we act upon\n    changedFiles.forEach(changedFile => {\n      queuedChangedFileMap.set(changedFile.filePath, changedFile);\n    });\n    return buildUpdatePromise;\n  } else {\n    // there is not an active build going going on\n    // clear out any queued file changes, and run the build\n    queuedChangedFileMap.clear();\n\n    const buildUpdateCompleteCallback: () => Promise<any> = () => {\n      // the update is complete, so check if there are pending updates that need to be run\n      buildUpdatePromise = null;\n      if (queuedChangedFileMap.size > 0) {\n        const queuedChangeFileList: ChangedFile[] = [];\n        queuedChangedFileMap.forEach(changedFile => {\n          queuedChangeFileList.push(changedFile);\n        });\n        return queueOrRunBuildUpdate(queuedChangeFileList, context);\n      }\n      return Promise.resolve();\n    };\n\n    buildUpdatePromise = buildTask.buildUpdate(changedFiles, context);\n    return buildUpdatePromise.then(buildUpdateCompleteCallback).catch((err: Error) => {\n      return buildUpdateCompleteCallback();\n    });\n  }\n}\n\n\n\n\n\n\nlet queuedCopyChanges: ChangedFile[] = [];\nlet queuedCopyTimerId: any;\n\nexport function copyUpdate(event: string, filePath: string, context: BuildContext) {\n  const changedFile: ChangedFile = {\n    event: event,\n    filePath: filePath,\n    ext: extname(filePath).toLowerCase()\n  };\n  // do not allow duplicates\n  if (!queuedCopyChanges.some(f => f.filePath === filePath)) {\n    queuedCopyChanges.push(changedFile);\n\n    // debounce our build update incase there are multiple files\n    clearTimeout(queuedCopyTimerId);\n\n    // run this code in a few milliseconds if another hasn't come in behind it\n    queuedCopyTimerId = setTimeout(() => {\n\n      const changedFiles = queuedCopyChanges.concat([]);\n      // clear out all the files that are queued up for the build update\n      queuedCopyChanges.length = 0;\n\n      if (changedFiles && changedFiles.length) {\n        // cool, we've got some build updating to do ;)\n        copyUpdateHandler(changedFiles, context);\n      }\n    }, BUILD_UPDATE_DEBOUNCE_MS);\n  }\n\n  return Promise.resolve();\n}\n\n\nexport function runBuildUpdate(context: BuildContext, changedFiles: ChangedFile[]) {\n  if (!changedFiles || !changedFiles.length) {\n    return null;\n  }\n\n  const jsFiles = changedFiles.filter(f => f.ext === '.js');\n  if (jsFiles.length) {\n    // this is mainly for linked modules\n    // if a linked library has changed (which would have a js extention)\n    // we should do a full transpile build because of this\n    context.bundleState = BuildState.RequiresUpdate;\n  }\n\n  const tsFiles = changedFiles.filter(f => f.ext === '.ts');\n  if (tsFiles.length) {\n    let requiresFullBuild = false;\n    for (const tsFile of tsFiles) {\n      if (!canRunTranspileUpdate(tsFile.event, tsFiles[0].filePath, context)) {\n        requiresFullBuild = true;\n        break;\n      }\n    }\n\n    if (requiresFullBuild) {\n      // .ts file was added or deleted, we need a full rebuild\n      context.transpileState = BuildState.RequiresBuild;\n      context.deepLinkState = BuildState.RequiresBuild;\n    } else {\n      // .ts files have changed, so we can get away with doing an update\n      context.transpileState = BuildState.RequiresUpdate;\n      context.deepLinkState = BuildState.RequiresUpdate;\n    }\n  }\n\n\n  const sassFiles = changedFiles.filter(f => /^\\.s(c|a)ss$/.test(f.ext));\n  if (sassFiles.length) {\n    // .scss or .sass file was changed/added/deleted, lets do a sass update\n    context.sassState = BuildState.RequiresUpdate;\n  }\n\n  const sassFilesNotChanges = changedFiles.filter(f => f.ext === '.ts' && f.event !== 'change');\n  if (sassFilesNotChanges.length) {\n    // .ts file was either added or deleted, so we'll have to\n    // run sass again to add/remove that .ts file's potential .scss file\n    context.sassState = BuildState.RequiresUpdate;\n  }\n\n  const htmlFiles = changedFiles.filter(f => f.ext === '.html');\n  if (htmlFiles.length) {\n    if (context.bundleState === BuildState.SuccessfulBuild && htmlFiles.every(f => f.event === 'change')) {\n      // .html file was changed\n      // just doing a template update is fine\n      context.templateState = BuildState.RequiresUpdate;\n\n    } else {\n      // .html file was added/deleted\n      // we should do a full transpile build because of this\n      context.transpileState = BuildState.RequiresBuild;\n      context.deepLinkState = BuildState.RequiresBuild;\n    }\n  }\n\n  if (context.transpileState === BuildState.RequiresUpdate || context.transpileState === BuildState.RequiresBuild) {\n    if (context.bundleState === BuildState.SuccessfulBuild || context.bundleState === BuildState.RequiresUpdate) {\n      // transpiling needs to happen\n      // and there has already been a successful bundle before\n      // so let's just do a bundle update\n      context.bundleState = BuildState.RequiresUpdate;\n    } else {\n      // transpiling needs to happen\n      // but we've never successfully bundled before\n      // so let's do a full bundle build\n      context.bundleState = BuildState.RequiresBuild;\n    }\n  }\n  return changedFiles.concat();\n}\n\n\nconst taskInfo: TaskInfo = {\n  fullArg: '--watch',\n  shortArg: null,\n  envVar: 'IONIC_WATCH',\n  packageConfig: 'ionic_watch',\n  defaultConfigFile: 'watch.config'\n};\n\n\nexport interface WatchConfig {\n  [index: string]: Watcher;\n}\n\nexport interface Watcher {\n  // https://www.npmjs.com/package/chokidar\n  paths?: string[]|string;\n  options?: {\n    ignored?: string|string[]|Function;\n    ignoreInitial?: boolean;\n    followSymlinks?: boolean;\n    cwd?: string;\n  };\n  eventName?: string;\n  callback?: {\n    (event: string, filePath: string, context: BuildContext): Promise<any>;\n  };\n}\n\nlet watchCount = 0;\n\nconst BUILD_UPDATE_DEBOUNCE_MS = 20;\n"
  },
  {
    "path": "src/webpack/cache-loader-impl.ts",
    "content": "import { normalize, resolve } from 'path';\nimport * as Constants from '../util/constants';\nimport { changeExtension, getBooleanPropertyValue, getContext, readAndCacheFile} from '../util/helpers';\nimport { Logger } from '../logger/logger';\nimport { FileCache } from '../util/file-cache';\n\nexport function cacheLoader(source: string, map: any, webpackContex: any) {\n  webpackContex.cacheable();\n  const callback = webpackContex.async();\n  try {\n    const context = getContext();\n\n    if (getBooleanPropertyValue(Constants.ENV_AOT_WRITE_TO_DISK)) {\n      const jsPath = changeExtension(resolve(normalize(webpackContex.resourcePath)), '.js');\n      const newSourceFile = { path: jsPath, content: source};\n      context.fileCache.set(jsPath, newSourceFile);\n\n      const mapPath = changeExtension(jsPath, '.js.map');\n      const newMapFile = { path: mapPath, content: JSON.stringify(map)};\n      context.fileCache.set(mapPath, newMapFile);\n    }\n    callback(null, source, map);\n  } catch (ex) {\n    callback(ex);\n  }\n}\n"
  },
  {
    "path": "src/webpack/cache-loader.ts",
    "content": "import { cacheLoader } from './cache-loader-impl';\n\nmodule.exports = function loader(source: string, map: any) {\n  cacheLoader(source, map, this);\n};\n"
  },
  {
    "path": "src/webpack/common-chunks-plugins.ts",
    "content": " const CommonChunksPlugin = require('webpack/lib/optimize/CommonsChunkPlugin');\n\nimport * as Constants from '../util/constants';\nimport { getStringPropertyValue } from '../util/helpers';\n\nexport function getCommonChunksPlugin() {\n  return new CommonChunksPlugin({\n    name: 'vendor',\n    minChunks: checkIfInNodeModules\n  });\n}\n\nfunction checkIfInNodeModules(webpackModule: any) {\n  return webpackModule && webpackModule.userRequest && webpackModule.userRequest.startsWith(getStringPropertyValue(Constants.ENV_VAR_NODE_MODULES_DIR));\n}\n"
  },
  {
    "path": "src/webpack/ionic-environment-plugin.ts",
    "content": "import { join } from 'path';\nimport * as Constants from '../util/constants';\nimport { changeExtension, getParsedDeepLinkConfig, getStringPropertyValue } from '../util/helpers';\nimport { BuildContext , DeepLinkConfigEntry} from '../util/interfaces';\nimport { Logger } from '../logger/logger';\nimport { getInstance } from '../util/hybrid-file-system-factory';\nimport { WatchMemorySystem } from './watch-memory-system';\n\nconst ContextElementDependency = require('webpack/lib/dependencies/ContextElementDependency');\n\nexport class IonicEnvironmentPlugin {\n  constructor(private context: BuildContext, private writeToDisk: boolean) {\n  }\n\n  apply(compiler: any) {\n\n    compiler.plugin('context-module-factory', (contextModuleFactory: any) => {\n      contextModuleFactory.plugin('after-resolve', (result: any, callback: Function) => {\n        if (!result) {\n          return callback();\n        }\n\n        const deepLinkConfig = getParsedDeepLinkConfig();\n        const webpackDeepLinkModuleDictionary = convertDeepLinkConfigToWebpackFormat(deepLinkConfig);\n        const ionicAngularDir = getStringPropertyValue(Constants.ENV_VAR_IONIC_ANGULAR_DIR);\n        const ngModuleLoaderDirectory = join(ionicAngularDir, 'util');\n        if (!result.resource.endsWith(ngModuleLoaderDirectory)) {\n          return callback(null, result);\n        }\n\n        result.resource = this.context.srcDir;\n        result.recursive = true;\n        result.dependencies.forEach((dependency: any) => dependency.critical = false);\n        result.resolveDependencies = (p1: any, p2: any, p3: any, p4: RegExp, cb: any ) => {\n          const dependencies = Object.keys(webpackDeepLinkModuleDictionary)\n                                  .map((key) => {\n                                    const value = webpackDeepLinkModuleDictionary[key];\n                                    if (value) {\n                                      return new ContextElementDependency(value, key);\n                                    }\n                                    return null;\n                                  }).filter(dependency => !!dependency);\n          cb(null, dependencies);\n        };\n        return callback(null, result);\n      });\n    });\n\n    compiler.plugin('environment', (otherCompiler: any, callback: Function) => {\n      Logger.debug('[IonicEnvironmentPlugin] apply: creating environment plugin');\n      const hybridFileSystem = getInstance(this.writeToDisk);\n      hybridFileSystem.setInputFileSystem(compiler.inputFileSystem);\n      hybridFileSystem.setOutputFileSystem(compiler.outputFileSystem);\n      compiler.inputFileSystem = hybridFileSystem;\n      compiler.outputFileSystem = hybridFileSystem;\n      compiler.watchFileSystem = new WatchMemorySystem(this.context.fileCache, this.context.srcDir);\n\n      // do a bunch of webpack specific stuff here, so cast to an any\n      // populate the content of the file system with any virtual files\n      // inspired by populateWebpackResolver method in Angular's webpack plugin\n      const webpackFileSystem: any = hybridFileSystem;\n      const fileStatsDictionary = hybridFileSystem.getAllFileStats();\n      const dirStatsDictionary = hybridFileSystem.getAllDirStats();\n\n      this.initializeWebpackFileSystemCaches(webpackFileSystem);\n\n      for (const filePath of Object.keys(fileStatsDictionary)) {\n        const stats =  fileStatsDictionary[filePath];\n        webpackFileSystem._statStorage.data[filePath] = [null, stats];\n        webpackFileSystem._readFileStorage.data[filePath] = [null, stats.content];\n      }\n\n      for (const dirPath of Object.keys(dirStatsDictionary)) {\n        const stats = dirStatsDictionary[dirPath];\n        const fileNames = hybridFileSystem.getFileNamesInDirectory(dirPath);\n        const dirNames = hybridFileSystem.getSubDirs(dirPath);\n        webpackFileSystem._statStorage.data[dirPath] = [null, stats];\n        webpackFileSystem._readdirStorage.data[dirPath] = [null, fileNames.concat(dirNames)];\n      }\n    });\n  }\n\n  private initializeWebpackFileSystemCaches(webpackFileSystem: any) {\n    if (!webpackFileSystem._statStorage) {\n      webpackFileSystem._statStorage = { };\n    }\n    if (!webpackFileSystem._statStorage.data) {\n      webpackFileSystem._statStorage.data = [];\n    }\n\n    if (!webpackFileSystem._readFileStorage) {\n      webpackFileSystem._readFileStorage = { };\n    }\n    if (!webpackFileSystem._readFileStorage.data) {\n      webpackFileSystem._readFileStorage.data = [];\n    }\n\n    if (!webpackFileSystem._readdirStorage) {\n      webpackFileSystem._readdirStorage = { };\n    }\n    if (!webpackFileSystem._readdirStorage.data) {\n      webpackFileSystem._readdirStorage.data = [];\n    }\n  }\n}\n\n\nexport function convertDeepLinkConfigToWebpackFormat(parsedDeepLinkConfigs: Map<string, DeepLinkConfigEntry>) {\n  const dictionary: { [index: string]: string} = { };\n  if (!parsedDeepLinkConfigs) {\n    parsedDeepLinkConfigs = new Map<string, DeepLinkConfigEntry>();\n  }\n  parsedDeepLinkConfigs.forEach(parsedDeepLinkConfig => {\n    if (parsedDeepLinkConfig.userlandModulePath && parsedDeepLinkConfig.absolutePath) {\n      dictionary[parsedDeepLinkConfig.userlandModulePath] = parsedDeepLinkConfig.absolutePath;\n    }\n  });\n  return dictionary;\n}\n"
  },
  {
    "path": "src/webpack/ionic-webpack-factory.ts",
    "content": "import { getCommonChunksPlugin } from './common-chunks-plugins';\nimport { IonicEnvironmentPlugin } from './ionic-environment-plugin';\nimport { provideCorrectSourcePath } from './source-mapper';\nimport { getContext } from '../util/helpers';\n\nexport function getIonicEnvironmentPlugin() {\n  const context = getContext();\n  return new IonicEnvironmentPlugin(context, true);\n}\n\nexport function getSourceMapperFunction(): Function {\n  return provideCorrectSourcePath;\n}\n\nexport { getCommonChunksPlugin };\n"
  },
  {
    "path": "src/webpack/loader-impl.spec.ts",
    "content": "import { join } from 'path';\nimport * as loader from './loader-impl';\nimport *  as helpers from '../util/helpers';\n\nfunction getMockContext() {\n  return {\n    fileCache: getMockFileCache()\n  };\n}\n\nfunction getMockFileCache() {\n  return {\n    get: () => { },\n    set: () => { }\n  };\n}\n\nfunction getMockWebpackObject(resourcePath: string) {\n  return {\n    cacheable: () => { },\n    async: () => {},\n    resourcePath: resourcePath\n  };\n}\n\ndescribe('webpack loader', () => {\n  it('should callback with file and original source map provided', (done: Function) => {\n    // arrange\n    const mockContext = getMockContext();\n    const mockSourceMap = { };\n    const sourceString = 'sourceString';\n    const fakePath = join(process.cwd(), 'some', 'path', 'content.js');\n    const fakeContent = 'SomeFileContent';\n    const mockWebpackObject = getMockWebpackObject(fakePath);\n    const spy = jasmine.createSpy('mock webpack callback');\n    spy.and.callFake(() => {\n      assertFunction();\n    });\n    spyOn(mockWebpackObject, mockWebpackObject.cacheable.name);\n    spyOn(mockWebpackObject, mockWebpackObject.async.name).and.returnValue(spy);\n    spyOn(helpers, helpers.getContext.name).and.returnValue(mockContext);\n    spyOn(helpers, helpers.readAndCacheFile.name).and.returnValue(Promise.resolve(fakeContent));\n    spyOn(mockContext.fileCache, mockContext.fileCache.get.name).and.returnValue({\n      path: fakePath,\n      content: fakeContent\n    });\n\n    // act\n    loader.webpackLoader(sourceString, mockSourceMap, mockWebpackObject);\n\n    // assert\n    const assertFunction = () => {\n      expect(helpers.readAndCacheFile).toHaveBeenCalledTimes(2);\n      expect(spy).toHaveBeenCalledWith(null, fakeContent, mockSourceMap);\n      done();\n    };\n  });\n\n  it('should callback with file and map loaded from file cache', (done: Function) => {\n    // arrange\n    const mockContext = getMockContext();\n\n    const mockSourceMap = { };\n    const sourceString = 'sourceString';\n    const fakePath = join(process.cwd(), 'some', 'path', 'content.js');\n    const fakeContent = `{\"test\": \"test\"}`;\n    const mockWebpackObject = getMockWebpackObject(fakePath);\n    const spy = jasmine.createSpy('mock webpack callback');\n    spy.and.callFake(() => {\n      assertFunction();\n    });\n    spyOn(mockWebpackObject, mockWebpackObject.cacheable.name);\n    spyOn(mockWebpackObject, mockWebpackObject.async.name).and.returnValue(spy);\n    spyOn(helpers, helpers.getContext.name).and.returnValue(mockContext);\n    spyOn(helpers, helpers.readAndCacheFile.name).and.returnValue(Promise.resolve(fakeContent));\n    const fileCacheSpy = spyOn(mockContext.fileCache, mockContext.fileCache.get.name).and.returnValue({\n      path: fakePath,\n      content: fakeContent\n    });\n\n    // act\n    loader.webpackLoader(sourceString, mockSourceMap, mockWebpackObject);\n\n    // assert\n    const assertFunction = () => {\n      expect(fileCacheSpy).toHaveBeenCalledTimes(2);\n      expect(fileCacheSpy.calls.first().args[0]).toEqual(fakePath);\n      expect(fileCacheSpy.calls.mostRecent().args[0]).toEqual(fakePath + '.map');\n      expect(spy.calls.mostRecent().args[0]).toEqual(null);\n      expect(spy.calls.mostRecent().args[1]).toEqual(fakeContent);\n      expect(spy.calls.mostRecent().args[2]).not.toEqual(mockSourceMap);\n      done();\n    };\n  });\n\n\n  it('should callback with error when can\\'t load file from disk', (done: Function) => {\n    // arrange\n    const cantReadFileError = 'Failed to read file from disk';\n    const mockContext = getMockContext();\n    const mockSourceMap = { };\n    const sourceString = 'sourceString';\n    const fakePath = join(process.cwd(), 'some', 'path', 'content.js');\n    const mockWebpackObject = getMockWebpackObject(fakePath);\n    const spy = jasmine.createSpy('mock webpack callback');\n    spy.and.callFake(() => {\n      assertFunction();\n    });\n    spyOn(mockWebpackObject, mockWebpackObject.cacheable.name);\n    spyOn(mockWebpackObject, mockWebpackObject.async.name).and.returnValue(spy);\n    spyOn(helpers, helpers.getContext.name).and.returnValue(mockContext);\n    spyOn(mockContext.fileCache, mockContext.fileCache.get.name).and.returnValue(null);\n    spyOn(mockContext.fileCache, mockContext.fileCache.set.name);\n    spyOn(helpers, helpers.readAndCacheFile.name).and.returnValue(Promise.reject(new Error(cantReadFileError)));\n\n    // assert\n    const assertFunction = () => {\n      expect(spy.calls.mostRecent().args[0]).toBeTruthy();\n      expect(spy.calls.mostRecent().args[0].message).toEqual(cantReadFileError);\n      done();\n    };\n\n    // act\n    return loader.webpackLoader(sourceString, mockSourceMap, mockWebpackObject);\n  });\n\n  it('should callback with content from disk', (done: Function) => {\n    // arrange\n    const mockContext = getMockContext();\n    const mockSourceMap = { };\n    const sourceString = 'sourceString';\n    const fakePath = join(process.cwd(), 'some', 'path', 'content.js');\n    const fakeContent = `{\"test\": \"test\"}`;\n    const mockWebpackObject = getMockWebpackObject(fakePath);\n    const callbackSpy = jasmine.createSpy('mock webpack callback');\n    callbackSpy.and.callFake(() => {\n      assertFunction();\n    });\n    spyOn(mockWebpackObject, mockWebpackObject.cacheable.name);\n    spyOn(mockWebpackObject, mockWebpackObject.async.name).and.returnValue(callbackSpy);\n    spyOn(helpers, helpers.getContext.name).and.returnValue(mockContext);\n    spyOn(mockContext.fileCache, mockContext.fileCache.set.name);\n    const readFileSpy = spyOn(helpers, helpers.readAndCacheFile.name).and.returnValue(Promise.resolve(fakeContent));\n    spyOn(mockContext.fileCache, mockContext.fileCache.get.name).and.returnValue({\n      path: fakePath,\n      content: fakeContent\n    });\n\n    // act\n    loader.webpackLoader(sourceString, mockSourceMap, mockWebpackObject);\n\n    // assert\n    const assertFunction = () => {\n      expect(readFileSpy).toHaveBeenCalledTimes(2);\n      expect(readFileSpy.calls.first().args[0]).toEqual(fakePath);\n      expect(readFileSpy.calls.mostRecent().args[0]).toEqual(fakePath + '.map');\n      expect(callbackSpy.calls.mostRecent().args[0]).toEqual(null);\n      expect(callbackSpy.calls.mostRecent().args[1]).toEqual(fakeContent);\n      expect(callbackSpy.calls.mostRecent().args[2]).toBeTruthy();\n      done();\n    };\n  });\n});\n"
  },
  {
    "path": "src/webpack/loader-impl.ts",
    "content": "import { normalize, resolve } from 'path';\nimport { changeExtension, getContext, readAndCacheFile} from '../util/helpers';\nimport { Logger } from '../logger/logger';\nimport { FileCache } from '../util/file-cache';\n\nexport function webpackLoader(source: string, map: any, webpackContex: any) {\n  webpackContex.cacheable();\n  var callback = webpackContex.async();\n  const context = getContext();\n\n  const absolutePath = resolve(normalize(webpackContex.resourcePath));\n  Logger.debug(`[Webpack] webpackLoader: processing the following file: ${absolutePath}`);\n  const javascriptPath = changeExtension(absolutePath, '.js');\n  const sourceMapPath = javascriptPath + '.map';\n\n  Promise.all([\n   readFile(context.fileCache, javascriptPath),\n   readFile(context.fileCache, sourceMapPath)\n  ]).then(([javascriptFile, mapFile]) => {\n    let sourceMapObject = map;\n    if (mapFile) {\n      try {\n        sourceMapObject = JSON.parse(mapFile.content);\n\n      } catch (ex) {\n        Logger.debug(`[Webpack] loader: Attempted to parse the JSON sourcemap for ${mapFile.path} and failed -\n          using the original, webpack provided source map`);\n      }\n      if (sourceMapObject) {\n        sourceMapObject.sources = [absolutePath];\n        if (!sourceMapObject.sourcesContent || sourceMapObject.sourcesContent.length === 0) {\n          sourceMapObject.sourcesContent = [source];\n        }\n      }\n    }\n    callback(null, javascriptFile.content, sourceMapObject);\n  }).catch(err => {\n    Logger.debug(`[Webpack] loader: Encountered an unexpected error: ${err.message}`);\n    callback(err);\n  });\n}\n\nfunction readFile(fileCache: FileCache, filePath: string) {\n  return readAndCacheFile(filePath).then((fileContent: string) => {\n    Logger.debug(`[Webpack] loader: Loaded ${filePath} successfully from disk`);\n    return fileCache.get(filePath);\n  }).catch(err => {\n    Logger.debug(`[Webpack] loader: Failed to load ${filePath} from disk`);\n    throw err;\n  });\n}\n"
  },
  {
    "path": "src/webpack/loader.ts",
    "content": "import { webpackLoader } from './loader-impl';\n\nmodule.exports = function loader(source: string, map: any) {\n  webpackLoader(source, map, this);\n};\n"
  },
  {
    "path": "src/webpack/source-mapper.ts",
    "content": "import { BuildContext } from '../util/interfaces';\nimport { getContext, toUnixPath} from '../util/helpers';\nimport { join, normalize, relative, resolve, sep } from 'path';\nimport { SOURCE_MAP_TYPE_CHEAP, ENV_VAR_SOURCE_MAP_TYPE } from '../util/constants';\n\nexport function provideCorrectSourcePath(webpackObj: any) {\n  const context = getContext();\n  return provideCorrectSourcePathInternal(webpackObj, context);\n}\n\nfunction provideCorrectSourcePathInternal(webpackObj: any, context: BuildContext) {\n  const webpackResourcePath = webpackObj.resourcePath;\n  const noTilde = webpackResourcePath.replace(/~/g, 'node_modules');\n  const absolutePath = resolve(normalize(noTilde));\n  if (process.env[ENV_VAR_SOURCE_MAP_TYPE] === SOURCE_MAP_TYPE_CHEAP) {\n    const mapPath = sep + absolutePath;\n    return toUnixPath(mapPath);\n  }\n  // does the full map\n  const backPath = relative(context.buildDir, context.rootDir);\n  const relativePath = relative(context.rootDir, absolutePath);\n  const relativeToBuildDir = join(backPath, relativePath);\n  return toUnixPath(relativeToBuildDir);\n}\n"
  },
  {
    "path": "src/webpack/watch-memory-system.ts",
    "content": "import { extname } from 'path';\nimport { FileCache } from '../util/file-cache';\nimport { on, EventType } from '../util/events';\nimport { Logger } from '../logger/logger';\n\nexport class WatchMemorySystem {\n\n  private changes: Set<string>;\n  private isAggregating: boolean;\n  private isListening: boolean;\n  private lastWatchEventTimestamp: number = Date.now();\n\n  private filePathsBeingWatched: string[];\n  private dirPaths: string[];\n  private missing: string[];\n  private startTime: number;\n  private options: any;\n  private immediateCallback: (filePath: string, timestamp: number) => void;\n  private aggregatedCallback: (err: Error, changesFilePaths: string[], dirPaths: string[], missingPaths: string[], timesOne: any, timesTwo: any) => void;\n\n  constructor(private fileCache: FileCache, private srcDir: string) {\n  }\n\n  close() {\n    this.isListening = false;\n  }\n\n  pause() {\n    this.isListening = false;\n  }\n\n  watch(filePathsBeingWatched: string[], dirPaths: string[], missing: string[], startTime: number, options: any, aggregatedCallback: (err: Error, changesFilePaths: string[]) => void, immediateCallback: (filePath: string, timestamp: number) => void) {\n    this.filePathsBeingWatched = filePathsBeingWatched;\n    this.dirPaths = dirPaths;\n    this.missing = missing;\n    this.startTime = startTime;\n    this.options = options;\n\n    this.immediateCallback = immediateCallback;\n    this.aggregatedCallback = aggregatedCallback;\n\n    if (!this.isListening) {\n      this.startListening();\n    }\n\n    return {\n      pause: this.pause,\n      close: this.close\n    };\n  }\n\n  startListening() {\n    this.isListening = true;\n    on(EventType.WebpackFilesChanged, () => {\n      this.changes = new Set<string>();\n      const filePaths = this.fileCache.getAll().filter(file => file.timestamp >= this.lastWatchEventTimestamp && file.path.startsWith(this.srcDir) && extname(file.path) === '.ts').map(file => file.path);\n      Logger.debug('filePaths: ', filePaths);\n      this.lastWatchEventTimestamp = Date.now();\n      this.processChanges(filePaths);\n    });\n  }\n\n  processChanges(filePaths: string[]) {\n    this.immediateCallback(filePaths[0], Date.now());\n    for ( const path of filePaths) {\n      this.changes.add(path);\n    }\n    // don't bother waiting around, just call doneAggregating right away.\n    // keep it as a function in case we need to wait via setTimeout a bit in the future\n    this.doneAggregating(this.changes);\n  }\n\n  doneAggregating(changes: Set<string>) {\n    this.isAggregating = false;\n    // process the changes\n    const filePaths = Array.from(changes);\n    const files = filePaths.filter(filePath => this.filePathsBeingWatched.indexOf(filePath) >= 0).sort();\n    const dirs = filePaths.filter(filePath => this.dirPaths.indexOf(filePath) >= 0).sort();\n    const missing = filePaths.filter(filePath => this.missing.indexOf(filePath) >= 0).sort();\n    const times = this.getTimes(this.filePathsBeingWatched, this.startTime, this.fileCache);\n    this.aggregatedCallback(null, files, dirs, missing, times, times);\n  }\n\n  getTimes(allFiles: string[], startTime: number, fileCache: FileCache) {\n    let times: any = { };\n    for (const filePath of allFiles) {\n      const file = fileCache.get(filePath);\n      if (file) {\n        times[filePath] = file.timestamp;\n      } else {\n        times[filePath] = startTime;\n      }\n    }\n    return times;\n  }\n}\n"
  },
  {
    "path": "src/webpack.ts",
    "content": "import { EventEmitter } from 'events';\nimport { dirname, join } from 'path';\n\nimport * as webpackApi from 'webpack';\n\nimport { Logger } from './logger/logger';\nimport { fillConfigDefaults, getUserConfigFile, replacePathVars } from './util/config';\nimport * as Constants from './util/constants';\nimport { BuildError, IgnorableError } from './util/errors';\nimport { emit, EventType } from './util/events';\nimport { getBooleanPropertyValue, printDependencyMap, webpackStatsToDependencyMap, writeFileAsync } from './util/helpers';\nimport { BuildContext, BuildState, ChangedFile, TaskInfo } from './util/interfaces';\n\n\nconst eventEmitter = new EventEmitter();\nconst INCREMENTAL_BUILD_FAILED = 'incremental_build_failed';\nconst INCREMENTAL_BUILD_SUCCESS = 'incremental_build_success';\n\n/*\n * Due to how webpack watch works, sometimes we start an update event\n * but it doesn't affect the bundle at all, for example adding a new typescript file\n * not imported anywhere or adding an html file not used anywhere.\n * In this case, we'll be left hanging and have screwed up logging when the bundle is modified\n * because multiple promises will resolve at the same time (we queue up promises waiting for an event to occur)\n * To mitigate this, store pending \"webpack watch\"/bundle update promises in this array and only resolve the\n * the most recent one. reject all others at that time with an IgnorableError.\n */\nlet pendingPromises: Promise<any>[] = [];\n\nexport function webpack(context: BuildContext, configFile: string) {\n  configFile = getUserConfigFile(context, taskInfo, configFile);\n\n  const logger = new Logger('webpack');\n\n  return webpackWorker(context, configFile)\n    .then(() => {\n      context.bundleState = BuildState.SuccessfulBuild;\n      logger.finish();\n    })\n    .catch(err => {\n      context.bundleState = BuildState.RequiresBuild;\n      throw logger.fail(err);\n    });\n}\n\n\nexport function webpackUpdate(changedFiles: ChangedFile[], context: BuildContext, configFile?: string) {\n  const logger = new Logger('webpack update');\n  const webpackConfig = getWebpackConfig(context, configFile);\n  Logger.debug('webpackUpdate: Starting Incremental Build');\n  const promisetoReturn = runWebpackIncrementalBuild(false, context, webpackConfig);\n  emit(EventType.WebpackFilesChanged, null);\n  return promisetoReturn.then((stats: any) => {\n      // the webpack incremental build finished, so reset the list of pending promises\n      pendingPromises = [];\n      Logger.debug('webpackUpdate: Incremental Build Done, processing Data');\n      return webpackBuildComplete(stats, context, webpackConfig);\n    }).then(() => {\n      context.bundleState = BuildState.SuccessfulBuild;\n      return logger.finish();\n    }).catch(err => {\n      context.bundleState = BuildState.RequiresBuild;\n      if (err instanceof IgnorableError) {\n        throw err;\n      }\n\n      throw logger.fail(err);\n    });\n}\n\n\nexport function webpackWorker(context: BuildContext, configFile: string): Promise<any> {\n  const webpackConfig = getWebpackConfig(context, configFile);\n\n  let promise: Promise<any> = null;\n  if (context.isWatch) {\n    promise = runWebpackIncrementalBuild(!context.webpackWatch, context, webpackConfig);\n  } else {\n    promise = runWebpackFullBuild(webpackConfig);\n  }\n\n  return promise\n    .then((stats: any) => {\n      return webpackBuildComplete(stats, context, webpackConfig);\n    });\n}\n\nfunction webpackBuildComplete(stats: any, context: BuildContext, webpackConfig: WebpackConfig) {\n  if (getBooleanPropertyValue(Constants.ENV_PRINT_WEBPACK_DEPENDENCY_TREE)) {\n    Logger.debug('Webpack Dependency Map Start');\n    const dependencyMap = webpackStatsToDependencyMap(context, stats);\n    printDependencyMap(dependencyMap);\n    Logger.debug('Webpack Dependency Map End');\n  }\n\n  // set the module files used in this bundle\n  // this reference can be used elsewhere in the build (sass)\n  const files: string[] = [];\n  stats.compilation.modules.forEach((webpackModule: any) => {\n    if (webpackModule.resource) {\n      files.push(webpackModule.resource);\n    } else if (webpackModule.context) {\n      files.push(webpackModule.context);\n    } else if (webpackModule.fileDependencies) {\n      webpackModule.fileDependencies.forEach((filePath: string) => {\n        files.push(filePath);\n      });\n    }\n  });\n\n  const trimmedFiles = files.filter(file => file && file.length > 0);\n\n  context.moduleFiles = trimmedFiles;\n\n  return setBundledFiles(context);\n}\n\nexport function setBundledFiles(context: BuildContext) {\n  const bundledFilesToWrite = context.fileCache.getAll().filter(file => {\n    return dirname(file.path).indexOf(context.buildDir) >= 0 && (file.path.endsWith('.js') || file.path.endsWith('.js.map'));\n  });\n  context.bundledFilePaths = bundledFilesToWrite.map(bundledFile => bundledFile.path);\n}\n\nexport function runWebpackFullBuild(config: WebpackConfig) {\n  return new Promise((resolve, reject) => {\n    const callback = (err: Error, stats: any) => {\n      if (err) {\n        reject(new BuildError(err));\n      } else {\n        const info = stats.toJson();\n\n        if (stats.hasErrors()) {\n          reject(new BuildError(info.errors));\n        } else if (stats.hasWarnings()) {\n          Logger.debug(info.warnings);\n          resolve(stats);\n        } else {\n          resolve(stats);\n        }\n      }\n    };\n    const compiler = webpackApi(config as any);\n    compiler.run(callback);\n  });\n}\n\nfunction runWebpackIncrementalBuild(initializeWatch: boolean, context: BuildContext, config: WebpackConfig) {\n  const promise = new Promise((resolve, reject) => {\n    // start listening for events, remove listeners once an event is received\n    eventEmitter.on(INCREMENTAL_BUILD_FAILED, (err: Error) => {\n      Logger.debug('Webpack Bundle Update Failed');\n      eventEmitter.removeAllListeners();\n      handleWebpackBuildFailure(resolve, reject, err, promise, pendingPromises);\n    });\n\n    eventEmitter.on(INCREMENTAL_BUILD_SUCCESS, (stats: any) => {\n      Logger.debug('Webpack Bundle Updated');\n      eventEmitter.removeAllListeners();\n      handleWebpackBuildSuccess(resolve, reject, stats, promise, pendingPromises);\n    });\n\n    if (initializeWatch) {\n      startWebpackWatch(context, config);\n    }\n  });\n\n  pendingPromises.push(promise);\n\n  return promise;\n}\n\nfunction handleWebpackBuildFailure(resolve: Function, reject: Function, error: Error, promise: Promise<any>, pendingPromises: Promise<void>[]) {\n  // check if the promise if the last promise in the list of pending promises\n  if (pendingPromises.length > 0 && pendingPromises[pendingPromises.length - 1] === promise) {\n    // reject this one with a build error\n    reject(new BuildError(error));\n    return;\n  }\n  // for all others, reject with an ignorable error\n  reject(new IgnorableError());\n}\n\nfunction handleWebpackBuildSuccess(resolve: Function, reject: Function, stats: any, promise: Promise<any>, pendingPromises: Promise<void>[]) {\n  // check if the promise if the last promise in the list of pending promises\n  if (pendingPromises.length > 0 && pendingPromises[pendingPromises.length - 1] === promise) {\n    Logger.debug('handleWebpackBuildSuccess: Resolving with Webpack data');\n    resolve(stats);\n    return;\n  }\n  // for all others, reject with an ignorable error\n  Logger.debug('handleWebpackBuildSuccess: Rejecting with ignorable error');\n  reject(new IgnorableError());\n}\n\nfunction startWebpackWatch(context: BuildContext, config: WebpackConfig) {\n  Logger.debug('Starting Webpack watch');\n  const compiler = webpackApi(config as any);\n  context.webpackWatch = compiler.watch({}, (err: Error, stats: any) => {\n    if (err) {\n      eventEmitter.emit(INCREMENTAL_BUILD_FAILED, err);\n    } else {\n      eventEmitter.emit(INCREMENTAL_BUILD_SUCCESS, stats);\n    }\n  });\n}\n\nexport function getWebpackConfig(context: BuildContext, configFile: string): WebpackConfig {\n  configFile = getUserConfigFile(context, taskInfo, configFile);\n  const webpackConfigDictionary = fillConfigDefaults(configFile, taskInfo.defaultConfigFile);\n  const webpackConfig: WebpackConfig = getWebpackConfigFromDictionary(context, webpackConfigDictionary);\n  webpackConfig.entry = replacePathVars(context, webpackConfig.entry);\n  webpackConfig.output.path = replacePathVars(context, webpackConfig.output.path);\n\n  return webpackConfig;\n}\n\nexport function getWebpackConfigFromDictionary(context: BuildContext, webpackConfigDictionary: any): WebpackConfig {\n  // todo, support more ENV here\n  if (context.runAot) {\n    return webpackConfigDictionary['prod'];\n  }\n  return webpackConfigDictionary['dev'];\n}\n\n\nexport function getOutputDest(context: BuildContext) {\n  const webpackConfig = getWebpackConfig(context, null);\n  return join(webpackConfig.output.path, webpackConfig.output.filename);\n}\n\nconst taskInfo: TaskInfo = {\n  fullArg: '--webpack',\n  shortArg: '-w',\n  envVar: 'IONIC_WEBPACK',\n  packageConfig: 'ionic_webpack',\n  defaultConfigFile: 'webpack.config'\n};\n\n\nexport interface WebpackConfig {\n  // https://www.npmjs.com/package/webpack\n  devtool: string;\n  entry: string | { [key: string]: any };\n  output: WebpackOutputObject;\n  resolve: WebpackResolveObject;\n}\n\nexport interface WebpackOutputObject {\n  path: string;\n  filename: string;\n}\n\nexport interface WebpackResolveObject {\n  extensions: string[];\n  modules: string[];\n}\n"
  },
  {
    "path": "src/worker-client.ts",
    "content": "import { BuildContext, WorkerProcess, WorkerMessage } from './util/interfaces';\nimport { BuildError } from './util/errors';\nimport { jsonToBuildError } from './util/helpers';\nimport { Logger } from './logger/logger';\nimport { fork, ChildProcess } from 'child_process';\nimport { join } from 'path';\n\n\nexport function runWorker(taskModule: string, taskWorker: string, context: BuildContext, workerConfig: any) {\n  return new Promise((resolve, reject) => {\n    const worker = <ChildProcess>createWorker(taskModule);\n    const msg: WorkerMessage = {\n      taskModule,\n      taskWorker,\n      context: {\n        // only copy over what's important\n        // don't copy over the large data properties\n        rootDir: context.rootDir,\n        tmpDir: context.tmpDir,\n        srcDir: context.srcDir,\n        wwwDir: context.wwwDir,\n        wwwIndex: context.wwwIndex,\n        buildDir: context.buildDir,\n        bundledFilePaths: context.bundledFilePaths,\n        isProd: context.isProd,\n        isWatch: context.isWatch,\n        runAot: context.runAot,\n        runMinifyJs: context.runMinifyJs,\n        runMinifyCss: context.runMinifyCss,\n        optimizeJs: context.optimizeJs,\n        bundler: context.bundler,\n        inlineTemplates: context.inlineTemplates,\n      },\n      workerConfig\n    };\n\n    worker.on('message', (msg: WorkerMessage) => {\n      if (msg.error) {\n        reject(new BuildError(msg.error));\n\n      } else if (msg.reject) {\n        const buildError = jsonToBuildError(msg.reject);\n        reject(buildError);\n\n      } else {\n        resolve(msg.resolve);\n      }\n\n      killWorker(msg.pid);\n    });\n\n    worker.on('error', (err: any) => {\n      Logger.error(`worker error, taskModule: ${taskModule}, pid: ${worker.pid}, error: ${err}`);\n    });\n\n    worker.on('exit', (code: number) => {\n      Logger.debug(`worker exited, taskModule: ${taskModule}, pid: ${worker.pid}`);\n    });\n\n    worker.send(msg);\n  });\n}\n\n\nfunction killWorker(pid: number) {\n  for (var i = workers.length - 1; i >= 0; i--) {\n    if (workers[i].worker.pid === pid) {\n      try {\n        workers[i].worker.kill('SIGKILL');\n      } catch (e) {\n        Logger.error(`killWorker, ${pid}: ${e}`);\n      } finally {\n        delete workers[i].worker;\n        workers.splice(i, 1);\n      }\n    }\n  }\n}\n\n\nexport function createWorker(taskModule: string): any {\n  for (var i = workers.length - 1; i >= 0; i--) {\n    if (workers[i].task === taskModule) {\n      try {\n        workers[i].worker.kill('SIGKILL');\n      } catch (e) {\n        Logger.debug(`createWorker, ${taskModule} kill('SIGKILL'): ${e}`);\n      } finally {\n        delete workers[i].worker;\n        workers.splice(i, 1);\n      }\n    }\n  }\n\n  try {\n    const workerModule = join(__dirname, 'worker-process.js');\n    const worker = fork(workerModule, process.argv, {\n      env: {\n        FORCE_COLOR: true,\n        npm_config_argv: process.env.npm_config_argv\n      }\n    });\n\n    Logger.debug(`worker created, taskModule: ${taskModule}, pid: ${worker.pid}`);\n\n    workers.push({\n      task: taskModule,\n      worker: worker\n    });\n\n    return worker;\n\n  } catch (e) {\n    throw new BuildError(`unable to create worker-process, task: ${taskModule}: ${e}`);\n  }\n}\n\n\nexport const workers: WorkerProcess[] = [];\n"
  },
  {
    "path": "src/worker-process.ts",
    "content": "import { BuildError } from './util/errors';\nimport { buildErrorToJson } from './util/helpers';\nimport { Logger } from './logger/logger';\nimport { WorkerMessage } from './util/interfaces';\n\n\nprocess.on('message', (msg: WorkerMessage) => {\n  try {\n    const modulePath = `./${msg.taskModule}`;\n    const taskWorker = require(modulePath)[msg.taskWorker];\n\n    taskWorker(msg.context, msg.workerConfig)\n      .then((val: any) => {\n        taskResolve(msg.taskModule, msg.taskWorker, val);\n      }, (val: any) => {\n        taskReject(msg.taskModule, msg.taskWorker, val);\n      })\n      .catch((err: any) => {\n        taskError(msg.taskModule, msg.taskWorker, err);\n      });\n\n  } catch (e) {\n    taskError(msg.taskModule, msg.taskWorker, e);\n    process.exit(1);\n  }\n});\n\n\nfunction taskResolve(taskModule: string, taskWorker: string, val: any) {\n  const msg: WorkerMessage = {\n    taskModule: taskModule,\n    taskWorker: taskWorker,\n    resolve: val,\n    pid: process.pid\n  };\n\n  Logger.debug(`worker resolve, taskModule: ${msg.taskModule}, pid: ${msg.pid}`);\n\n  process.send(msg);\n}\n\n\nfunction taskReject(taskModule: string, taskWorker: string, error: Error) {\n  const buildError = new BuildError(error.message);\n  const json = buildErrorToJson(buildError);\n  const msg: WorkerMessage = {\n    taskModule: taskModule,\n    taskWorker: taskWorker,\n    reject: json,\n    pid: process.pid\n  };\n\n  Logger.debug(`worker reject, taskModule: ${msg.taskModule}, pid: ${msg.pid}`);\n\n  process.send(msg);\n}\n\n\nfunction taskError(taskModule: string, taskWorker: string, error: Error) {\n  const buildError = new BuildError(error.message);\n  const json = buildErrorToJson(buildError);\n  const msg: WorkerMessage = {\n    taskModule: taskModule,\n    taskWorker: taskWorker,\n    error: json,\n    pid: process.pid\n  };\n\n  Logger.debug(`worker error, taskModule: ${msg.taskModule}, pid: ${msg.pid}`);\n\n  process.send(msg);\n}\n"
  },
  {
    "path": "tsconfig.json",
    "content": "{\n  \"compilerOptions\": {\n    \"allowSyntheticDefaultImports\": true,\n    \"declaration\": true,\n    \"lib\": [\"dom\", \"es2015\"],\n    \"module\": \"commonjs\",\n    \"outDir\": \"./dist\",\n    \"target\": \"es5\",\n    \"noImplicitAny\": true,\n    \"types\": [\n      \"node\",\n      \"jest\"\n    ]\n  },\n  \"include\": [\n    \"src/**/*.ts\"\n  ],\n  \"exclude\": [\n    \"node_modules\"\n  ]\n}\n"
  },
  {
    "path": "tslint.json",
    "content": "{\n  \"extends\": \"tslint-ionic-rules\"\n}\n"
  }
]