├── .github └── workflows │ ├── cd.yml │ └── ci.yml ├── .gitignore ├── CHANGELOG.md ├── CONTRIBUTING.md ├── LICENSE ├── README.md ├── lerna.json ├── package-lock.json ├── package.json ├── packages ├── cordova-builders │ ├── .npmignore │ ├── CHANGELOG.md │ ├── README.md │ ├── add │ │ ├── index.ts │ │ ├── schema.d.ts │ │ └── schema.json │ ├── assets │ │ ├── consolelog-config.js │ │ ├── consolelogs.js │ │ └── cordova.js │ ├── builders.json │ ├── collection.json │ ├── cordova-build │ │ ├── index.ts │ │ ├── schema.d.ts │ │ └── schema.json │ ├── cordova-serve │ │ ├── index.ts │ │ ├── log-server.ts │ │ ├── schema.d.ts │ │ └── schema.json │ ├── lint-staged.config.js │ ├── package-lock.json │ ├── package.json │ ├── tsconfig.json │ └── utils │ │ ├── append-scripts.ts │ │ ├── config.ts │ │ └── index.ts └── schematics │ ├── .npmignore │ ├── CHANGELOG.md │ ├── README.md │ ├── collection.json │ ├── component │ ├── files │ │ └── __name@dasherize@if-flat__ │ │ │ ├── __name@dasherize__.__type@dasherize__.__styleext__.template │ │ │ ├── __name@dasherize__.__type@dasherize__.html.template │ │ │ ├── __name@dasherize__.__type@dasherize__.spec.ts.template │ │ │ ├── __name@dasherize__.__type@dasherize__.ts.template │ │ │ └── __name@dasherize__.module.ts.template │ ├── index.ts │ ├── schema.d.ts │ └── schema.json │ ├── lint-staged.config.js │ ├── package-lock.json │ ├── package.json │ ├── page │ ├── files │ │ └── __name@dasherize@if-flat__ │ │ │ ├── __name@dasherize__-routing.module.ts │ │ │ ├── __name@dasherize__.module.ts │ │ │ ├── __name@dasherize__.page.__styleext__ │ │ │ ├── __name@dasherize__.page.html │ │ │ ├── __name@dasherize__.page.spec.ts │ │ │ └── __name@dasherize__.page.ts │ ├── index.ts │ ├── route-utils │ │ └── index.ts │ ├── schema.d.ts │ └── schema.json │ ├── tsconfig.json │ └── util │ ├── ast-util.ts │ ├── change.ts │ └── index.ts └── tsconfig.json /.github/workflows/cd.yml: -------------------------------------------------------------------------------- 1 | name: CD 2 | 3 | on: 4 | push: 5 | branches: 6 | - stable 7 | 8 | jobs: 9 | build: 10 | name: Build, Test, and Deploy 11 | runs-on: ubuntu-latest 12 | steps: 13 | - uses: actions/checkout@v4 14 | with: 15 | fetch-depth: 0 16 | - uses: actions/setup-node@v4 17 | with: 18 | node-version: 18 19 | - name: Configure Identity 20 | run: | 21 | git config user.name github-actions 22 | git config user.email github-actions@github.com 23 | - name: Prepare NPM Token 24 | run: echo //registry.npmjs.org/:_authToken=${NPM_TOKEN} > .npmrc 25 | shell: bash 26 | env: 27 | NPM_TOKEN: ${{ secrets.NPM_TOKEN }} 28 | - name: Install Dependencies 29 | run: npm ci --no-package-lock 30 | shell: bash 31 | - name: Bootstrap 32 | run: npm run bootstrap -- --ignore-scripts 33 | shell: bash 34 | - name: Release 35 | run: npm run publish:ci 36 | shell: bash 37 | env: 38 | GH_TOKEN: ${{ secrets.GITHUB_TOKEN }} 39 | -------------------------------------------------------------------------------- /.github/workflows/ci.yml: -------------------------------------------------------------------------------- 1 | name: CI 2 | 3 | on: 4 | push: 5 | branches-ignore: 6 | - stable 7 | pull_request: 8 | branches-ignore: 9 | - stable 10 | 11 | jobs: 12 | build-and-test: 13 | name: Build and Test (Node ${{ matrix.node }}) 14 | runs-on: ubuntu-latest 15 | timeout-minutes: 30 16 | strategy: 17 | matrix: 18 | node: 19 | - 18.x 20 | steps: 21 | - uses: actions/setup-node@v4 22 | with: 23 | node-version: ${{ matrix.node }} 24 | - uses: actions/checkout@v4 25 | - name: Restore Dependency Cache 26 | uses: actions/cache@v4 27 | with: 28 | path: ~/.npm 29 | key: ${{ runner.OS }}-dependency-cache-${{ hashFiles('**/package.json') }} 30 | - run: npm ci 31 | - run: npm run bootstrap 32 | - run: npm run lint 33 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | .DS_Store 2 | Thumbs.db 3 | UserInterfaceState.xcuserstate 4 | .env 5 | node_modules 6 | **/*.js 7 | **/*.d.ts 8 | !packages/cordova-builders/**/schema.d.ts 9 | !packages/cordova-builders/assets/* 10 | !packages/schematics/**/schema.d.ts 11 | !packages/schematics/*/files/**/* 12 | !packages/**/lint-staged.config.js 13 | .vscode 14 | -------------------------------------------------------------------------------- /CHANGELOG.md: -------------------------------------------------------------------------------- 1 | # Historical Changelog 2 | 3 | This changelog is here for historical reasons. The current and active changelogs for each of the packages in this monorepo can be found here 4 | 5 | - [`@ionic/angular-toolkit`](https://github.com/ionic-team/angular-toolkit/blob/main/packages/schematics/CHANGELOG.md) 6 | - [`@ionic/cordova-builders`](https://github.com/ionic-team/angular-toolkit/blob/main/packages/cordova-builders/CHANGELOG.md) 7 | 8 | ## [5.0.3](https://github.com/ionic-team/angular-toolkit/compare/v5.0.2...v5.0.3) (2021-11-04) 9 | 10 | 11 | ### Bug Fixes 12 | 13 | * **deps:** use the correct version range ([83cd638](https://github.com/ionic-team/angular-toolkit/commit/83cd638dd0c70e0869d10ddc8e9bb97b3cd6e372)) 14 | 15 | ## [5.0.2](https://github.com/ionic-team/angular-toolkit/compare/v5.0.1...v5.0.2) (2021-11-04) 16 | 17 | 18 | ### Bug Fixes 19 | 20 | * **deps:** move peer deps to hard deps ([d389cd3](https://github.com/ionic-team/angular-toolkit/commit/d389cd38c9e4834acba0cbfaa8c71e1c346bb872)) 21 | 22 | ## [5.0.1](https://github.com/ionic-team/angular-toolkit/compare/v5.0.0...v5.0.1) (2021-11-04) 23 | 24 | 25 | ### Bug Fixes 26 | 27 | * **deps:** remove peer dep on devkit/architect ([eb46e23](https://github.com/ionic-team/angular-toolkit/commit/eb46e231fd68dec26c168776379a8ed45be99dd4)), closes [#460](https://github.com/ionic-team/angular-toolkit/issues/460) 28 | 29 | # [5.0.0](https://github.com/ionic-team/angular-toolkit/compare/v4.0.0...v5.0.0) (2021-10-28) 30 | 31 | 32 | ### Bug Fixes 33 | 34 | * bump cheerio to rc4 ([905cff2](https://github.com/ionic-team/angular-toolkit/commit/905cff2f48d001fbfe304ab8b786ef1c8ba8eccd)), closes [#456](https://github.com/ionic-team/angular-toolkit/issues/456) 35 | * update deps to match latest angular ([0100b8b](https://github.com/ionic-team/angular-toolkit/commit/0100b8b98ebcad8d1ad572d7042aaf9e49f30886)), closes [#455](https://github.com/ionic-team/angular-toolkit/issues/455) [#452](https://github.com/ionic-team/angular-toolkit/issues/452) 36 | 37 | 38 | ### Features 39 | 40 | * update to support angular 12.0 ([671bfba](https://github.com/ionic-team/angular-toolkit/commit/671bfba7349768c80dc4cfe02fe8c91616927cf1)), closes [#459](https://github.com/ionic-team/angular-toolkit/issues/459) [#460](https://github.com/ionic-team/angular-toolkit/issues/460) 41 | 42 | 43 | ### BREAKING CHANGES 44 | 45 | * Apps must use Angular 12.0 46 | 47 | # [4.0.0](https://github.com/ionic-team/angular-toolkit/compare/v3.1.1...v4.0.0) (2021-05-25) 48 | 49 | 50 | ### Features 51 | 52 | * **package:** update to angular12 ([707175d](https://github.com/ionic-team/angular-toolkit/commit/707175df21762b0c35d8c9cb1d20e881c7b83fbf)), closes [#447](https://github.com/ionic-team/angular-toolkit/issues/447) [#448](https://github.com/ionic-team/angular-toolkit/issues/448) 53 | 54 | 55 | ### BREAKING CHANGES 56 | 57 | * **package:** Users will need to upgrade to 12.0.0 of Angular/Angular CLI in order to use this. 58 | In order to migrate, please see https://update.angular.io 59 | 60 | ## [3.1.1](https://github.com/ionic-team/angular-toolkit/compare/v3.1.0...v3.1.1) (2021-03-16) 61 | 62 | 63 | ### Bug Fixes 64 | 65 | * **deps:** rebuild lock file for new versions ([f2a48fe](https://github.com/ionic-team/angular-toolkit/commit/f2a48fe9ca23788b0b01a66c8c6f02431e687dac)) 66 | 67 | # [3.1.0](https://github.com/ionic-team/angular-toolkit/compare/v3.0.0...v3.1.0) (2021-02-12) 68 | 69 | 70 | ### Features 71 | 72 | * **package:** update to Angular11.1 ([71b9800](https://github.com/ionic-team/angular-toolkit/commit/71b9800203db630a64a06e7b6cb60b7925c49743)), closes [#438](https://github.com/ionic-team/angular-toolkit/issues/438) 73 | * **test:** update async to waitForAsync ([#434](https://github.com/ionic-team/angular-toolkit/issues/434)) ([2185e50](https://github.com/ionic-team/angular-toolkit/commit/2185e50aa3e9bdc11115b71045f3ecd6c0e199a7)) 74 | 75 | # [3.0.0](https://github.com/ionic-team/angular-toolkit/compare/v2.3.3...v3.0.0) (2020-11-17) 76 | 77 | 78 | ### Features 79 | 80 | * **deps:** update deps in packages.json ([e56c93e](https://github.com/ionic-team/angular-toolkit/commit/e56c93e29f024a82fe6dfe8573c5d6e3b7b01550)) 81 | * update to support angular 11 ([afe9848](https://github.com/ionic-team/angular-toolkit/commit/afe9848974f72553a37e990b8d6b3410977b16f0)), closes [#388](https://github.com/ionic-team/angular-toolkit/issues/388) 82 | 83 | 84 | ### BREAKING CHANGES 85 | 86 | * **deps:** Users are required to update to 11.0.0 of Angular/Angular CLI in order to use this. 87 | In order to migrate, please see https://update.angular.io 88 | 89 | ## [2.3.3](https://github.com/ionic-team/angular-toolkit/compare/v2.3.2...v2.3.3) (2020-08-24) 90 | 91 | 92 | ### Bug Fixes 93 | 94 | * **generators:** update path builder ([#275](https://github.com/ionic-team/angular-toolkit/issues/275)) ([1393e8c](https://github.com/ionic-team/angular-toolkit/commit/1393e8c088c93d2a505d1ac96c68f03e32a815ac)), closes [#274](https://github.com/ionic-team/angular-toolkit/issues/274) 95 | 96 | ## [2.3.2](https://github.com/ionic-team/angular-toolkit/compare/v2.3.1...v2.3.2) (2020-08-21) 97 | 98 | 99 | ### Bug Fixes 100 | 101 | * **cordovaserve:** update copy-webpack to new format ([#272](https://github.com/ionic-team/angular-toolkit/issues/272)) ([c1e66b5](https://github.com/ionic-team/angular-toolkit/commit/c1e66b554a80665eed77ad3adfd1117b49bc73ab)), closes [#265](https://github.com/ionic-team/angular-toolkit/issues/265) 102 | 103 | ## [2.3.1](https://github.com/ionic-team/angular-toolkit/compare/v2.3.0...v2.3.1) (2020-08-19) 104 | 105 | 106 | ### Bug Fixes 107 | 108 | * **deps:** update deps for security vulnerabilities ([#263](https://github.com/ionic-team/angular-toolkit/issues/263)) ([f0e514d](https://github.com/ionic-team/angular-toolkit/commit/f0e514d6140d929e521ff4f77844642424cfe7cb)), closes [#261](https://github.com/ionic-team/angular-toolkit/issues/261) 109 | 110 | # [2.3.0](https://github.com/ionic-team/angular-toolkit/compare/v2.2.0...v2.3.0) (2020-07-20) 111 | 112 | 113 | ### Features 114 | 115 | * update schematics to support ng10 ([#251](https://github.com/ionic-team/angular-toolkit/issues/251)) ([7c9a245](https://github.com/ionic-team/angular-toolkit/commit/7c9a24560088163547369ad67f6d3d98e752bdca)) 116 | 117 | # [2.2.0](https://github.com/ionic-team/angular-toolkit/compare/v2.1.2...v2.2.0) (2020-02-24) 118 | 119 | 120 | ### Features 121 | 122 | * **deps:** bump deps to support angular 9 ([5b80c04](https://github.com/ionic-team/angular-toolkit/commit/5b80c04de6c00b06339c183bbd30efeff5f51dc3)) 123 | 124 | ## [2.1.2](https://github.com/ionic-team/angular-toolkit/compare/v2.1.1...v2.1.2) (2020-01-13) 125 | 126 | 127 | ### Bug Fixes 128 | 129 | * **cordova:** add cordova.js to index.html even without scripts array ([c8bb37b](https://github.com/ionic-team/angular-toolkit/commit/c8bb37bd1f817f762720b0e3a5c89b4d6a7464e0)), closes [#188](https://github.com/ionic-team/angular-toolkit/issues/188) 130 | 131 | ## [2.1.1](https://github.com/ionic-team/angular-toolkit/compare/v2.1.0...v2.1.1) (2019-10-22) 132 | 133 | 134 | ### Bug Fixes 135 | 136 | * **build:** handle no scripts in angular ([#182](https://github.com/ionic-team/angular-toolkit/issues/182)) ([388e1ad](https://github.com/ionic-team/angular-toolkit/commit/388e1ad3dec5004ceaa8030acf7a248c121be94c)), closes [#179](https://github.com/ionic-team/angular-toolkit/issues/179) 137 | 138 | # [2.1.0](https://github.com/ionic-team/angular-toolkit/compare/v2.0.0...v2.1.0) (2019-10-22) 139 | 140 | 141 | ### Bug Fixes 142 | 143 | * **routing:** split out routes into routing module ([#181](https://github.com/ionic-team/angular-toolkit/issues/181)) ([b13b823](https://github.com/ionic-team/angular-toolkit/commit/b13b8233c1b693be7a845494b3d98cda2c8fe1da)) 144 | * **unit-tests:** allow the components to hydrate ([#173](https://github.com/ionic-team/angular-toolkit/issues/173)) ([4159e59](https://github.com/ionic-team/angular-toolkit/commit/4159e598a43bdedaaaa4d179dd7c3fabc2618d42)) 145 | 146 | 147 | ### Features 148 | 149 | * **router:** change to dynamic import ([#176](https://github.com/ionic-team/angular-toolkit/issues/176)) ([fbf3627](https://github.com/ionic-team/angular-toolkit/commit/fbf3627f8a182b48bdacd6ca601d2c9411cf3fda)) 150 | 151 | # [2.0.0](https://github.com/ionic-team/angular-toolkit/compare/v1.5.1...v2.0.0) (2019-06-25) 152 | 153 | 154 | ### Features 155 | 156 | * support Angular 8 ([#132](https://github.com/ionic-team/angular-toolkit/issues/132)) ([166d547](https://github.com/ionic-team/angular-toolkit/commit/166d547)) 157 | 158 | 159 | ### BREAKING CHANGES 160 | 161 | * this updates dependencies for Angular 8. Users are 162 | required to update to 8.0.0 of Angular/Angular CLI in order to use 163 | this. In order to migrate, please see https://update.angular.io 164 | 165 | ## [1.5.1](https://github.com/ionic-team/angular-toolkit/compare/v1.5.0...v1.5.1) (2019-04-09) 166 | 167 | 168 | ### Bug Fixes 169 | 170 | * **cordova-build:** only set sourceMap if specified ([#107](https://github.com/ionic-team/angular-toolkit/issues/107)) ([2a99ac0](https://github.com/ionic-team/angular-toolkit/commit/2a99ac0)) 171 | 172 | # [1.5.0](https://github.com/ionic-team/angular-toolkit/compare/v1.4.1...v1.5.0) (2019-03-21) 173 | 174 | 175 | ### Bug Fixes 176 | 177 | * **cordova:** obey `--source-map` for production builds ([23481bd](https://github.com/ionic-team/angular-toolkit/commit/23481bd)) 178 | 179 | 180 | ### Features 181 | 182 | * **cordova-serve:** support --consolelogs option ([#100](https://github.com/ionic-team/angular-toolkit/issues/100)) ([07af906](https://github.com/ionic-team/angular-toolkit/commit/07af906)) 183 | 184 | ## [1.4.1](https://github.com/ionic-team/angular-toolkit/compare/v1.4.0...v1.4.1) (2019-03-19) 185 | 186 | 187 | ### Bug Fixes 188 | 189 | * **page:** remove padding attribute in ion-content template ([#106](https://github.com/ionic-team/angular-toolkit/issues/106)) ([c33f932](https://github.com/ionic-team/angular-toolkit/commit/c33f932)) 190 | * **schematics:** update component spec ([#88](https://github.com/ionic-team/angular-toolkit/issues/88)) ([f19e6d8](https://github.com/ionic-team/angular-toolkit/commit/f19e6d8)) 191 | 192 | # [1.4.0](https://github.com/ionic-team/angular-toolkit/compare/v1.3.0...v1.4.0) (2019-02-13) 193 | 194 | 195 | ### Features 196 | 197 | * **component:** add custom component schematic ([#68](https://github.com/ionic-team/angular-toolkit/issues/68)) ([527f54e](https://github.com/ionic-team/angular-toolkit/commit/527f54e)) 198 | 199 | # [1.3.0](https://github.com/ionic-team/angular-toolkit/compare/v1.2.3...v1.3.0) (2019-01-29) 200 | 201 | 202 | ### Bug Fixes 203 | 204 | * **serve:** pass `cordovaBasePath` to cordova-build builder ([#57](https://github.com/ionic-team/angular-toolkit/issues/57)) ([93e3bbe](https://github.com/ionic-team/angular-toolkit/commit/93e3bbe)) 205 | 206 | 207 | ### Features 208 | 209 | * **build:** add `--cordova-mock` option ([#63](https://github.com/ionic-team/angular-toolkit/issues/63)) ([a659636](https://github.com/ionic-team/angular-toolkit/commit/a659636)) 210 | 211 | ## [1.2.3](https://github.com/ionic-team/angular-toolkit/compare/v1.2.2...v1.2.3) (2019-01-24) 212 | 213 | 214 | ### Bug Fixes 215 | 216 | * **application:** add e2e schematics to fulfill `ng g app` ([fc1421e](https://github.com/ionic-team/angular-toolkit/commit/fc1421e)) 217 | * **build:** never delete output path ([b614db9](https://github.com/ionic-team/angular-toolkit/commit/b614db9)) 218 | * **serve:** use proxyConfig option from serve ([859ce96](https://github.com/ionic-team/angular-toolkit/commit/859ce96)) 219 | 220 | ## [1.2.2](https://github.com/ionic-team/angular-toolkit/compare/v1.2.1...v1.2.2) (2018-12-21) 221 | 222 | 223 | ### Bug Fixes 224 | 225 | * **page:** properly handle project selection ([4875aa7](https://github.com/ionic-team/angular-toolkit/commit/4875aa7)) 226 | 227 | ## [1.2.1](https://github.com/ionic-team/angular-toolkit/compare/v1.2.0...v1.2.1) (2018-12-19) 228 | 229 | 230 | ### Bug Fixes 231 | 232 | * **build:** respect browserTarget setting ([3a9adfa](https://github.com/ionic-team/angular-toolkit/commit/3a9adfa)) 233 | * **page:** dasherize route path ([e32e77b](https://github.com/ionic-team/angular-toolkit/commit/e32e77b)) 234 | 235 | # [1.2.0](https://github.com/ionic-team/angular-toolkit/compare/v1.1.0...v1.2.0) (2018-11-15) 236 | 237 | 238 | ### Bug Fixes 239 | 240 | * **changelog:** correctly link to commits ([#33](https://github.com/ionic-team/angular-toolkit/issues/33)) ([be96104](https://github.com/ionic-team/angular-toolkit/commit/be96104)) 241 | 242 | 243 | ### Features 244 | 245 | * **serve:** support `--ssl` for dev-server ([9d65915](https://github.com/ionic-team/angular-toolkit/commit/9d65915)) 246 | 247 | # [1.1.0](https://github.com/ionic-team/angular-toolkit.git/compare/v1.0.0...v1.1.0) (2018-10-31) 248 | 249 | 250 | ### Bug Fixes 251 | 252 | * **serve:** validate cordova-build options to provide defaults ([98d6a63](https://github.com/ionic-team/angular-toolkit/commit/98d6a63)) 253 | 254 | 255 | ### Features 256 | 257 | * support Angular 7 ([3d1172b](https://github.com/ionic-team/angular-toolkit/commit/3d1172b)) 258 | 259 | # 1.0.0 (2018-10-05) 260 | 261 | 262 | ### Features 263 | 264 | * Initial release ([2a5fab5](https://github.com/ionic-team/angular-toolkit/commit/2a5fab5)) 265 | -------------------------------------------------------------------------------- /CONTRIBUTING.md: -------------------------------------------------------------------------------- 1 | # Contributing 2 | 3 | :mega: **Support/Questions?**: Please see our [Support 4 | Page](https://ionicframework.com/support) for general support questions. The 5 | issues on GitHub should be reserved for bug reports and feature requests. 6 | 7 | ### Local Setup 8 | 9 | 1. Fork the repo & clone it locally. 10 | 1. `npm install` 11 | 1. `npm run build` to build source files 12 | 13 | #### Warning: `npm link` may not work 14 | 15 | Using symlinks for Angular tooling may lead to issues. To test changes in a real project, copy the library to `node_modules/@ionic/angular-toolkit`. For example, using `rsync`: 16 | 17 | ``` 18 | rsync -avuP --exclude .git --exclude node_modules /path/to/git/angular-toolkit node_modules/@ionic 19 | ``` 20 | 21 | ### Publishing 22 | 23 | CI automatically publishes the next version semantically from analyzing commits in `stable`. To maintain a shared history between `main` and `stable`, the branches must be rebased with each other locally. 24 | 25 | * When it's time to cut a release from `main`: 26 | 27 | ``` 28 | git checkout stable 29 | git rebase main 30 | git push origin stable 31 | ``` 32 | 33 | * Await successful publish in CI. Ionitron will push the updated versions and tags to `stable`. 34 | * Sync `main` with `stable`. 35 | 36 | ``` 37 | git pull origin stable 38 | git checkout main 39 | git rebase stable 40 | git push origin main 41 | ``` 42 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2018 Ionic 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Angular Toolkit monorepo 2 | 3 | This is a monorepo with the follow packages 4 | 5 | | Package | Source | Version | 6 | |------------------------------------------------------------------------------------|--------------------------------------------------------------|---------------------------------------------------------------------------------------------------------------------------| 7 | | [`@ionic/angular-toolkit`](https://www.npmjs.com/package/@ionic/angular-toolkit) | [`./packages/schematics`](./packages/schematics) | [![npm](https://img.shields.io/npm/v/@ionic/angular-toolkit.svg)](https://www.npmjs.com/package/@ionic/angular-toolkit) | 8 | | [`@ionic/cordova-builders`](https://www.npmjs.com/package/@ionic/cordova-builders) | [`./packages/cordova-builders`](./packages/cordova-builders) | [![npm](https://img.shields.io/npm/v/@ionic/cordova-builders.svg)](https://www.npmjs.com/package/@ionic/cordova-builders) | 9 | 10 | -------------------------------------------------------------------------------- /lerna.json: -------------------------------------------------------------------------------- 1 | { 2 | "version": "independent", 3 | "packages": ["packages/*"], 4 | "command": { 5 | "bootstrap": { 6 | "hoist": true 7 | }, 8 | "version": { 9 | "allowBranch": "stable", 10 | "conventionalCommits": true, 11 | "createRelease": "github", 12 | "message": "chore(release): publish [skip ci]", 13 | "tagVersionPrefix": "" 14 | }, 15 | "publish": { 16 | "verifyAccess": false 17 | } 18 | } 19 | } 20 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "version": "5.0.3", 3 | "license": "MIT", 4 | "description": "Schematics and builders for @ionic/angular apps.", 5 | "homepage": "https://ionicframework.com/", 6 | "author": "Ionic Team (https://ionicframework.com)", 7 | "scripts": { 8 | "bootstrap": "lerna bootstrap && npm run build", 9 | "build": "lerna run build", 10 | "lint": "lerna run lint", 11 | "lint:fix": "lerna run lint -- -- --fix", 12 | "prepublishOnly": "npm run build", 13 | "publish:testing": "lerna publish prerelease --preid=testing --exact --no-git-tag-version --no-push --dist-tag=testing", 14 | "publish:ci": "lerna publish -m 'chore(release): publish [skip ci]' --exact --conventional-commits --yes --no-verify-access", 15 | "publish:ci:testing": "lerna publish prerelease --preid=testing --no-git-tag-version --no-push --dist-tag=testing -m 'chore(release): publish [skip ci]' --exact --conventional-commits --yes", 16 | "cz": "git-cz" 17 | }, 18 | "repository": { 19 | "type": "git", 20 | "url": "https://github.com/ionic-team/angular-toolkit.git" 21 | }, 22 | "bugs": { 23 | "url": "https://github.com/ionic-team/angular-toolkit/issues" 24 | }, 25 | "keywords": [ 26 | "angular", 27 | "Angular CLI", 28 | "blueprints", 29 | "code generation", 30 | "devkit", 31 | "schematics", 32 | "ionic", 33 | "ionic framework", 34 | "ionicframework" 35 | ], 36 | "devDependencies": { 37 | "@ionic/eslint-config": "^0.3.0", 38 | "@ionic/prettier-config": "^2.0.0", 39 | "commitizen": "^4.2.4", 40 | "cz-conventional-changelog": "^3.3.0", 41 | "eslint": "^8.57.0", 42 | "eslint-config-prettier": "^8.3.0", 43 | "eslint-plugin-import": "^2.25.3", 44 | "eslint-plugin-prettier": "^4.0.0", 45 | "husky": "^7.0.4", 46 | "lerna": "^4.0.0", 47 | "prettier": "^2.4.1", 48 | "typescript": ">=5.5.0 <5.9.0", 49 | "typescript-eslint-language-service": "^4.1.3" 50 | }, 51 | "config": { 52 | "commitizen": { 53 | "path": "./node_modules/cz-conventional-changelog" 54 | } 55 | }, 56 | "prettier": "@ionic/prettier-config", 57 | "eslintConfig": { 58 | "root": true, 59 | "ignorePatterns": [ 60 | "node_modules", 61 | "**/*.d.ts", 62 | "packages/schematics/**/files" 63 | ], 64 | "parser": "@typescript-eslint/parser", 65 | "parserOptions": { 66 | "project": "./tsconfig.json" 67 | }, 68 | "plugins": [ 69 | "@typescript-eslint", 70 | "prettier" 71 | ], 72 | "extends": [ 73 | "@ionic/eslint-config/recommended" 74 | ], 75 | "rules": { 76 | "prettier/prettier": "error" 77 | } 78 | } 79 | } 80 | -------------------------------------------------------------------------------- /packages/cordova-builders/.npmignore: -------------------------------------------------------------------------------- 1 | # having this file excludes .gitignore entries 2 | 3 | tsconfig.json 4 | tslint.js 5 | .circleci 6 | *.ts 7 | !*.d.ts 8 | -------------------------------------------------------------------------------- /packages/cordova-builders/CHANGELOG.md: -------------------------------------------------------------------------------- 1 | # Change Log 2 | 3 | All notable changes to this project will be documented in this file. 4 | See [Conventional Commits](https://conventionalcommits.org) for commit guidelines. 5 | 6 | # [12.2.0](https://github.com/ionic-team/angular-toolkit/compare/@ionic/cordova-builders@12.1.2...@ionic/cordova-builders@12.2.0) (2025-04-10) 7 | 8 | 9 | ### Features 10 | 11 | * support angular 19 ([#520](https://github.com/ionic-team/angular-toolkit/issues/520)) ([2823580](https://github.com/ionic-team/angular-toolkit/commit/282358057bac80e3f3a3f96cec6b7d8c1380cea0)) 12 | 13 | 14 | 15 | 16 | 17 | ## [12.1.2](https://github.com/ionic-team/angular-toolkit/compare/@ionic/cordova-builders@12.1.1...@ionic/cordova-builders@12.1.2) (2024-09-20) 18 | 19 | 20 | ### Bug Fixes 21 | 22 | * Remove extra line from README ([#517](https://github.com/ionic-team/angular-toolkit/issues/517)) ([a6d925f](https://github.com/ionic-team/angular-toolkit/commit/a6d925fca2a477e54d9157529cb25b4f48b04176)) 23 | 24 | 25 | 26 | 27 | 28 | ## [12.1.1](https://github.com/ionic-team/angular-toolkit/compare/@ionic/cordova-builders@12.1.0...@ionic/cordova-builders@12.1.1) (2024-09-05) 29 | 30 | 31 | ### Bug Fixes 32 | 33 | * **cordova-builders:** release bump ([#515](https://github.com/ionic-team/angular-toolkit/issues/515)) ([047a563](https://github.com/ionic-team/angular-toolkit/commit/047a563db305a07e1a18ac08935d2809dc3a9a04)) 34 | 35 | 36 | 37 | 38 | 39 | # [12.1.0](https://github.com/ionic-team/angular-toolkit/compare/@ionic/cordova-builders@11.0.0...@ionic/cordova-builders@12.1.0) (2024-09-03) 40 | 41 | 42 | ### Features 43 | 44 | * support angular 18 ([#513](https://github.com/ionic-team/angular-toolkit/issues/513)) ([70d5066](https://github.com/ionic-team/angular-toolkit/commit/70d50665a000c10b1cf5701aad664c2e620541c1)) 45 | 46 | 47 | 48 | 49 | 50 | # [12.0.0](https://github.com/ionic-team/angular-toolkit/compare/@ionic/cordova-builders@10.1.1...@ionic/cordova-builders@11.0.0) (2024-09-01) 51 | 52 | 53 | ### Features 54 | 55 | * support angular 18 ([#508](https://github.com/ionic-team/angular-toolkit/issues/508)) ([c002c51](https://github.com/ionic-team/angular-toolkit/commit/c002c51cc09f45639ca97bc5354840d9c384556c)) 56 | 57 | 58 | ### BREAKING CHANGES 59 | 60 | * The minimum version of Angular required is now 18. Please updates your apps to use 61 | the latest release of Angular. 62 | 63 | # [11.0.0](https://github.com/ionic-team/angular-toolkit/compare/@ionic/cordova-builders@10.1.1...@ionic/cordova-builders@11.0.0) (2024-01-09) 64 | 65 | 66 | ### Features 67 | 68 | * support angular 17 ([#508](https://github.com/ionic-team/angular-toolkit/issues/508)) ([c002c51](https://github.com/ionic-team/angular-toolkit/commit/c002c51cc09f45639ca97bc5354840d9c384556c)) 69 | 70 | 71 | ### BREAKING CHANGES 72 | 73 | * The minimum version of Angular required is now 17. Please updates your apps to use 74 | the latest release of Angular. 75 | 76 | 77 | 78 | 79 | 80 | ## [10.1.1](https://github.com/ionic-team/angular-toolkit/compare/@ionic/cordova-builders@10.1.0...@ionic/cordova-builders@10.1.1) (2024-01-09) 81 | 82 | 83 | ### Reverts 84 | 85 | * Revert "feat: support angular 17 (#503)" ([feb6361](https://github.com/ionic-team/angular-toolkit/commit/feb6361f1452e5ccbe242b0e00c0ded05beacec4)), closes [#503](https://github.com/ionic-team/angular-toolkit/issues/503) 86 | 87 | 88 | 89 | 90 | 91 | # [10.1.0](https://github.com/ionic-team/angular-toolkit/compare/@ionic/cordova-builders@10.0.0...@ionic/cordova-builders@10.1.0) (2024-01-09) 92 | 93 | 94 | ### Features 95 | 96 | * support angular 17 ([#503](https://github.com/ionic-team/angular-toolkit/issues/503)) ([26f0cd9](https://github.com/ionic-team/angular-toolkit/commit/26f0cd9a17b1489a1e864bb468f4e51315d4a004)), closes [#502](https://github.com/ionic-team/angular-toolkit/issues/502) 97 | 98 | 99 | 100 | 101 | 102 | # [10.0.0](https://github.com/ionic-team/angular-toolkit/compare/@ionic/cordova-builders@9.0.0...@ionic/cordova-builders@10.0.0) (2023-07-06) 103 | 104 | 105 | ### Features 106 | 107 | * **builders:** add support for angular 16.0 ([379d8d4](https://github.com/ionic-team/angular-toolkit/commit/379d8d43d066b1cd556b083ccb506703a166ce1d)), closes [#493](https://github.com/ionic-team/angular-toolkit/issues/493) [#494](https://github.com/ionic-team/angular-toolkit/issues/494) 108 | 109 | 110 | ### BREAKING CHANGES 111 | 112 | * **builders:** The minimum version of Angular required is now 16. Please updates your apps to use 113 | the lates release of Angular 114 | 115 | 116 | 117 | 118 | 119 | # [9.0.0](https://github.com/ionic-team/angular-toolkit/compare/@ionic/cordova-builders@8.0.0...@ionic/cordova-builders@9.0.0) (2023-03-28) 120 | 121 | 122 | ### Features 123 | 124 | * bump deps to angular 15 ([009079a](https://github.com/ionic-team/angular-toolkit/commit/009079a5cf13804d55a7b1d15c79824cac9db179)) 125 | 126 | 127 | ### BREAKING CHANGES 128 | 129 | * Apps will need to update to Angular 15 in order to suppor this verion. 130 | 131 | 132 | 133 | 134 | 135 | # [8.0.0](https://github.com/ionic-team/angular-toolkit/compare/@ionic/cordova-builders@7.0.0...@ionic/cordova-builders@8.0.0) (2023-02-15) 136 | 137 | 138 | ### Features 139 | 140 | * bump deps to support angular v15 ([e7fdfd8](https://github.com/ionic-team/angular-toolkit/commit/e7fdfd8581819430b549cfae4a87e9edbadf57c9)), closes [#481](https://github.com/ionic-team/angular-toolkit/issues/481) [#477](https://github.com/ionic-team/angular-toolkit/issues/477) [#482](https://github.com/ionic-team/angular-toolkit/issues/482) 141 | 142 | 143 | ### BREAKING CHANGES 144 | 145 | * Apps will need to update to Angular 15 in order to support this version 146 | 147 | 148 | 149 | 150 | 151 | # [7.0.0](https://github.com/ionic-team/angular-toolkit/compare/@ionic/cordova-builders@6.1.0...@ionic/cordova-builders@7.0.0) (2022-07-18) 152 | 153 | 154 | ### Features 155 | 156 | * bump deps to angular 14 ([767598e](https://github.com/ionic-team/angular-toolkit/commit/767598eace5bc91767008fd86670729c8079a1d9)), closes [#474](https://github.com/ionic-team/angular-toolkit/issues/474) 157 | 158 | 159 | ### BREAKING CHANGES 160 | 161 | * Apps will need to update to Angular 14 in order to suppor this verion. 162 | 163 | 164 | 165 | 166 | 167 | # [6.1.0](https://github.com/ionic-team/angular-toolkit/compare/@ionic/cordova-builders@6.0.0...@ionic/cordova-builders@6.1.0) (2022-03-08) 168 | 169 | 170 | ### Features 171 | 172 | * add cordova-builders ng-add ([#467](https://github.com/ionic-team/angular-toolkit/issues/467)) ([1b9a0bd](https://github.com/ionic-team/angular-toolkit/commit/1b9a0bdabdbdcd1f6226a8ddc771abd1f54afd42)) 173 | 174 | 175 | 176 | 177 | 178 | # 6.0.0 (2022-01-18) 179 | 180 | 181 | ### Features 182 | 183 | * split tooling ([#465](https://github.com/ionic-team/angular-toolkit/issues/465)) ([a8303ec](https://github.com/ionic-team/angular-toolkit/commit/a8303ec5df92c9f463ded30fbcb97a908578adf5)) 184 | 185 | 186 | ### BREAKING CHANGES 187 | 188 | * ionic/angular + cordova users will now need to install @ionic/cordova-builders and 189 | update their angular.json to reflect the new package name 190 | 191 | * chore(): fix package description 192 | 193 | 194 | 195 | 196 | 197 | ## 5.0.4-testing.4 (2021-11-17) 198 | 199 | **Note:** Version bump only for package @ionic/cordova-builders 200 | 201 | 202 | 203 | 204 | 205 | ## 5.0.4-testing.3 (2021-11-16) 206 | 207 | **Note:** Version bump only for package @ionic/cordova-builders 208 | 209 | 210 | 211 | 212 | 213 | ## 5.0.4-testing.2 (2021-11-16) 214 | 215 | **Note:** Version bump only for package @ionic/cordova-builders 216 | 217 | 218 | 219 | 220 | 221 | ## 5.0.4-testing.1 (2021-11-16) 222 | 223 | **Note:** Version bump only for package @ionic/cordova-builders 224 | 225 | 226 | 227 | 228 | 229 | ## 5.0.4-testing.0 (2021-11-16) 230 | 231 | **Note:** Version bump only for package @ionic/cordova-builders 232 | -------------------------------------------------------------------------------- /packages/cordova-builders/README.md: -------------------------------------------------------------------------------- 1 | # `@ionic/cordova-builders` 2 | 3 | A collection of builders for `@ionic/angular` projects using Cordova. 4 | 5 | To add these builders to your project, run 6 | 7 | ```shell 8 | ng add @ionic/cordova-builders 9 | ``` 10 | -------------------------------------------------------------------------------- /packages/cordova-builders/add/index.ts: -------------------------------------------------------------------------------- 1 | import type { Rule, Tree } from '@angular-devkit/schematics'; 2 | import { chain, SchematicsException } from '@angular-devkit/schematics'; 3 | import { getWorkspace } from '@schematics/angular/utility/workspace'; 4 | 5 | import { addArchitectBuilder, getDefaultAngularAppName } from './../utils/config'; 6 | import type { Schema as AddOptions } from './schema'; 7 | 8 | function addCordovaBuilder(projectName: string): Rule { 9 | return (host: Tree) => { 10 | addArchitectBuilder(host, projectName, 'ionic-cordova-serve', { 11 | builder: '@ionic/cordova-builders:cordova-serve', 12 | options: { 13 | cordovaBuildTarget: `${projectName}:ionic-cordova-build`, 14 | devServerTarget: `${projectName}:serve`, 15 | }, 16 | configurations: { 17 | production: { 18 | cordovaBuildTarget: `${projectName}:ionic-cordova-build:production`, 19 | devServerTarget: `${projectName}:serve:production`, 20 | }, 21 | }, 22 | }); 23 | addArchitectBuilder(host, projectName, 'ionic-cordova-build', { 24 | builder: '@ionic/cordova-builders:cordova-build', 25 | options: { 26 | browserTarget: `${projectName}:build`, 27 | }, 28 | configurations: { 29 | production: { 30 | browserTarget: `${projectName}:build:production`, 31 | }, 32 | }, 33 | }); 34 | return host; 35 | }; 36 | } 37 | 38 | export default function ngAdd(options: AddOptions): Rule { 39 | return async (host: Tree) => { 40 | const workspace = await getWorkspace(host); 41 | if (!options.project) { 42 | options.project = getDefaultAngularAppName(workspace); 43 | } 44 | const project = workspace.projects.get(options.project); 45 | 46 | if (!project || project.extensions.projectType !== 'application') { 47 | throw new SchematicsException(`Ionic Add requires a project type of "application".`); 48 | } 49 | 50 | return chain([addCordovaBuilder(options.project)]); 51 | }; 52 | } 53 | -------------------------------------------------------------------------------- /packages/cordova-builders/add/schema.d.ts: -------------------------------------------------------------------------------- 1 | export interface Schema { 2 | project?: string; 3 | } 4 | -------------------------------------------------------------------------------- /packages/cordova-builders/add/schema.json: -------------------------------------------------------------------------------- 1 | { 2 | "$schema": "http://json-schema.org/schema", 3 | "$id": "ionicNgAdd", 4 | "title": "Ionic Add options", 5 | "type": "object", 6 | "properties": { 7 | "project": { 8 | "type": "string", 9 | "description": "The name of the project.", 10 | "$default": { 11 | "$source": "projectName" 12 | } 13 | } 14 | }, 15 | "required": [] 16 | } 17 | -------------------------------------------------------------------------------- /packages/cordova-builders/assets/consolelog-config.js: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ionic-team/angular-toolkit/2146fa2f9337451289d6fd412c2d8595778b2e54/packages/cordova-builders/assets/consolelog-config.js -------------------------------------------------------------------------------- /packages/cordova-builders/assets/consolelogs.js: -------------------------------------------------------------------------------- 1 | // Script injected by @ionic/angular-toolkit to send console logs back 2 | // to a websocket server so they can be printed to the terminal 3 | window.Ionic = window.Ionic || {}; 4 | window.Ionic.ConsoleLogServer = { 5 | start: function (config) { 6 | var self = this; 7 | 8 | this.socket = new WebSocket( 9 | 'ws://' + window.location.hostname + ':' + String(config.wsPort) 10 | ); 11 | this.msgQueue = []; 12 | 13 | this.socket.onopen = function () { 14 | self.socketReady = true; 15 | 16 | self.socket.onclose = function () { 17 | self.socketReady = false; 18 | console.warn('Console log server closed'); 19 | }; 20 | }; 21 | 22 | this.patchConsole(); 23 | }, 24 | 25 | queueMessageSend: function (msg) { 26 | this.msgQueue.push(msg); 27 | this.drainMessageQueue(); 28 | }, 29 | 30 | drainMessageQueue: function () { 31 | var msg; 32 | while ((msg = this.msgQueue.shift())) { 33 | if (this.socketReady) { 34 | try { 35 | this.socket.send(JSON.stringify(msg)); 36 | } catch (e) { 37 | if (!(e instanceof TypeError)) { 38 | console.error('ws error: ' + e); 39 | } 40 | } 41 | } 42 | } 43 | }, 44 | 45 | patchConsole: function () { 46 | var self = this; 47 | 48 | function _patchConsole(consoleType) { 49 | console[consoleType] = (function () { 50 | var orgConsole = console[consoleType]; 51 | return function () { 52 | orgConsole.apply(console, arguments); 53 | var msg = { 54 | category: 'console', 55 | type: consoleType, 56 | data: [], 57 | }; 58 | for (var i = 0; i < arguments.length; i++) { 59 | msg.data.push(arguments[i]); 60 | } 61 | if (msg.data.length) { 62 | self.queueMessageSend(msg); 63 | } 64 | }; 65 | })(); 66 | } 67 | 68 | // https://cordova.apache.org/docs/en/latest/reference/cordova-plugin-console/#supported-methods 69 | var consoleFns = [ 70 | 'log', 71 | 'error', 72 | 'exception', 73 | 'warn', 74 | 'info', 75 | 'debug', 76 | 'assert', 77 | 'dir', 78 | 'dirxml', 79 | 'time', 80 | 'timeEnd', 81 | 'table', 82 | ]; 83 | for (var i in consoleFns) { 84 | _patchConsole(consoleFns[i]); 85 | } 86 | }, 87 | }; 88 | 89 | Ionic.ConsoleLogServer.start(Ionic.ConsoleLogServerConfig || {}); 90 | -------------------------------------------------------------------------------- /packages/cordova-builders/assets/cordova.js: -------------------------------------------------------------------------------- 1 | // mock cordova.js 2 | -------------------------------------------------------------------------------- /packages/cordova-builders/builders.json: -------------------------------------------------------------------------------- 1 | { 2 | "builders": { 3 | "cordova-build": { 4 | "implementation": "./cordova-build/index", 5 | "schema": "./cordova-build/schema.json", 6 | "description": "Perform a browser build with Cordova assets." 7 | }, 8 | 9 | 10 | "cordova-serve": { 11 | "implementation": "./cordova-serve/index", 12 | "schema": "./cordova-serve/schema.json", 13 | "description": "Run the dev-server with Cordova assets." 14 | } 15 | } 16 | } 17 | -------------------------------------------------------------------------------- /packages/cordova-builders/collection.json: -------------------------------------------------------------------------------- 1 | { 2 | "$schema": "../../node_modules/@angular-devkit/schematics/collection-schema.json", 3 | "schematics": { 4 | "ng-add": { 5 | "description": "Add Cordova builders to your project", 6 | "factory": "./add", 7 | "schema": "./add/schema.json" 8 | } 9 | } 10 | } 11 | -------------------------------------------------------------------------------- /packages/cordova-builders/cordova-build/index.ts: -------------------------------------------------------------------------------- 1 | import { createBuilder, targetFromTargetString } from '@angular-devkit/architect'; 2 | import type { BuilderContext, BuilderOutput } from '@angular-devkit/architect'; 3 | import type { json } from '@angular-devkit/core'; 4 | 5 | import { prepareBrowserConfig, validateBuilderConfig } from '../utils'; 6 | 7 | import type { CordovaBuildBuilderSchema } from './schema'; 8 | 9 | export async function buildCordova( 10 | options: CordovaBuildBuilderSchema, 11 | context: BuilderContext 12 | ): Promise { 13 | context.reportStatus(`running cordova build...`); 14 | // Get angular browser build target 15 | const browserTargetSpec = targetFromTargetString(options.browserTarget); 16 | // Get browser build options 17 | const browserBuildTargetOptions = await context.getTargetOptions(browserTargetSpec); 18 | 19 | const formattedOptions = validateBuilderConfig(options); 20 | const newOptions = prepareBrowserConfig(formattedOptions, browserBuildTargetOptions); 21 | 22 | const browserBuild = await context.scheduleTarget(browserTargetSpec, newOptions); 23 | return browserBuild.result; 24 | } 25 | 26 | export default createBuilder(buildCordova); 27 | -------------------------------------------------------------------------------- /packages/cordova-builders/cordova-build/schema.d.ts: -------------------------------------------------------------------------------- 1 | export interface CordovaBuildBuilderSchema { 2 | browserTarget: string; 3 | platform?: string; 4 | cordovaBasePath?: string; 5 | sourceMap?: boolean; 6 | cordovaAssets?: boolean; 7 | cordovaMock?: boolean; 8 | consolelogs?: boolean; 9 | consolelogsPort?: number; 10 | } 11 | -------------------------------------------------------------------------------- /packages/cordova-builders/cordova-build/schema.json: -------------------------------------------------------------------------------- 1 | { 2 | "title": "Cordova build for Ionic", 3 | "description": "Options for Cordova build for Ionic.", 4 | "type": "object", 5 | "properties": { 6 | "browserTarget": { 7 | "type": "string", 8 | "description": "Target to build." 9 | }, 10 | "consolelogs": { 11 | "type": "boolean", 12 | "description": "Inject script to print console logs to the terminal." 13 | }, 14 | "consolelogsPort": { 15 | "type": "number", 16 | "description": "Port for console log server.", 17 | "default": 53703 18 | }, 19 | "platform": { 20 | "type": "string", 21 | "description": "Cordova platform to use during build." 22 | }, 23 | "cordovaBasePath": { 24 | "type": "string", 25 | "description": "Path to cordova directory" 26 | }, 27 | "sourceMap": { 28 | "type": "boolean", 29 | "description": "Create source-map file" 30 | }, 31 | "cordovaAssets": { 32 | "type": "boolean", 33 | "description": "Bundle Cordova assets with build", 34 | "default": true 35 | }, 36 | "cordovaMock": { 37 | "type": "boolean", 38 | "description": "Bundle empty cordova.js with build", 39 | "default": false 40 | } 41 | }, 42 | "additionalProperties": false, 43 | "required": ["browserTarget"] 44 | } 45 | -------------------------------------------------------------------------------- /packages/cordova-builders/cordova-serve/index.ts: -------------------------------------------------------------------------------- 1 | import { createBuilder, targetFromTargetString } from '@angular-devkit/architect'; 2 | import type { BuilderContext } from '@angular-devkit/architect'; 3 | import { executeDevServerBuilder } from '@angular-devkit/build-angular'; 4 | import type { 5 | ExecutionTransformer, 6 | DevServerBuilderOptions, 7 | DevServerBuilderOutput, 8 | } from '@angular-devkit/build-angular'; 9 | import { ScriptsWebpackPlugin } from '@angular-devkit/build-angular/src/tools/webpack/plugins'; 10 | import type { json } from '@angular-devkit/core'; 11 | import * as CopyWebpackPlugin from 'copy-webpack-plugin'; 12 | import { basename } from 'path'; 13 | import { from } from 'rxjs'; 14 | import type { Observable } from 'rxjs'; 15 | import { switchMap } from 'rxjs/operators'; 16 | import type { Configuration } from 'webpack'; 17 | 18 | import { prepareServerConfig } from '../utils'; 19 | import type { FormattedAssets } from '../utils'; 20 | import { augmentIndexHtml } from '../utils/append-scripts'; 21 | 22 | import { createConsoleLogServer } from './log-server'; 23 | import type { CordovaServeBuilderSchema } from './schema'; 24 | 25 | export type CordovaDevServerBuilderOptions = CordovaServeBuilderSchema & json.JsonObject; 26 | 27 | export function serveCordova( 28 | options: CordovaServeBuilderSchema, 29 | context: BuilderContext 30 | ): Observable { 31 | const { devServerTarget, port, host, ssl } = options; 32 | const root = context.workspaceRoot; 33 | const devServerTargetSpec = targetFromTargetString(devServerTarget); 34 | 35 | async function setup() { 36 | const devServerTargetOptions = await context.getTargetOptions(devServerTargetSpec); 37 | const devServerName = await context.getBuilderNameForTarget(devServerTargetSpec); 38 | 39 | devServerTargetOptions.port = port; 40 | devServerTargetOptions.host = host; 41 | devServerTargetOptions.ssl = ssl; 42 | 43 | const formattedOptions = await context.validateOptions(devServerTargetOptions, devServerName); 44 | const formattedAssets = prepareServerConfig(options, root); 45 | if (options.consolelogs && options.consolelogsPort) { 46 | await createConsoleLogServer(host, options.consolelogsPort); 47 | } 48 | return { formattedOptions, formattedAssets }; 49 | } 50 | 51 | return from(setup()).pipe( 52 | switchMap( 53 | ({ formattedOptions, formattedAssets }) => 54 | executeDevServerBuilder( 55 | formattedOptions as unknown as DevServerBuilderOptions, 56 | context as any, 57 | getTransforms(formattedAssets, context) as any 58 | ) as unknown as Observable 59 | ) 60 | ); 61 | } 62 | export default createBuilder(serveCordova); 63 | 64 | function getTransforms(formattedAssets: FormattedAssets, context: BuilderContext) { 65 | return { 66 | webpackConfiguration: cordovaServeTransform(formattedAssets, context), 67 | indexHtml: indexHtmlTransformFactory(formattedAssets, context), 68 | }; 69 | } 70 | 71 | const cordovaServeTransform: ( 72 | formattedAssets: FormattedAssets, 73 | context: BuilderContext 74 | ) => ExecutionTransformer = 75 | (formattedAssets, { workspaceRoot }) => 76 | (browserWebpackConfig) => { 77 | const scriptExtras = formattedAssets.globalScriptsByBundleName.map((script: { bundleName: any; paths: any }) => { 78 | const bundleName = script.bundleName; 79 | return new ScriptsWebpackPlugin({ 80 | name: bundleName, 81 | sourceMap: true, 82 | filename: `${basename(bundleName)}.js`, 83 | scripts: script.paths, 84 | basePath: workspaceRoot, 85 | }); 86 | }); 87 | 88 | const copyWebpackPluginInstance = new CopyWebpackPlugin({ 89 | patterns: formattedAssets.copyWebpackPluginPatterns, 90 | }); 91 | 92 | // eslint-disable-next-line @typescript-eslint/no-non-null-assertion 93 | (browserWebpackConfig.plugins as any)?.push(...scriptExtras, copyWebpackPluginInstance); 94 | return browserWebpackConfig; 95 | }; 96 | 97 | export const indexHtmlTransformFactory: ( 98 | formattedAssets: FormattedAssets, 99 | context: BuilderContext 100 | ) => (content: string) => Promise = 101 | ({ globalScriptsByBundleName }) => 102 | (indexTransform: string) => { 103 | const augmentedHtml = augmentIndexHtml(indexTransform, globalScriptsByBundleName); 104 | return Promise.resolve(augmentedHtml); 105 | }; -------------------------------------------------------------------------------- /packages/cordova-builders/cordova-serve/log-server.ts: -------------------------------------------------------------------------------- 1 | import { red, reset, yellow } from 'colorette'; 2 | import * as util from 'util'; 3 | import * as WebSocket from 'ws'; 4 | 5 | export interface ConsoleLogServerMessage { 6 | category: 'console'; 7 | type: string; 8 | data: any[]; 9 | } 10 | 11 | export interface ConsoleLogServerOptions { 12 | consolelogs: boolean; 13 | consolelogsPort: number; 14 | } 15 | 16 | export function isConsoleLogServerMessage(m: any): m is ConsoleLogServerMessage { 17 | return ( 18 | m && typeof m.category === 'string' && typeof m.type === 'string' && m.data && typeof m.data.length === 'number' 19 | ); 20 | } 21 | 22 | export async function createConsoleLogServer(host: string, port: number): Promise { 23 | const wss = new WebSocket.Server({ host, port }); 24 | 25 | wss.on('connection', (ws) => { 26 | ws.on('message', (data) => { 27 | let msg: { category: string; type: string; data: any[] }; 28 | try { 29 | msg = JSON.parse(data.toString()); 30 | } catch (e: any) { 31 | process.stderr.write(`Error parsing JSON message from client: "${data}" ${red(e.stack ? e.stack : e)}\n`); 32 | return; 33 | } 34 | 35 | if (!isConsoleLogServerMessage(msg)) { 36 | const m = util.inspect(msg, { colors: true }); 37 | process.stderr.write(`Bad format in client message: ${m}\n`); 38 | return; 39 | } 40 | 41 | if (msg.category === 'console') { 42 | let status: ((_: string) => string) | undefined; 43 | 44 | if (msg.type === 'info' || msg.type === 'log') { 45 | status = reset; 46 | } else if (msg.type === 'error') { 47 | status = red; 48 | } else if (msg.type === 'warn') { 49 | status = yellow; 50 | } 51 | 52 | // pretty print objects and arrays (no newlines for arrays) 53 | msg.data = msg.data.map((d) => JSON.stringify(d, undefined, d?.length ? '' : ' ')); 54 | if (status) { 55 | process.stdout.write(`[${status('console.' + msg.type)}]: ${msg.data.join(' ')}\n`); 56 | } else { 57 | process.stdout.write(`[console]: ${msg.data.join(' ')}\n`); 58 | } 59 | } 60 | }); 61 | 62 | ws.on('error', (err: NodeJS.ErrnoException) => { 63 | if (err && err.code !== 'ECONNRESET') { 64 | process.stderr.write(`There was an error with the logging stream: ${JSON.stringify(err)}\n`); 65 | } 66 | }); 67 | }); 68 | 69 | wss.on('error', (err: NodeJS.ErrnoException) => { 70 | process.stderr.write(`There was an error with the logging websocket: ${JSON.stringify(err)}\n`); 71 | }); 72 | 73 | return wss; 74 | } 75 | -------------------------------------------------------------------------------- /packages/cordova-builders/cordova-serve/schema.d.ts: -------------------------------------------------------------------------------- 1 | export interface CordovaServeBuilderSchema { 2 | cordovaBuildTarget: string; 3 | devServerTarget: string; 4 | platform?: string; 5 | port: number; 6 | host: string; 7 | ssl: boolean; 8 | cordovaBasePath?: string; 9 | sourceMap?: boolean; 10 | cordovaAssets?: boolean; 11 | cordovaMock?: boolean; 12 | consolelogs?: boolean; 13 | consolelogsPort?: number; 14 | } 15 | -------------------------------------------------------------------------------- /packages/cordova-builders/cordova-serve/schema.json: -------------------------------------------------------------------------------- 1 | { 2 | "title": "Cordova serve for Ionic", 3 | "description": "Options for Cordova serve for Ionic.", 4 | "type": "object", 5 | "properties": { 6 | "cordovaBuildTarget": { 7 | "type": "string", 8 | "description": "Target to use for build." 9 | }, 10 | "consolelogs": { 11 | "type": "boolean", 12 | "description": "Print console logs to the terminal." 13 | }, 14 | "consolelogsPort": { 15 | "type": "number", 16 | "description": "Port for console log server.", 17 | "default": 53703 18 | }, 19 | "devServerTarget": { 20 | "type": "string", 21 | "description": "Target to use for serve." 22 | }, 23 | "platform": { 24 | "type": "string", 25 | "description": "Cordova platform to use during serve." 26 | }, 27 | "ssl": { 28 | "type": "boolean", 29 | "description": "Serve using HTTPS.", 30 | "default": false 31 | }, 32 | "port": { 33 | "type": "number", 34 | "description": "Port to listen on.", 35 | "default": 4200 36 | }, 37 | "host": { 38 | "type": "string", 39 | "description": "Host to listen on.", 40 | "default": "localhost" 41 | }, 42 | "cordovaBasePath": { 43 | "type": "string", 44 | "description": "Path to cordova directory" 45 | }, 46 | "sourceMap": { 47 | "type": "boolean", 48 | "description": "Create source-map file" 49 | }, 50 | "cordovaAssets": { 51 | "type": "boolean", 52 | "description": "Bundle Cordova assets with build", 53 | "default": true 54 | }, 55 | "cordovaMock": { 56 | "type": "boolean", 57 | "description": "Bundle empty cordova.js with build", 58 | "default": false 59 | } 60 | }, 61 | "additionalProperties": false, 62 | "required": ["cordovaBuildTarget", "devServerTarget"] 63 | } 64 | -------------------------------------------------------------------------------- /packages/cordova-builders/lint-staged.config.js: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ionic-team/angular-toolkit/2146fa2f9337451289d6fd412c2d8595778b2e54/packages/cordova-builders/lint-staged.config.js -------------------------------------------------------------------------------- /packages/cordova-builders/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "@ionic/cordova-builders", 3 | "version": "12.2.0", 4 | "license": "MIT", 5 | "description": "Cordova builders for @ionic/angular apps", 6 | "homepage": "https://ionicframework.com/", 7 | "author": "Ionic Team (https://ionicframework.com)", 8 | "scripts": { 9 | "build": "tsc", 10 | "lint": "true", 11 | "watch": "tsc -w", 12 | "prepublishOnly": "npm run build" 13 | }, 14 | "repository": { 15 | "type": "git", 16 | "url": "https://github.com/ionic-team/angular-toolkit.git" 17 | }, 18 | "bugs": { 19 | "url": "https://github.com/ionic-team/angular-toolkit/issues" 20 | }, 21 | "eslintConfig": { 22 | "extends": [ 23 | "../../package.json" 24 | ] 25 | }, 26 | "keywords": [ 27 | "angular", 28 | "Angular CLI", 29 | "blueprints", 30 | "code generation", 31 | "devkit", 32 | "schematics", 33 | "ionic", 34 | "ionic framework", 35 | "ionicframework" 36 | ], 37 | "dependencies": { 38 | "@angular-devkit/architect": "^0.1900.0", 39 | "@angular-devkit/build-angular": "^19.0.0", 40 | "@angular-devkit/core": "^19.0.0", 41 | "@angular-devkit/schematics": "^19.0.0", 42 | "@schematics/angular": "^19.0.0", 43 | "cheerio": "^1.0.0-rc.10", 44 | "colorette": "^2.0.16", 45 | "copy-webpack-plugin": "^12.0.2", 46 | "ws": "^8.2.3" 47 | }, 48 | "devDependencies": { 49 | "@angular-eslint/builder": "^19.0.0", 50 | "@angular-eslint/eslint-plugin": "^19.0.0", 51 | "@types/node": "^18.19 || ^20.11 || ^22.0.0", 52 | "@types/ws": "^8.5.12", 53 | "lint-staged": "^15.2.9", 54 | "typescript": ">=5.5.0 <5.9.0" 55 | }, 56 | "builders": "./builders.json", 57 | "schematics": "./collection.json", 58 | "gitHead": "35dbfaa5d93cb165387321a3f8a0f43e604f5d02" 59 | } 60 | -------------------------------------------------------------------------------- /packages/cordova-builders/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "../../tsconfig.json", 3 | "include": ["**/*.ts"], 4 | "exclude": ["**/*.spec.ts"] 5 | } 6 | -------------------------------------------------------------------------------- /packages/cordova-builders/utils/append-scripts.ts: -------------------------------------------------------------------------------- 1 | import { load } from 'cheerio'; 2 | 3 | import type { GlobalScriptsByBundleName } from '.'; 4 | 5 | export function augmentIndexHtml(indexString: string, scripts: GlobalScriptsByBundleName[]): string { 6 | const $ = load(indexString); 7 | for (const script of scripts) { 8 | $('html').append(``); 9 | } 10 | const final = $.html(); 11 | return final; 12 | } 13 | -------------------------------------------------------------------------------- /packages/cordova-builders/utils/config.ts: -------------------------------------------------------------------------------- 1 | import type { Tree } from '@angular-devkit/schematics'; 2 | import { SchematicsException } from '@angular-devkit/schematics'; 3 | 4 | const CONFIG_PATH = 'angular.json'; 5 | 6 | export function getDefaultAngularAppName(config: any): string { 7 | const projects = config.projects; 8 | const projectNames = Object.keys(projects); 9 | 10 | for (const projectName of projectNames) { 11 | const projectConfig = projects[projectName]; 12 | if (isAngularBrowserProject(projectConfig)) { 13 | return projectName; 14 | } 15 | } 16 | 17 | return projectNames[0]; 18 | } 19 | 20 | export function addArchitectBuilder( 21 | host: Tree, 22 | projectName: string, 23 | builderName: string, 24 | builderOpts: any 25 | ): void | never { 26 | const config = readConfig(host); 27 | const appConfig = getAngularAppConfig(config, projectName); 28 | appConfig.architect[builderName] = builderOpts; 29 | writeConfig(host, config); 30 | } 31 | 32 | export function readConfig(host: Tree): any { 33 | const sourceText = host.read(CONFIG_PATH)?.toString('utf-8'); 34 | if (!sourceText) { 35 | return; 36 | } 37 | return JSON.parse(sourceText); 38 | } 39 | 40 | export function writeConfig(host: Tree, config: JSON): void { 41 | host.overwrite(CONFIG_PATH, JSON.stringify(config, null, 2)); 42 | } 43 | 44 | function isAngularBrowserProject(projectConfig: any): boolean { 45 | if (projectConfig.projectType === 'application') { 46 | const buildConfig = projectConfig.architect.build; 47 | return buildConfig.builder === '@angular-devkit/build-angular:browser'; 48 | } 49 | 50 | return false; 51 | } 52 | 53 | export function getAngularAppConfig(config: any, projectName: string): any | never { 54 | // eslint-disable-next-line no-prototype-builtins 55 | if (!config.projects.hasOwnProperty(projectName)) { 56 | throw new SchematicsException(`Could not find project: ${projectName}`); 57 | } 58 | 59 | const projectConfig = config.projects[projectName]; 60 | if (isAngularBrowserProject(projectConfig)) { 61 | return projectConfig; 62 | } 63 | 64 | if (config.projectType !== 'application') { 65 | throw new SchematicsException(`Invalid projectType for ${projectName}: ${config.projectType}`); 66 | } else { 67 | const buildConfig = projectConfig.architect.build; 68 | throw new SchematicsException(`Invalid builder for ${projectName}: ${buildConfig.builder}`); 69 | } 70 | } 71 | -------------------------------------------------------------------------------- /packages/cordova-builders/utils/index.ts: -------------------------------------------------------------------------------- 1 | import type { AssetPatternClass } from '@angular-devkit/build-angular/src/builders/browser/schema'; 2 | import { normalizeExtraEntryPoints } from '@angular-devkit/build-angular/src/tools/webpack/utils/helpers'; 3 | import type { JsonObject } from '@angular-devkit/core'; 4 | import { getSystemPath, join, normalize } from '@angular-devkit/core'; 5 | import { writeFileSync } from 'fs'; 6 | import { posix, resolve } from 'path'; 7 | 8 | import type { CordovaBuildBuilderSchema } from '../cordova-build/schema'; 9 | import type { CordovaServeBuilderSchema } from '../cordova-serve/schema'; 10 | 11 | export function validateBuilderConfig(builderOptions: CordovaBuildBuilderSchema): CordovaBuildBuilderSchema { 12 | // if we're mocking cordova.js, don't build cordova bundle 13 | const newOptions = { ...builderOptions }; 14 | if (newOptions.cordovaMock) { 15 | newOptions.cordovaAssets = true; 16 | } 17 | 18 | if (builderOptions.cordovaAssets && !builderOptions.platform) { 19 | throw new Error('The `--platform` option is required with `--cordova-assets`'); 20 | } 21 | return newOptions; 22 | } 23 | 24 | export function prepareBrowserConfig( 25 | options: CordovaBuildBuilderSchema | CordovaServeBuilderSchema | any, 26 | browserOptions: any 27 | ): JsonObject { 28 | const optionsStarter = { ...browserOptions }; 29 | const cordovaBasePath = normalize(options.cordovaBasePath ? options.cordovaBasePath : '.'); 30 | 31 | if (typeof options.sourceMap !== 'undefined') { 32 | optionsStarter.sourceMap = options.sourceMap; 33 | } 34 | 35 | // We always need to output the build to `www` because it is a hard 36 | // requirement of Cordova. 37 | if ('outputPath' in options) { 38 | optionsStarter.outputPath = join(cordovaBasePath, normalize('www')); 39 | } 40 | 41 | // Cordova CLI will error if `www` is missing. The Angular CLI deletes it 42 | // by default. Let's keep it around. 43 | if ('deleteOutputPath' in options) { 44 | optionsStarter.deleteOutputPath = false; 45 | } 46 | 47 | // Initialize an empty script array to make sure assets are pushed even when 48 | // scripts is not configured in angular.json 49 | if (!optionsStarter.scripts) { 50 | optionsStarter.scripts = []; 51 | } 52 | 53 | if (options.consolelogs) { 54 | // Write the config to a file, and then include that in the bundle so it loads on window 55 | const configPath = getSystemPath(join(normalize(__dirname), '../assets', normalize('consolelog-config.js'))); 56 | writeFileSync( 57 | configPath, 58 | `window.Ionic = window.Ionic || {}; Ionic.ConsoleLogServerConfig = { wsPort: ${options.consolelogsPort} }` 59 | ); 60 | optionsStarter.scripts.push({ 61 | input: configPath, 62 | bundleName: 'consolelogs', 63 | }); 64 | optionsStarter.scripts.push({ 65 | input: getSystemPath(join(normalize(__dirname), '../assets', normalize('consolelogs.js'))), 66 | bundleName: 'consolelogs', 67 | }); 68 | } 69 | 70 | if (options.cordovaMock) { 71 | if (browserOptions.scripts) { 72 | browserOptions.scripts.push({ 73 | input: getSystemPath(join(normalize(__dirname), '../assets', normalize('cordova.js'))), 74 | bundleName: 'cordova', 75 | }); 76 | } 77 | } else if (options.cordovaAssets) { 78 | const platformWWWPath = join(cordovaBasePath, normalize(`platforms/${options.platform}/platform_www`)); 79 | 80 | // Add Cordova www assets that were generated whenever platform(s) and 81 | // plugin(s) are added. This includes `cordova.js`, 82 | // `cordova_plugins.js`, and all plugin JS. 83 | if (optionsStarter.assets) { 84 | optionsStarter.assets.push({ 85 | glob: '**/*', 86 | input: getSystemPath(platformWWWPath), 87 | output: './', 88 | }); 89 | } 90 | 91 | // Register `cordova.js` as a global script so it is included in 92 | // `index.html`. 93 | if (optionsStarter.scripts) { 94 | optionsStarter.scripts.push({ 95 | input: getSystemPath(join(platformWWWPath, normalize('cordova.js'))), 96 | bundleName: 'cordova', 97 | }); 98 | } 99 | } 100 | 101 | return optionsStarter; 102 | } 103 | 104 | export interface GlobalScriptsByBundleName { 105 | bundleName: string; 106 | paths: string[]; 107 | inject: boolean; 108 | } 109 | export interface FormattedAssets { 110 | globalScriptsByBundleName: GlobalScriptsByBundleName[]; 111 | 112 | copyWebpackPluginPatterns: any[]; 113 | } 114 | export function prepareServerConfig(options: CordovaServeBuilderSchema, root: string): FormattedAssets { 115 | const scripts = []; 116 | const assets = []; 117 | const cordovaBasePath = normalize(options.cordovaBasePath ? options.cordovaBasePath : '.'); 118 | if (options.consolelogs) { 119 | // Write the config to a file, and then include that in the bundle so it loads on window 120 | const configPath = getSystemPath(join(normalize(__dirname), '../assets', normalize('consolelog-config.js'))); 121 | writeFileSync( 122 | configPath, 123 | `window.Ionic = window.Ionic || {}; Ionic.ConsoleLogServerConfig = { wsPort: ${options.consolelogsPort} }` 124 | ); 125 | scripts.push({ input: configPath, bundleName: 'consolelogs' }); 126 | scripts.push({ 127 | input: getSystemPath(join(normalize(__dirname), '../assets', normalize('consolelogs.js'))), 128 | bundleName: 'consolelogs', 129 | }); 130 | } 131 | if (options.cordovaMock) { 132 | scripts.push({ 133 | input: getSystemPath(join(normalize(__dirname), '../assets', normalize('cordova.js'))), 134 | bundleName: 'cordova', 135 | }); 136 | } else if (options.cordovaAssets) { 137 | const platformWWWPath = join(cordovaBasePath, normalize(`platforms/${options.platform}/platform_www`)); 138 | assets.push({ 139 | glob: '**/*', 140 | input: getSystemPath(platformWWWPath), 141 | output: './', 142 | }); 143 | scripts.push({ 144 | input: getSystemPath(join(platformWWWPath, normalize('cordova.js'))), 145 | bundleName: 'cordova', 146 | }); 147 | } 148 | 149 | const globalScriptsByBundleName = normalizeExtraEntryPoints(scripts, 'scripts').reduce( 150 | (prev: { bundleName: string; paths: string[]; inject: boolean }[], curr) => { 151 | const { bundleName, inject, input } = curr; 152 | const resolvedPath = resolve(root, input); 153 | const existingEntry = prev.find((el) => el.bundleName === bundleName); 154 | if (existingEntry) { 155 | existingEntry.paths.push(resolvedPath); 156 | } else { 157 | prev.push({ 158 | bundleName, 159 | inject, 160 | paths: [resolvedPath], 161 | }); 162 | } 163 | return prev; 164 | }, 165 | [] 166 | ); 167 | 168 | const copyWebpackPluginPatterns = assets.map((asset: AssetPatternClass) => { 169 | // Resolve input paths relative to workspace root and add slash at the end. 170 | // eslint-disable-next-line prefer-const 171 | let { input, output, ignore = [], glob } = asset; 172 | input = resolve(root, input).replace(/\\/g, '/'); 173 | input = input.endsWith('/') ? input : input + '/'; 174 | output = output?.endsWith('/') ? output : (output ?? '') + '/'; 175 | 176 | return { 177 | context: input, 178 | // Now we remove starting slash to make Webpack place it from the output root. 179 | to: output.replace(/^\//, ''), 180 | from: glob, 181 | noErrorOnMissing: true, 182 | globOptions: { 183 | dot: true, 184 | ignore: [ 185 | '.gitkeep', 186 | '**/.DS_Store', 187 | '**/Thumbs.db', 188 | // Negate patterns needs to be absolute because copy-webpack-plugin uses absolute globs which 189 | // causes negate patterns not to match. 190 | // See: https://github.com/webpack-contrib/copy-webpack-plugin/issues/498#issuecomment-639327909 191 | ...ignore, 192 | ].map((i) => posix.join(input, i)), 193 | }, 194 | }; 195 | }); 196 | 197 | return { globalScriptsByBundleName, copyWebpackPluginPatterns }; 198 | } 199 | -------------------------------------------------------------------------------- /packages/schematics/.npmignore: -------------------------------------------------------------------------------- 1 | # having this file excludes .gitignore entries 2 | 3 | tsconfig.json 4 | tslint.js 5 | .circleci 6 | *.ts 7 | !*.d.ts 8 | !**/files/**/* 9 | -------------------------------------------------------------------------------- /packages/schematics/CHANGELOG.md: -------------------------------------------------------------------------------- 1 | # Change Log 2 | 3 | All notable changes to this project will be documented in this file. 4 | See [Conventional Commits](https://conventionalcommits.org) for commit guidelines. 5 | 6 | # [12.2.0](https://github.com/ionic-team/angular-toolkit/compare/@ionic/angular-toolkit@12.1.1...@ionic/angular-toolkit@12.2.0) (2025-04-10) 7 | 8 | 9 | ### Features 10 | 11 | * support angular 19 ([#520](https://github.com/ionic-team/angular-toolkit/issues/520)) ([2823580](https://github.com/ionic-team/angular-toolkit/commit/282358057bac80e3f3a3f96cec6b7d8c1380cea0)) 12 | 13 | 14 | 15 | 16 | 17 | ## [12.1.1](https://github.com/ionic-team/angular-toolkit/compare/@ionic/angular-toolkit@12.1.0...@ionic/angular-toolkit@12.1.1) (2024-09-03) 18 | 19 | 20 | ### Bug Fixes 21 | 22 | * release bump ([#514](https://github.com/ionic-team/angular-toolkit/issues/514)) ([d084c97](https://github.com/ionic-team/angular-toolkit/commit/d084c9790745163e29d2e3441345c1acf852fc90)) 23 | 24 | 25 | 26 | 27 | 28 | # [12.1.0](https://github.com/ionic-team/angular-toolkit/compare/@ionic/angular-toolkit@11.0.1...@ionic/angular-toolkit@12.1.0) (2024-09-03) 29 | 30 | 31 | ### Features 32 | 33 | * support angular 18 ([#513](https://github.com/ionic-team/angular-toolkit/issues/513)) ([70d5066](https://github.com/ionic-team/angular-toolkit/commit/70d50665a000c10b1cf5701aad664c2e620541c1)) 34 | 35 | 36 | 37 | 38 | 39 | # [12.0.0](https://github.com/ionic-team/angular-toolkit/compare/@ionic/cordova-builders@10.1.1...@ionic/cordova-builders@11.0.0) (2024-09-01) 40 | 41 | 42 | ### Features 43 | 44 | * support angular 18 ([#508](https://github.com/ionic-team/angular-toolkit/issues/508)) ([c002c51](https://github.com/ionic-team/angular-toolkit/commit/c002c51cc09f45639ca97bc5354840d9c384556c)) 45 | 46 | 47 | ### BREAKING CHANGES 48 | 49 | * The minimum version of Angular required is now 18. Please updates your apps to use 50 | the latest release of Angular. 51 | 52 | ## [11.0.1](https://github.com/ionic-team/angular-toolkit/compare/@ionic/angular-toolkit@11.0.0...@ionic/angular-toolkit@11.0.1) (2024-01-29) 53 | 54 | 55 | ### Bug Fixes 56 | 57 | * standalone component can be created ([#510](https://github.com/ionic-team/angular-toolkit/issues/510)) ([cc17ab5](https://github.com/ionic-team/angular-toolkit/commit/cc17ab51bf8b3e03a9cd18c1a8e3e527d63519ef)) 58 | 59 | 60 | 61 | 62 | 63 | # [11.0.0](https://github.com/ionic-team/angular-toolkit/compare/@ionic/angular-toolkit@10.1.1...@ionic/angular-toolkit@11.0.0) (2024-01-09) 64 | 65 | 66 | ### Features 67 | 68 | * support angular 17 ([#508](https://github.com/ionic-team/angular-toolkit/issues/508)) ([c002c51](https://github.com/ionic-team/angular-toolkit/commit/c002c51cc09f45639ca97bc5354840d9c384556c)) 69 | 70 | 71 | ### BREAKING CHANGES 72 | 73 | * The minimum version of Angular required is now 17. Please updates your apps to use 74 | the latest release of Angular. 75 | 76 | 77 | 78 | 79 | 80 | ## [10.1.1](https://github.com/ionic-team/angular-toolkit/compare/@ionic/angular-toolkit@10.1.0...@ionic/angular-toolkit@10.1.1) (2024-01-09) 81 | 82 | 83 | ### Reverts 84 | 85 | * Revert "feat: support angular 17 (#503)" ([feb6361](https://github.com/ionic-team/angular-toolkit/commit/feb6361f1452e5ccbe242b0e00c0ded05beacec4)), closes [#503](https://github.com/ionic-team/angular-toolkit/issues/503) 86 | 87 | 88 | 89 | 90 | 91 | # [10.1.0](https://github.com/ionic-team/angular-toolkit/compare/@ionic/angular-toolkit@10.0.0...@ionic/angular-toolkit@10.1.0) (2024-01-09) 92 | 93 | 94 | ### Bug Fixes 95 | 96 | * **page:** remove waitFromAsync from beforeEach ([#501](https://github.com/ionic-team/angular-toolkit/issues/501)) ([55527e7](https://github.com/ionic-team/angular-toolkit/commit/55527e786df3e19b6e16fa0ad799d0c10bec104d)) 97 | 98 | 99 | ### Features 100 | 101 | * **schematics:** use Ionic standalone components ([#504](https://github.com/ionic-team/angular-toolkit/issues/504)) ([8d6ce1c](https://github.com/ionic-team/angular-toolkit/commit/8d6ce1c4b7622ec4afa746bf8d97a87f668b61bd)), closes [#500](https://github.com/ionic-team/angular-toolkit/issues/500) 102 | * support angular 17 ([#503](https://github.com/ionic-team/angular-toolkit/issues/503)) ([26f0cd9](https://github.com/ionic-team/angular-toolkit/commit/26f0cd9a17b1489a1e864bb468f4e51315d4a004)), closes [#502](https://github.com/ionic-team/angular-toolkit/issues/502) 103 | 104 | 105 | 106 | 107 | 108 | # [10.0.0](https://github.com/ionic-team/angular-toolkit/compare/@ionic/angular-toolkit@9.0.0...@ionic/angular-toolkit@10.0.0) (2023-07-06) 109 | 110 | 111 | ### Bug Fixes 112 | 113 | * remove extra parentheses with spec test ([#490](https://github.com/ionic-team/angular-toolkit/issues/490)) ([87b1018](https://github.com/ionic-team/angular-toolkit/commit/87b1018c65048f90db2ab50a486aa4c392bb1c8b)), closes [#489](https://github.com/ionic-team/angular-toolkit/issues/489) 114 | * **template:** fix page component test ([a2a4b80](https://github.com/ionic-team/angular-toolkit/commit/a2a4b80c1329202ee2c02aff9531585e0352cc7a)) 115 | 116 | 117 | ### Features 118 | 119 | * **builders:** add support for angular 16.0 ([379d8d4](https://github.com/ionic-team/angular-toolkit/commit/379d8d43d066b1cd556b083ccb506703a166ce1d)), closes [#493](https://github.com/ionic-team/angular-toolkit/issues/493) [#494](https://github.com/ionic-team/angular-toolkit/issues/494) 120 | 121 | 122 | ### BREAKING CHANGES 123 | 124 | * **builders:** The minimum version of Angular required is now 16. Please updates your apps to use 125 | the lates release of Angular 126 | 127 | 128 | 129 | 130 | 131 | # [9.0.0](https://github.com/ionic-team/angular-toolkit/compare/@ionic/angular-toolkit@8.1.0...@ionic/angular-toolkit@9.0.0) (2023-03-28) 132 | 133 | 134 | ### Features 135 | 136 | * **schematics:** bump deps ([19f1abc](https://github.com/ionic-team/angular-toolkit/commit/19f1abc79df3e037744df859e13f2c8f5bd7b985)) 137 | 138 | 139 | ### BREAKING CHANGES 140 | 141 | * **schematics:** This bumps angualr-toolkit to support version 15 of angular 142 | 143 | 144 | 145 | 146 | 147 | # [8.1.0](https://github.com/ionic-team/angular-toolkit/compare/@ionic/angular-toolkit@8.0.0...@ionic/angular-toolkit@8.1.0) (2023-03-28) 148 | 149 | 150 | ### Features 151 | 152 | * add support for standalone pages ([5523f7a](https://github.com/ionic-team/angular-toolkit/commit/5523f7a8b891b86a0db0ab7781529211cd6a9d83)) 153 | * **page:** add large header ([e018d4a](https://github.com/ionic-team/angular-toolkit/commit/e018d4ae29726d3ab2bf4cf9f372b1ca95df610b)) 154 | 155 | 156 | 157 | 158 | 159 | # [8.0.0](https://github.com/ionic-team/angular-toolkit/compare/@ionic/angular-toolkit@7.0.0...@ionic/angular-toolkit@8.0.0) (2023-02-15) 160 | 161 | 162 | ### Features 163 | 164 | * bump deps to support angular v15 ([e7fdfd8](https://github.com/ionic-team/angular-toolkit/commit/e7fdfd8581819430b549cfae4a87e9edbadf57c9)), closes [#481](https://github.com/ionic-team/angular-toolkit/issues/481) [#477](https://github.com/ionic-team/angular-toolkit/issues/477) [#482](https://github.com/ionic-team/angular-toolkit/issues/482) 165 | 166 | 167 | ### BREAKING CHANGES 168 | 169 | * Apps will need to update to Angular 15 in order to support this version 170 | 171 | 172 | 173 | 174 | 175 | # [7.0.0](https://github.com/ionic-team/angular-toolkit/compare/@ionic/angular-toolkit@6.1.0...@ionic/angular-toolkit@7.0.0) (2022-07-18) 176 | 177 | 178 | ### Bug Fixes 179 | 180 | * **schematics:** provide default path value ([07bba34](https://github.com/ionic-team/angular-toolkit/commit/07bba341490b7e9f8ce3483c3c78e4761d592cc0)), closes [#473](https://github.com/ionic-team/angular-toolkit/issues/473) 181 | 182 | 183 | ### Features 184 | 185 | * bump deps to angular 14 ([767598e](https://github.com/ionic-team/angular-toolkit/commit/767598eace5bc91767008fd86670729c8079a1d9)), closes [#474](https://github.com/ionic-team/angular-toolkit/issues/474) 186 | 187 | 188 | ### BREAKING CHANGES 189 | 190 | * Apps will need to update to Angular 14 in order to suppor this verion. 191 | 192 | 193 | 194 | 195 | 196 | # [6.1.0](https://github.com/ionic-team/angular-toolkit/compare/@ionic/angular-toolkit@6.0.0...@ionic/angular-toolkit@6.1.0) (2022-03-08) 197 | 198 | 199 | ### Features 200 | 201 | * add resolver to collections ([08375b7](https://github.com/ionic-team/angular-toolkit/commit/08375b786327c39575efba5fee88aa15f9acc126)), closes [#461](https://github.com/ionic-team/angular-toolkit/issues/461) 202 | 203 | 204 | 205 | 206 | 207 | # 6.0.0 (2022-01-18) 208 | 209 | 210 | ### Bug Fixes 211 | 212 | * turn of declarations ([c5b0ca9](https://github.com/ionic-team/angular-toolkit/commit/c5b0ca9e448f71123dedb4afd4e3dad7d365493c)) 213 | 214 | 215 | ### Features 216 | 217 | * split tooling ([#465](https://github.com/ionic-team/angular-toolkit/issues/465)) ([a8303ec](https://github.com/ionic-team/angular-toolkit/commit/a8303ec5df92c9f463ded30fbcb97a908578adf5)) 218 | 219 | 220 | ### BREAKING CHANGES 221 | 222 | * ionic/angular + cordova users will now need to install @ionic/cordova-builders and 223 | update their angular.json to reflect the new package name 224 | 225 | * chore(): fix package description 226 | 227 | 228 | 229 | 230 | 231 | ## 5.0.4-testing.4 (2021-11-17) 232 | 233 | **Note:** Version bump only for package @ionic/angular-toolkit 234 | 235 | 236 | 237 | 238 | 239 | ## 5.0.4-testing.3 (2021-11-16) 240 | 241 | **Note:** Version bump only for package @ionic/angular-toolkit 242 | 243 | 244 | 245 | 246 | 247 | ## 5.0.4-testing.2 (2021-11-16) 248 | 249 | **Note:** Version bump only for package @ionic/angular-toolkit 250 | 251 | 252 | 253 | 254 | 255 | ## 5.0.4-testing.1 (2021-11-16) 256 | 257 | **Note:** Version bump only for package @ionic/angular-toolkit 258 | 259 | 260 | 261 | 262 | 263 | ## 5.0.4-testing.0 (2021-11-16) 264 | 265 | **Note:** Version bump only for package @ionic/angular-toolkit 266 | -------------------------------------------------------------------------------- /packages/schematics/README.md: -------------------------------------------------------------------------------- 1 | # `@ionic/angular-toolkit` 2 | 3 | A collection of schematics for `@ionic/angular` projects. 4 | 5 | This packages is provided by default to all `@ionic/angular` projects by default. 6 | -------------------------------------------------------------------------------- /packages/schematics/collection.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": ["@schematics/angular"], 3 | "schematics": { 4 | "page": { 5 | "aliases": ["pg"], 6 | "factory": "./page", 7 | "description": "Create an Ionic page.", 8 | "schema": "./page/schema.json" 9 | }, 10 | "component": { 11 | "aliases": ["c"], 12 | "factory": "./component", 13 | "description": "Create an Angular component.", 14 | "schema": "./component/schema.json" 15 | } 16 | } 17 | } 18 | -------------------------------------------------------------------------------- /packages/schematics/component/files/__name@dasherize@if-flat__/__name@dasherize__.__type@dasherize__.__styleext__.template: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ionic-team/angular-toolkit/2146fa2f9337451289d6fd412c2d8595778b2e54/packages/schematics/component/files/__name@dasherize@if-flat__/__name@dasherize__.__type@dasherize__.__styleext__.template -------------------------------------------------------------------------------- /packages/schematics/component/files/__name@dasherize@if-flat__/__name@dasherize__.__type@dasherize__.html.template: -------------------------------------------------------------------------------- 1 |

2 | <%= dasherize(name) %> works! 3 |

4 | -------------------------------------------------------------------------------- /packages/schematics/component/files/__name@dasherize@if-flat__/__name@dasherize__.__type@dasherize__.spec.ts.template: -------------------------------------------------------------------------------- 1 | import { ComponentFixture, TestBed, waitForAsync } from '@angular/core/testing';<% if(!standalone) {%> 2 | import { IonicModule } from '@ionic/angular';<%} %> 3 | 4 | import { <%= classify(name) %><%= classify(type) %> } from './<%= dasherize(name) %>.<%= dasherize(type) %>'; 5 | 6 | describe('<%= classify(name) %><%= classify(type) %>', () => { 7 | let component: <%= classify(name) %><%= classify(type) %>; 8 | let fixture: ComponentFixture<<%= classify(name) %><%= classify(type) %>>; 9 | 10 | beforeEach(waitForAsync(() => { 11 | TestBed.configureTestingModule({<% if(!standalone) {%> 12 | declarations: [ <%= classify(name) %><%= classify(type) %> ], 13 | imports: [IonicModule.forRoot()]<%} %><% if(standalone) {%> 14 | imports: [<%= classify(name) %><%= classify(type) %>],<%} %> 15 | }).compileComponents(); 16 | 17 | fixture = TestBed.createComponent(<%= classify(name) %><%= classify(type) %>); 18 | component = fixture.componentInstance; 19 | fixture.detectChanges(); 20 | })); 21 | 22 | it('should create', () => { 23 | expect(component).toBeTruthy(); 24 | }); 25 | }); 26 | -------------------------------------------------------------------------------- /packages/schematics/component/files/__name@dasherize@if-flat__/__name@dasherize__.__type@dasherize__.ts.template: -------------------------------------------------------------------------------- 1 | import { Component, OnInit } from '@angular/core'; 2 | 3 | @Component({ 4 | selector: '<%= selector %>', 5 | templateUrl: './<%= dasherize(name) %>.<%= dasherize(type) %>.html', 6 | styleUrls: ['./<%= dasherize(name) %>.<%= dasherize(type) %>.<%= styleext %>'],<% if(standalone) {%> 7 | standalone: true,<%} %> 8 | }) 9 | export class <%= classify(name) %><%= classify(type) %> implements OnInit { 10 | 11 | constructor() { } 12 | 13 | ngOnInit() {} 14 | 15 | } 16 | -------------------------------------------------------------------------------- /packages/schematics/component/files/__name@dasherize@if-flat__/__name@dasherize__.module.ts.template: -------------------------------------------------------------------------------- 1 | import { NgModule } from '@angular/core'; 2 | import { CommonModule } from '@angular/common'; 3 | import { FormsModule } from '@angular/forms'; 4 | 5 | import { IonicModule } from '@ionic/angular'; 6 | 7 | import { <%= classify(name) %><%= classify(type) %> } from './<%= dasherize(name) %>.component'; 8 | 9 | @NgModule({ 10 | imports: [ CommonModule, FormsModule, IonicModule,], 11 | declarations: [<%= classify(name) %><%= classify(type) %> ], 12 | exports: [<%= classify(name) %><%= classify(type) %> ] 13 | }) 14 | export class <%= classify(name) %><%= classify(type) %> Module {} 15 | -------------------------------------------------------------------------------- /packages/schematics/component/index.ts: -------------------------------------------------------------------------------- 1 | import { strings } from '@angular-devkit/core'; 2 | import type { FileOperator, Rule, Tree } from '@angular-devkit/schematics'; 3 | import { 4 | forEach, 5 | SchematicsException, 6 | apply, 7 | branchAndMerge, 8 | chain, 9 | filter, 10 | mergeWith, 11 | move, 12 | noop, 13 | applyTemplates, 14 | url, 15 | } from '@angular-devkit/schematics'; 16 | import { buildRelativePath } from '@schematics/angular/utility/find-module'; 17 | import { parseName } from '@schematics/angular/utility/parse-name'; 18 | import { validateHtmlSelector } from '@schematics/angular/utility/validation'; 19 | import { buildDefaultPath, getWorkspace } from '@schematics/angular/utility/workspace'; 20 | import * as ts from 'typescript'; 21 | 22 | import { buildSelector } from '../util'; 23 | import { 24 | addDeclarationToModule, 25 | addEntryComponentToModule, 26 | addExportToModule, 27 | addSymbolToNgModuleMetadata, 28 | } from '../util/ast-util'; 29 | import { InsertChange } from '../util/change'; 30 | 31 | import type { Schema as ComponentOptions } from './schema'; 32 | 33 | function readIntoSourceFile(host: Tree, modulePath: string): ts.SourceFile { 34 | const text = host.read(modulePath); 35 | if (text === null) { 36 | throw new SchematicsException(`File ${modulePath} does not exist.`); 37 | } 38 | const sourceText = text.toString('utf-8'); 39 | 40 | return ts.createSourceFile(modulePath, sourceText, ts.ScriptTarget.Latest, true); 41 | } 42 | 43 | function addImportToNgModule(options: ComponentOptions): Rule { 44 | return (host: Tree) => { 45 | if (!options.module) { 46 | return host; 47 | } 48 | if (!options.createModule && options.module) { 49 | addImportToDeclarations(host, options); 50 | } 51 | if (options.createModule && options.module) { 52 | addImportToImports(host, options); 53 | } 54 | return host; 55 | }; 56 | } 57 | 58 | function addImportToDeclarations(host: Tree, options: ComponentOptions): void { 59 | if (options.module) { 60 | const modulePath = options.module; 61 | let source = readIntoSourceFile(host, modulePath); 62 | 63 | const componentPath = 64 | `/${options.path}/` + 65 | (options.flat ? '' : strings.dasherize(options.name) + '/') + 66 | strings.dasherize(options.name) + 67 | '.component'; 68 | const relativePath = buildRelativePath(modulePath, componentPath); 69 | const classifiedName = strings.classify(`${options.name}Component`); 70 | const declarationChanges = addDeclarationToModule(source, modulePath, classifiedName, relativePath); 71 | 72 | const declarationRecorder = host.beginUpdate(modulePath); 73 | for (const change of declarationChanges) { 74 | if (change instanceof InsertChange) { 75 | declarationRecorder.insertLeft(change.pos, change.toAdd); 76 | } 77 | } 78 | host.commitUpdate(declarationRecorder); 79 | 80 | if (options.export) { 81 | // Need to refresh the AST because we overwrote the file in the host. 82 | source = readIntoSourceFile(host, modulePath); 83 | 84 | const exportRecorder = host.beginUpdate(modulePath); 85 | const exportChanges = addExportToModule( 86 | source, 87 | modulePath, 88 | strings.classify(`${options.name}Component`), 89 | relativePath 90 | ); 91 | 92 | for (const change of exportChanges) { 93 | if (change instanceof InsertChange) { 94 | exportRecorder.insertLeft(change.pos, change.toAdd); 95 | } 96 | } 97 | host.commitUpdate(exportRecorder); 98 | } 99 | 100 | if (options.entryComponent) { 101 | // Need to refresh the AST because we overwrote the file in the host. 102 | source = readIntoSourceFile(host, modulePath); 103 | 104 | const entryComponentRecorder = host.beginUpdate(modulePath); 105 | const entryComponentChanges = addEntryComponentToModule( 106 | source, 107 | modulePath, 108 | strings.classify(`${options.name}Component`), 109 | relativePath 110 | ); 111 | 112 | for (const change of entryComponentChanges) { 113 | if (change instanceof InsertChange) { 114 | entryComponentRecorder.insertLeft(change.pos, change.toAdd); 115 | } 116 | } 117 | host.commitUpdate(entryComponentRecorder); 118 | } 119 | } 120 | } 121 | 122 | function addImportToImports(host: Tree, options: ComponentOptions): void { 123 | if (options.module) { 124 | const modulePath = options.module; 125 | const moduleSource = readIntoSourceFile(host, modulePath); 126 | 127 | const componentModulePath = 128 | `/${options.path}/` + 129 | (options.flat ? '' : strings.dasherize(options.name) + '/') + 130 | strings.dasherize(options.name) + 131 | '.module'; 132 | 133 | const relativePath = buildRelativePath(modulePath, componentModulePath); 134 | const classifiedName = strings.classify(`${options.name}ComponentModule`); 135 | const importChanges = addSymbolToNgModuleMetadata( 136 | moduleSource, 137 | modulePath, 138 | 'imports', 139 | classifiedName, 140 | relativePath 141 | ); 142 | 143 | const importRecorder = host.beginUpdate(modulePath); 144 | for (const change of importChanges) { 145 | if (change instanceof InsertChange) { 146 | importRecorder.insertLeft(change.pos, change.toAdd); 147 | } 148 | } 149 | host.commitUpdate(importRecorder); 150 | } 151 | } 152 | 153 | export default function (options: ComponentOptions): Rule { 154 | return async (host: Tree) => { 155 | const workspace = await getWorkspace(host); 156 | const project = workspace.projects.get(options.project as string); 157 | 158 | if (!project) { 159 | throw new SchematicsException(`Project "${options.project}" does not exist.`); 160 | } 161 | 162 | if (project && options.path === undefined) { 163 | options.path = buildDefaultPath(project); 164 | } 165 | 166 | const parsedPath = parseName(options.path as string, options.name); 167 | options.name = parsedPath.name; 168 | options.path = parsedPath.path; 169 | options.selector = options.selector ? options.selector : buildSelector(options, project?.prefix ?? 'app'); 170 | 171 | validateHtmlSelector(options.selector); 172 | 173 | const templateSource = apply(url('./files'), [ 174 | options.spec ? noop() : filter((p) => !p.endsWith('.spec.ts.template')), 175 | options.createModule ? noop() : filter((p) => !p.endsWith('.module.ts.template')), 176 | applyTemplates({ 177 | ...strings, 178 | 'if-flat': (s: string) => (options.flat ? '' : s), 179 | ...options, 180 | }), 181 | !options.type 182 | ? forEach(((file) => { 183 | return file.path.includes('..') 184 | ? { 185 | content: file.content, 186 | path: file.path.replace('..', '.'), 187 | } 188 | : file; 189 | }) as FileOperator) 190 | : noop(), 191 | move(parsedPath.path), 192 | ]); 193 | 194 | return chain([branchAndMerge(chain([addImportToNgModule(options), mergeWith(templateSource)]))]); 195 | }; 196 | } 197 | -------------------------------------------------------------------------------- /packages/schematics/component/schema.d.ts: -------------------------------------------------------------------------------- 1 | export interface Schema { 2 | path?: string; 3 | project?: string; 4 | name: string; 5 | prefix?: string; 6 | styleext?: string; 7 | spec?: boolean; 8 | flat?: boolean; 9 | selector?: string; 10 | createModule?: boolean; 11 | module?: string; 12 | export?: boolean; 13 | entryComponent?: boolean; 14 | type?: string; 15 | standalone?: boolean; 16 | } 17 | -------------------------------------------------------------------------------- /packages/schematics/component/schema.json: -------------------------------------------------------------------------------- 1 | { 2 | "$schema": "http://json-schema.org/schema", 3 | "$id": "SchematicsIonicAngularComponent", 4 | "title": "@ionic/angular Component Options Schema", 5 | "type": "object", 6 | "properties": { 7 | "path": { 8 | "type": "string", 9 | "format": "path", 10 | "description": "The path to create the page", 11 | "visible": false, 12 | "$default": { 13 | "$source": "workingDirectory" 14 | } 15 | }, 16 | "project": { 17 | "type": "string", 18 | "description": "The name of the project", 19 | "$default": { 20 | "$source": "projectName" 21 | } 22 | }, 23 | "name": { 24 | "type": "string", 25 | "description": "The name of the page", 26 | "$default": { 27 | "$source": "argv", 28 | "index": 0 29 | } 30 | }, 31 | "prefix": { 32 | "type": "string", 33 | "description": "The prefix to apply to generated selectors", 34 | "alias": "p", 35 | "oneOf": [ 36 | { 37 | "maxLength": 0 38 | }, 39 | { 40 | "minLength": 1, 41 | "format": "html-selector" 42 | } 43 | ] 44 | }, 45 | "styleext": { 46 | "type": "string", 47 | "description": "The file extension of the style file for the page", 48 | "default": "css" 49 | }, 50 | "spec": { 51 | "type": "boolean", 52 | "description": "Specifies if a spec file is generated", 53 | "default": true 54 | }, 55 | "flat": { 56 | "type": "boolean", 57 | "description": "Flag to indicate if a dir is created", 58 | "default": false 59 | }, 60 | "selector": { 61 | "type": "string", 62 | "format": "html-selector", 63 | "description": "The selector to use for the page" 64 | }, 65 | "createModule": { 66 | "type": "boolean", 67 | "description": "Allows creating an NgModule for the component", 68 | "default": false 69 | }, 70 | "module": { 71 | "type": "string", 72 | "description": "Allows adding to an NgModule's imports or declarations" 73 | }, 74 | "export": { 75 | "type": "boolean", 76 | "default": false, 77 | "description": "When true, the declaring NgModule exports this component." 78 | }, 79 | "entryComponent": { 80 | "type": "boolean", 81 | "default": false, 82 | "description": "When true, the new component is the entry component of the declaring NgModule." 83 | }, 84 | "type": { 85 | "type": "string", 86 | "description": "Adds a developer-defined type to the filename, in the format \"name.type.ts\".", 87 | "default": "Component" 88 | }, 89 | "standalone": { 90 | "type": "boolean", 91 | "description": "Specifies if the component should be standalone", 92 | "default": false 93 | } 94 | }, 95 | "required": [] 96 | } 97 | -------------------------------------------------------------------------------- /packages/schematics/lint-staged.config.js: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ionic-team/angular-toolkit/2146fa2f9337451289d6fd412c2d8595778b2e54/packages/schematics/lint-staged.config.js -------------------------------------------------------------------------------- /packages/schematics/package-lock.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "@ionic/angular-toolkit", 3 | "version": "12.2.0", 4 | "lockfileVersion": 3, 5 | "requires": true, 6 | "packages": { 7 | "": { 8 | "name": "@ionic/angular-toolkit", 9 | "version": "12.1.1", 10 | "license": "MIT", 11 | "devDependencies": { 12 | "lint-staged": "^12.0.2" 13 | } 14 | }, 15 | "node_modules/lint-staged": { 16 | "version": "12.5.0", 17 | "resolved": "https://registry.npmjs.org/lint-staged/-/lint-staged-12.5.0.tgz", 18 | "integrity": "sha512-BKLUjWDsKquV/JuIcoQW4MSAI3ggwEImF1+sB4zaKvyVx1wBk3FsG7UK9bpnmBTN1pm7EH2BBcMwINJzCRv12g==", 19 | "dev": true, 20 | "license": "MIT", 21 | "dependencies": { 22 | "cli-truncate": "^3.1.0", 23 | "colorette": "^2.0.16", 24 | "commander": "^9.3.0", 25 | "debug": "^4.3.4", 26 | "execa": "^5.1.1", 27 | "lilconfig": "2.0.5", 28 | "listr2": "^4.0.5", 29 | "micromatch": "^4.0.5", 30 | "normalize-path": "^3.0.0", 31 | "object-inspect": "^1.12.2", 32 | "pidtree": "^0.5.0", 33 | "string-argv": "^0.3.1", 34 | "supports-color": "^9.2.2", 35 | "yaml": "^1.10.2" 36 | }, 37 | "bin": { 38 | "lint-staged": "bin/lint-staged.js" 39 | }, 40 | "engines": { 41 | "node": "^12.20.0 || ^14.13.1 || >=16.0.0" 42 | }, 43 | "funding": { 44 | "url": "https://opencollective.com/lint-staged" 45 | } 46 | }, 47 | "node_modules/lint-staged/node_modules/aggregate-error": { 48 | "version": "3.1.0", 49 | "resolved": "https://registry.npmjs.org/aggregate-error/-/aggregate-error-3.1.0.tgz", 50 | "integrity": "sha512-4I7Td01quW/RpocfNayFdFVk1qSuoh0E7JrbRJ16nH01HhKFQ88INq9Sd+nd72zqRySlr9BmDA8xlEJ6vJMrYA==", 51 | "dev": true, 52 | "license": "MIT", 53 | "dependencies": { 54 | "clean-stack": "^2.0.0", 55 | "indent-string": "^4.0.0" 56 | }, 57 | "engines": { 58 | "node": ">=8" 59 | } 60 | }, 61 | "node_modules/lint-staged/node_modules/ansi-escapes": { 62 | "version": "4.3.2", 63 | "resolved": "https://registry.npmjs.org/ansi-escapes/-/ansi-escapes-4.3.2.tgz", 64 | "integrity": "sha512-gKXj5ALrKWQLsYG9jlTRmR/xKluxHV+Z9QEwNIgCfM1/uwPMCuzVVnh5mwTd+OuBZcwSIMbqssNWRm1lE51QaQ==", 65 | "dev": true, 66 | "license": "MIT", 67 | "dependencies": { 68 | "type-fest": "^0.21.3" 69 | }, 70 | "engines": { 71 | "node": ">=8" 72 | }, 73 | "funding": { 74 | "url": "https://github.com/sponsors/sindresorhus" 75 | } 76 | }, 77 | "node_modules/lint-staged/node_modules/ansi-regex": { 78 | "version": "6.1.0", 79 | "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-6.1.0.tgz", 80 | "integrity": "sha512-7HSX4QQb4CspciLpVFwyRe79O3xsIZDDLER21kERQ71oaPodF8jL725AgJMFAYbooIqolJoRLuM81SpeUkpkvA==", 81 | "dev": true, 82 | "license": "MIT", 83 | "engines": { 84 | "node": ">=12" 85 | }, 86 | "funding": { 87 | "url": "https://github.com/chalk/ansi-regex?sponsor=1" 88 | } 89 | }, 90 | "node_modules/lint-staged/node_modules/ansi-styles": { 91 | "version": "6.2.1", 92 | "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-6.2.1.tgz", 93 | "integrity": "sha512-bN798gFfQX+viw3R7yrGWRqnrN2oRkEkUjjl4JNn4E8GxxbjtG3FbrEIIY3l8/hrwUwIeCZvi4QuOTP4MErVug==", 94 | "dev": true, 95 | "license": "MIT", 96 | "engines": { 97 | "node": ">=12" 98 | }, 99 | "funding": { 100 | "url": "https://github.com/chalk/ansi-styles?sponsor=1" 101 | } 102 | }, 103 | "node_modules/lint-staged/node_modules/astral-regex": { 104 | "version": "2.0.0", 105 | "resolved": "https://registry.npmjs.org/astral-regex/-/astral-regex-2.0.0.tgz", 106 | "integrity": "sha512-Z7tMw1ytTXt5jqMcOP+OQteU1VuNK9Y02uuJtKQ1Sv69jXQKKg5cibLwGJow8yzZP+eAc18EmLGPal0bp36rvQ==", 107 | "dev": true, 108 | "license": "MIT", 109 | "engines": { 110 | "node": ">=8" 111 | } 112 | }, 113 | "node_modules/lint-staged/node_modules/braces": { 114 | "version": "3.0.3", 115 | "resolved": "https://registry.npmjs.org/braces/-/braces-3.0.3.tgz", 116 | "integrity": "sha512-yQbXgO/OSZVD2IsiLlro+7Hf6Q18EJrKSEsdoMzKePKXct3gvD8oLcOQdIzGupr5Fj+EDe8gO/lxc1BzfMpxvA==", 117 | "dev": true, 118 | "license": "MIT", 119 | "dependencies": { 120 | "fill-range": "^7.1.1" 121 | }, 122 | "engines": { 123 | "node": ">=8" 124 | } 125 | }, 126 | "node_modules/lint-staged/node_modules/clean-stack": { 127 | "version": "2.2.0", 128 | "resolved": "https://registry.npmjs.org/clean-stack/-/clean-stack-2.2.0.tgz", 129 | "integrity": "sha512-4diC9HaTE+KRAMWhDhrGOECgWZxoevMc5TlkObMqNSsVU62PYzXZ/SMTjzyGAFF1YusgxGcSWTEXBhp0CPwQ1A==", 130 | "dev": true, 131 | "license": "MIT", 132 | "engines": { 133 | "node": ">=6" 134 | } 135 | }, 136 | "node_modules/lint-staged/node_modules/cli-cursor": { 137 | "version": "3.1.0", 138 | "resolved": "https://registry.npmjs.org/cli-cursor/-/cli-cursor-3.1.0.tgz", 139 | "integrity": "sha512-I/zHAwsKf9FqGoXM4WWRACob9+SNukZTd94DWF57E4toouRulbCxcUh6RKUEOQlYTHJnzkPMySvPNaaSLNfLZw==", 140 | "dev": true, 141 | "license": "MIT", 142 | "dependencies": { 143 | "restore-cursor": "^3.1.0" 144 | }, 145 | "engines": { 146 | "node": ">=8" 147 | } 148 | }, 149 | "node_modules/lint-staged/node_modules/cli-truncate": { 150 | "version": "3.1.0", 151 | "resolved": "https://registry.npmjs.org/cli-truncate/-/cli-truncate-3.1.0.tgz", 152 | "integrity": "sha512-wfOBkjXteqSnI59oPcJkcPl/ZmwvMMOj340qUIY1SKZCv0B9Cf4D4fAucRkIKQmsIuYK3x1rrgU7MeGRruiuiA==", 153 | "dev": true, 154 | "license": "MIT", 155 | "dependencies": { 156 | "slice-ansi": "^5.0.0", 157 | "string-width": "^5.0.0" 158 | }, 159 | "engines": { 160 | "node": "^12.20.0 || ^14.13.1 || >=16.0.0" 161 | }, 162 | "funding": { 163 | "url": "https://github.com/sponsors/sindresorhus" 164 | } 165 | }, 166 | "node_modules/lint-staged/node_modules/color-convert": { 167 | "version": "2.0.1", 168 | "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", 169 | "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", 170 | "dev": true, 171 | "license": "MIT", 172 | "dependencies": { 173 | "color-name": "~1.1.4" 174 | }, 175 | "engines": { 176 | "node": ">=7.0.0" 177 | } 178 | }, 179 | "node_modules/lint-staged/node_modules/color-name": { 180 | "version": "1.1.4", 181 | "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", 182 | "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", 183 | "dev": true, 184 | "license": "MIT" 185 | }, 186 | "node_modules/lint-staged/node_modules/colorette": { 187 | "version": "2.0.20", 188 | "resolved": "https://registry.npmjs.org/colorette/-/colorette-2.0.20.tgz", 189 | "integrity": "sha512-IfEDxwoWIjkeXL1eXcDiow4UbKjhLdq6/EuSVR9GMN7KVH3r9gQ83e73hsz1Nd1T3ijd5xv1wcWRYO+D6kCI2w==", 190 | "dev": true, 191 | "license": "MIT" 192 | }, 193 | "node_modules/lint-staged/node_modules/commander": { 194 | "version": "9.5.0", 195 | "resolved": "https://registry.npmjs.org/commander/-/commander-9.5.0.tgz", 196 | "integrity": "sha512-KRs7WVDKg86PWiuAqhDrAQnTXZKraVcCc6vFdL14qrZ/DcWwuRo7VoiYXalXO7S5GKpqYiVEwCbgFDfxNHKJBQ==", 197 | "dev": true, 198 | "license": "MIT", 199 | "engines": { 200 | "node": "^12.20.0 || >=14" 201 | } 202 | }, 203 | "node_modules/lint-staged/node_modules/cross-spawn": { 204 | "version": "7.0.6", 205 | "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-7.0.6.tgz", 206 | "integrity": "sha512-uV2QOWP2nWzsy2aMp8aRibhi9dlzF5Hgh5SHaB9OiTGEyDTiJJyx0uy51QXdyWbtAHNua4XJzUKca3OzKUd3vA==", 207 | "dev": true, 208 | "license": "MIT", 209 | "dependencies": { 210 | "path-key": "^3.1.0", 211 | "shebang-command": "^2.0.0", 212 | "which": "^2.0.1" 213 | }, 214 | "engines": { 215 | "node": ">= 8" 216 | } 217 | }, 218 | "node_modules/lint-staged/node_modules/debug": { 219 | "version": "4.4.0", 220 | "resolved": "https://registry.npmjs.org/debug/-/debug-4.4.0.tgz", 221 | "integrity": "sha512-6WTZ/IxCY/T6BALoZHaE4ctp9xm+Z5kY/pzYaCHRFeyVhojxlrm+46y68HA6hr0TcwEssoxNiDEUJQjfPZ/RYA==", 222 | "dev": true, 223 | "license": "MIT", 224 | "dependencies": { 225 | "ms": "^2.1.3" 226 | }, 227 | "engines": { 228 | "node": ">=6.0" 229 | }, 230 | "peerDependenciesMeta": { 231 | "supports-color": { 232 | "optional": true 233 | } 234 | } 235 | }, 236 | "node_modules/lint-staged/node_modules/eastasianwidth": { 237 | "version": "0.2.0", 238 | "resolved": "https://registry.npmjs.org/eastasianwidth/-/eastasianwidth-0.2.0.tgz", 239 | "integrity": "sha512-I88TYZWc9XiYHRQ4/3c5rjjfgkjhLyW2luGIheGERbNQ6OY7yTybanSpDXZa8y7VUP9YmDcYa+eyq4ca7iLqWA==", 240 | "dev": true, 241 | "license": "MIT" 242 | }, 243 | "node_modules/lint-staged/node_modules/emoji-regex": { 244 | "version": "9.2.2", 245 | "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-9.2.2.tgz", 246 | "integrity": "sha512-L18DaJsXSUk2+42pv8mLs5jJT2hqFkFE4j21wOmgbUqsZ2hL72NsUU785g9RXgo3s0ZNgVl42TiHp3ZtOv/Vyg==", 247 | "dev": true, 248 | "license": "MIT" 249 | }, 250 | "node_modules/lint-staged/node_modules/execa": { 251 | "version": "5.1.1", 252 | "resolved": "https://registry.npmjs.org/execa/-/execa-5.1.1.tgz", 253 | "integrity": "sha512-8uSpZZocAZRBAPIEINJj3Lo9HyGitllczc27Eh5YYojjMFMn8yHMDMaUHE2Jqfq05D/wucwI4JGURyXt1vchyg==", 254 | "dev": true, 255 | "license": "MIT", 256 | "dependencies": { 257 | "cross-spawn": "^7.0.3", 258 | "get-stream": "^6.0.0", 259 | "human-signals": "^2.1.0", 260 | "is-stream": "^2.0.0", 261 | "merge-stream": "^2.0.0", 262 | "npm-run-path": "^4.0.1", 263 | "onetime": "^5.1.2", 264 | "signal-exit": "^3.0.3", 265 | "strip-final-newline": "^2.0.0" 266 | }, 267 | "engines": { 268 | "node": ">=10" 269 | }, 270 | "funding": { 271 | "url": "https://github.com/sindresorhus/execa?sponsor=1" 272 | } 273 | }, 274 | "node_modules/lint-staged/node_modules/fill-range": { 275 | "version": "7.1.1", 276 | "resolved": "https://registry.npmjs.org/fill-range/-/fill-range-7.1.1.tgz", 277 | "integrity": "sha512-YsGpe3WHLK8ZYi4tWDg2Jy3ebRz2rXowDxnld4bkQB00cc/1Zw9AWnC0i9ztDJitivtQvaI9KaLyKrc+hBW0yg==", 278 | "dev": true, 279 | "license": "MIT", 280 | "dependencies": { 281 | "to-regex-range": "^5.0.1" 282 | }, 283 | "engines": { 284 | "node": ">=8" 285 | } 286 | }, 287 | "node_modules/lint-staged/node_modules/get-stream": { 288 | "version": "6.0.1", 289 | "resolved": "https://registry.npmjs.org/get-stream/-/get-stream-6.0.1.tgz", 290 | "integrity": "sha512-ts6Wi+2j3jQjqi70w5AlN8DFnkSwC+MqmxEzdEALB2qXZYV3X/b1CTfgPLGJNMeAWxdPfU8FO1ms3NUfaHCPYg==", 291 | "dev": true, 292 | "license": "MIT", 293 | "engines": { 294 | "node": ">=10" 295 | }, 296 | "funding": { 297 | "url": "https://github.com/sponsors/sindresorhus" 298 | } 299 | }, 300 | "node_modules/lint-staged/node_modules/human-signals": { 301 | "version": "2.1.0", 302 | "resolved": "https://registry.npmjs.org/human-signals/-/human-signals-2.1.0.tgz", 303 | "integrity": "sha512-B4FFZ6q/T2jhhksgkbEW3HBvWIfDW85snkQgawt07S7J5QXTk6BkNV+0yAeZrM5QpMAdYlocGoljn0sJ/WQkFw==", 304 | "dev": true, 305 | "license": "Apache-2.0", 306 | "engines": { 307 | "node": ">=10.17.0" 308 | } 309 | }, 310 | "node_modules/lint-staged/node_modules/indent-string": { 311 | "version": "4.0.0", 312 | "resolved": "https://registry.npmjs.org/indent-string/-/indent-string-4.0.0.tgz", 313 | "integrity": "sha512-EdDDZu4A2OyIK7Lr/2zG+w5jmbuk1DVBnEwREQvBzspBJkCEbRa8GxU1lghYcaGJCnRWibjDXlq779X1/y5xwg==", 314 | "dev": true, 315 | "license": "MIT", 316 | "engines": { 317 | "node": ">=8" 318 | } 319 | }, 320 | "node_modules/lint-staged/node_modules/is-fullwidth-code-point": { 321 | "version": "4.0.0", 322 | "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-4.0.0.tgz", 323 | "integrity": "sha512-O4L094N2/dZ7xqVdrXhh9r1KODPJpFms8B5sGdJLPy664AgvXsreZUyCQQNItZRDlYug4xStLjNp/sz3HvBowQ==", 324 | "dev": true, 325 | "license": "MIT", 326 | "engines": { 327 | "node": ">=12" 328 | }, 329 | "funding": { 330 | "url": "https://github.com/sponsors/sindresorhus" 331 | } 332 | }, 333 | "node_modules/lint-staged/node_modules/is-number": { 334 | "version": "7.0.0", 335 | "resolved": "https://registry.npmjs.org/is-number/-/is-number-7.0.0.tgz", 336 | "integrity": "sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng==", 337 | "dev": true, 338 | "license": "MIT", 339 | "engines": { 340 | "node": ">=0.12.0" 341 | } 342 | }, 343 | "node_modules/lint-staged/node_modules/is-stream": { 344 | "version": "2.0.1", 345 | "resolved": "https://registry.npmjs.org/is-stream/-/is-stream-2.0.1.tgz", 346 | "integrity": "sha512-hFoiJiTl63nn+kstHGBtewWSKnQLpyb155KHheA1l39uvtO9nWIop1p3udqPcUd/xbF1VLMO4n7OI6p7RbngDg==", 347 | "dev": true, 348 | "license": "MIT", 349 | "engines": { 350 | "node": ">=8" 351 | }, 352 | "funding": { 353 | "url": "https://github.com/sponsors/sindresorhus" 354 | } 355 | }, 356 | "node_modules/lint-staged/node_modules/isexe": { 357 | "version": "2.0.0", 358 | "resolved": "https://registry.npmjs.org/isexe/-/isexe-2.0.0.tgz", 359 | "integrity": "sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw==", 360 | "dev": true, 361 | "license": "ISC" 362 | }, 363 | "node_modules/lint-staged/node_modules/lilconfig": { 364 | "version": "2.0.5", 365 | "resolved": "https://registry.npmjs.org/lilconfig/-/lilconfig-2.0.5.tgz", 366 | "integrity": "sha512-xaYmXZtTHPAw5m+xLN8ab9C+3a8YmV3asNSPOATITbtwrfbwaLJj8h66H1WMIpALCkqsIzK3h7oQ+PdX+LQ9Eg==", 367 | "dev": true, 368 | "license": "MIT", 369 | "engines": { 370 | "node": ">=10" 371 | } 372 | }, 373 | "node_modules/lint-staged/node_modules/listr2": { 374 | "version": "4.0.5", 375 | "resolved": "https://registry.npmjs.org/listr2/-/listr2-4.0.5.tgz", 376 | "integrity": "sha512-juGHV1doQdpNT3GSTs9IUN43QJb7KHdF9uqg7Vufs/tG9VTzpFphqF4pm/ICdAABGQxsyNn9CiYA3StkI6jpwA==", 377 | "dev": true, 378 | "license": "MIT", 379 | "dependencies": { 380 | "cli-truncate": "^2.1.0", 381 | "colorette": "^2.0.16", 382 | "log-update": "^4.0.0", 383 | "p-map": "^4.0.0", 384 | "rfdc": "^1.3.0", 385 | "rxjs": "^7.5.5", 386 | "through": "^2.3.8", 387 | "wrap-ansi": "^7.0.0" 388 | }, 389 | "engines": { 390 | "node": ">=12" 391 | }, 392 | "peerDependencies": { 393 | "enquirer": ">= 2.3.0 < 3" 394 | }, 395 | "peerDependenciesMeta": { 396 | "enquirer": { 397 | "optional": true 398 | } 399 | } 400 | }, 401 | "node_modules/lint-staged/node_modules/listr2/node_modules/ansi-regex": { 402 | "version": "5.0.1", 403 | "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz", 404 | "integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==", 405 | "dev": true, 406 | "license": "MIT", 407 | "engines": { 408 | "node": ">=8" 409 | } 410 | }, 411 | "node_modules/lint-staged/node_modules/listr2/node_modules/ansi-styles": { 412 | "version": "4.3.0", 413 | "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", 414 | "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", 415 | "dev": true, 416 | "license": "MIT", 417 | "dependencies": { 418 | "color-convert": "^2.0.1" 419 | }, 420 | "engines": { 421 | "node": ">=8" 422 | }, 423 | "funding": { 424 | "url": "https://github.com/chalk/ansi-styles?sponsor=1" 425 | } 426 | }, 427 | "node_modules/lint-staged/node_modules/listr2/node_modules/cli-truncate": { 428 | "version": "2.1.0", 429 | "resolved": "https://registry.npmjs.org/cli-truncate/-/cli-truncate-2.1.0.tgz", 430 | "integrity": "sha512-n8fOixwDD6b/ObinzTrp1ZKFzbgvKZvuz/TvejnLn1aQfC6r52XEx85FmuC+3HI+JM7coBRXUvNqEU2PHVrHpg==", 431 | "dev": true, 432 | "license": "MIT", 433 | "dependencies": { 434 | "slice-ansi": "^3.0.0", 435 | "string-width": "^4.2.0" 436 | }, 437 | "engines": { 438 | "node": ">=8" 439 | }, 440 | "funding": { 441 | "url": "https://github.com/sponsors/sindresorhus" 442 | } 443 | }, 444 | "node_modules/lint-staged/node_modules/listr2/node_modules/emoji-regex": { 445 | "version": "8.0.0", 446 | "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-8.0.0.tgz", 447 | "integrity": "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==", 448 | "dev": true, 449 | "license": "MIT" 450 | }, 451 | "node_modules/lint-staged/node_modules/listr2/node_modules/is-fullwidth-code-point": { 452 | "version": "3.0.0", 453 | "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-3.0.0.tgz", 454 | "integrity": "sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg==", 455 | "dev": true, 456 | "license": "MIT", 457 | "engines": { 458 | "node": ">=8" 459 | } 460 | }, 461 | "node_modules/lint-staged/node_modules/listr2/node_modules/slice-ansi": { 462 | "version": "3.0.0", 463 | "resolved": "https://registry.npmjs.org/slice-ansi/-/slice-ansi-3.0.0.tgz", 464 | "integrity": "sha512-pSyv7bSTC7ig9Dcgbw9AuRNUb5k5V6oDudjZoMBSr13qpLBG7tB+zgCkARjq7xIUgdz5P1Qe8u+rSGdouOOIyQ==", 465 | "dev": true, 466 | "license": "MIT", 467 | "dependencies": { 468 | "ansi-styles": "^4.0.0", 469 | "astral-regex": "^2.0.0", 470 | "is-fullwidth-code-point": "^3.0.0" 471 | }, 472 | "engines": { 473 | "node": ">=8" 474 | } 475 | }, 476 | "node_modules/lint-staged/node_modules/listr2/node_modules/string-width": { 477 | "version": "4.2.3", 478 | "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.3.tgz", 479 | "integrity": "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==", 480 | "dev": true, 481 | "license": "MIT", 482 | "dependencies": { 483 | "emoji-regex": "^8.0.0", 484 | "is-fullwidth-code-point": "^3.0.0", 485 | "strip-ansi": "^6.0.1" 486 | }, 487 | "engines": { 488 | "node": ">=8" 489 | } 490 | }, 491 | "node_modules/lint-staged/node_modules/listr2/node_modules/strip-ansi": { 492 | "version": "6.0.1", 493 | "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz", 494 | "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==", 495 | "dev": true, 496 | "license": "MIT", 497 | "dependencies": { 498 | "ansi-regex": "^5.0.1" 499 | }, 500 | "engines": { 501 | "node": ">=8" 502 | } 503 | }, 504 | "node_modules/lint-staged/node_modules/log-update": { 505 | "version": "4.0.0", 506 | "resolved": "https://registry.npmjs.org/log-update/-/log-update-4.0.0.tgz", 507 | "integrity": "sha512-9fkkDevMefjg0mmzWFBW8YkFP91OrizzkW3diF7CpG+S2EYdy4+TVfGwz1zeF8x7hCx1ovSPTOE9Ngib74qqUg==", 508 | "dev": true, 509 | "license": "MIT", 510 | "dependencies": { 511 | "ansi-escapes": "^4.3.0", 512 | "cli-cursor": "^3.1.0", 513 | "slice-ansi": "^4.0.0", 514 | "wrap-ansi": "^6.2.0" 515 | }, 516 | "engines": { 517 | "node": ">=10" 518 | }, 519 | "funding": { 520 | "url": "https://github.com/sponsors/sindresorhus" 521 | } 522 | }, 523 | "node_modules/lint-staged/node_modules/log-update/node_modules/ansi-regex": { 524 | "version": "5.0.1", 525 | "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz", 526 | "integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==", 527 | "dev": true, 528 | "license": "MIT", 529 | "engines": { 530 | "node": ">=8" 531 | } 532 | }, 533 | "node_modules/lint-staged/node_modules/log-update/node_modules/ansi-styles": { 534 | "version": "4.3.0", 535 | "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", 536 | "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", 537 | "dev": true, 538 | "license": "MIT", 539 | "dependencies": { 540 | "color-convert": "^2.0.1" 541 | }, 542 | "engines": { 543 | "node": ">=8" 544 | }, 545 | "funding": { 546 | "url": "https://github.com/chalk/ansi-styles?sponsor=1" 547 | } 548 | }, 549 | "node_modules/lint-staged/node_modules/log-update/node_modules/emoji-regex": { 550 | "version": "8.0.0", 551 | "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-8.0.0.tgz", 552 | "integrity": "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==", 553 | "dev": true, 554 | "license": "MIT" 555 | }, 556 | "node_modules/lint-staged/node_modules/log-update/node_modules/is-fullwidth-code-point": { 557 | "version": "3.0.0", 558 | "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-3.0.0.tgz", 559 | "integrity": "sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg==", 560 | "dev": true, 561 | "license": "MIT", 562 | "engines": { 563 | "node": ">=8" 564 | } 565 | }, 566 | "node_modules/lint-staged/node_modules/log-update/node_modules/slice-ansi": { 567 | "version": "4.0.0", 568 | "resolved": "https://registry.npmjs.org/slice-ansi/-/slice-ansi-4.0.0.tgz", 569 | "integrity": "sha512-qMCMfhY040cVHT43K9BFygqYbUPFZKHOg7K73mtTWJRb8pyP3fzf4Ixd5SzdEJQ6MRUg/WBnOLxghZtKKurENQ==", 570 | "dev": true, 571 | "license": "MIT", 572 | "dependencies": { 573 | "ansi-styles": "^4.0.0", 574 | "astral-regex": "^2.0.0", 575 | "is-fullwidth-code-point": "^3.0.0" 576 | }, 577 | "engines": { 578 | "node": ">=10" 579 | }, 580 | "funding": { 581 | "url": "https://github.com/chalk/slice-ansi?sponsor=1" 582 | } 583 | }, 584 | "node_modules/lint-staged/node_modules/log-update/node_modules/string-width": { 585 | "version": "4.2.3", 586 | "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.3.tgz", 587 | "integrity": "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==", 588 | "dev": true, 589 | "license": "MIT", 590 | "dependencies": { 591 | "emoji-regex": "^8.0.0", 592 | "is-fullwidth-code-point": "^3.0.0", 593 | "strip-ansi": "^6.0.1" 594 | }, 595 | "engines": { 596 | "node": ">=8" 597 | } 598 | }, 599 | "node_modules/lint-staged/node_modules/log-update/node_modules/strip-ansi": { 600 | "version": "6.0.1", 601 | "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz", 602 | "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==", 603 | "dev": true, 604 | "license": "MIT", 605 | "dependencies": { 606 | "ansi-regex": "^5.0.1" 607 | }, 608 | "engines": { 609 | "node": ">=8" 610 | } 611 | }, 612 | "node_modules/lint-staged/node_modules/log-update/node_modules/wrap-ansi": { 613 | "version": "6.2.0", 614 | "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-6.2.0.tgz", 615 | "integrity": "sha512-r6lPcBGxZXlIcymEu7InxDMhdW0KDxpLgoFLcguasxCaJ/SOIZwINatK9KY/tf+ZrlywOKU0UDj3ATXUBfxJXA==", 616 | "dev": true, 617 | "license": "MIT", 618 | "dependencies": { 619 | "ansi-styles": "^4.0.0", 620 | "string-width": "^4.1.0", 621 | "strip-ansi": "^6.0.0" 622 | }, 623 | "engines": { 624 | "node": ">=8" 625 | } 626 | }, 627 | "node_modules/lint-staged/node_modules/merge-stream": { 628 | "version": "2.0.0", 629 | "resolved": "https://registry.npmjs.org/merge-stream/-/merge-stream-2.0.0.tgz", 630 | "integrity": "sha512-abv/qOcuPfk3URPfDzmZU1LKmuw8kT+0nIHvKrKgFrwifol/doWcdA4ZqsWQ8ENrFKkd67Mfpo/LovbIUsbt3w==", 631 | "dev": true, 632 | "license": "MIT" 633 | }, 634 | "node_modules/lint-staged/node_modules/micromatch": { 635 | "version": "4.0.8", 636 | "resolved": "https://registry.npmjs.org/micromatch/-/micromatch-4.0.8.tgz", 637 | "integrity": "sha512-PXwfBhYu0hBCPw8Dn0E+WDYb7af3dSLVWKi3HGv84IdF4TyFoC0ysxFd0Goxw7nSv4T/PzEJQxsYsEiFCKo2BA==", 638 | "dev": true, 639 | "license": "MIT", 640 | "dependencies": { 641 | "braces": "^3.0.3", 642 | "picomatch": "^2.3.1" 643 | }, 644 | "engines": { 645 | "node": ">=8.6" 646 | } 647 | }, 648 | "node_modules/lint-staged/node_modules/mimic-fn": { 649 | "version": "2.1.0", 650 | "resolved": "https://registry.npmjs.org/mimic-fn/-/mimic-fn-2.1.0.tgz", 651 | "integrity": "sha512-OqbOk5oEQeAZ8WXWydlu9HJjz9WVdEIvamMCcXmuqUYjTknH/sqsWvhQ3vgwKFRR1HpjvNBKQ37nbJgYzGqGcg==", 652 | "dev": true, 653 | "license": "MIT", 654 | "engines": { 655 | "node": ">=6" 656 | } 657 | }, 658 | "node_modules/lint-staged/node_modules/ms": { 659 | "version": "2.1.3", 660 | "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz", 661 | "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==", 662 | "dev": true, 663 | "license": "MIT" 664 | }, 665 | "node_modules/lint-staged/node_modules/normalize-path": { 666 | "version": "3.0.0", 667 | "resolved": "https://registry.npmjs.org/normalize-path/-/normalize-path-3.0.0.tgz", 668 | "integrity": "sha512-6eZs5Ls3WtCisHWp9S2GUy8dqkpGi4BVSz3GaqiE6ezub0512ESztXUwUB6C6IKbQkY2Pnb/mD4WYojCRwcwLA==", 669 | "dev": true, 670 | "license": "MIT", 671 | "engines": { 672 | "node": ">=0.10.0" 673 | } 674 | }, 675 | "node_modules/lint-staged/node_modules/npm-run-path": { 676 | "version": "4.0.1", 677 | "resolved": "https://registry.npmjs.org/npm-run-path/-/npm-run-path-4.0.1.tgz", 678 | "integrity": "sha512-S48WzZW777zhNIrn7gxOlISNAqi9ZC/uQFnRdbeIHhZhCA6UqpkOT8T1G7BvfdgP4Er8gF4sUbaS0i7QvIfCWw==", 679 | "dev": true, 680 | "license": "MIT", 681 | "dependencies": { 682 | "path-key": "^3.0.0" 683 | }, 684 | "engines": { 685 | "node": ">=8" 686 | } 687 | }, 688 | "node_modules/lint-staged/node_modules/object-inspect": { 689 | "version": "1.13.4", 690 | "resolved": "https://registry.npmjs.org/object-inspect/-/object-inspect-1.13.4.tgz", 691 | "integrity": "sha512-W67iLl4J2EXEGTbfeHCffrjDfitvLANg0UlX3wFUUSTx92KXRFegMHUVgSqE+wvhAbi4WqjGg9czysTV2Epbew==", 692 | "dev": true, 693 | "license": "MIT", 694 | "engines": { 695 | "node": ">= 0.4" 696 | }, 697 | "funding": { 698 | "url": "https://github.com/sponsors/ljharb" 699 | } 700 | }, 701 | "node_modules/lint-staged/node_modules/onetime": { 702 | "version": "5.1.2", 703 | "resolved": "https://registry.npmjs.org/onetime/-/onetime-5.1.2.tgz", 704 | "integrity": "sha512-kbpaSSGJTWdAY5KPVeMOKXSrPtr8C8C7wodJbcsd51jRnmD+GZu8Y0VoU6Dm5Z4vWr0Ig/1NKuWRKf7j5aaYSg==", 705 | "dev": true, 706 | "license": "MIT", 707 | "dependencies": { 708 | "mimic-fn": "^2.1.0" 709 | }, 710 | "engines": { 711 | "node": ">=6" 712 | }, 713 | "funding": { 714 | "url": "https://github.com/sponsors/sindresorhus" 715 | } 716 | }, 717 | "node_modules/lint-staged/node_modules/p-map": { 718 | "version": "4.0.0", 719 | "resolved": "https://registry.npmjs.org/p-map/-/p-map-4.0.0.tgz", 720 | "integrity": "sha512-/bjOqmgETBYB5BoEeGVea8dmvHb2m9GLy1E9W43yeyfP6QQCZGFNa+XRceJEuDB6zqr+gKpIAmlLebMpykw/MQ==", 721 | "dev": true, 722 | "license": "MIT", 723 | "dependencies": { 724 | "aggregate-error": "^3.0.0" 725 | }, 726 | "engines": { 727 | "node": ">=10" 728 | }, 729 | "funding": { 730 | "url": "https://github.com/sponsors/sindresorhus" 731 | } 732 | }, 733 | "node_modules/lint-staged/node_modules/path-key": { 734 | "version": "3.1.1", 735 | "resolved": "https://registry.npmjs.org/path-key/-/path-key-3.1.1.tgz", 736 | "integrity": "sha512-ojmeN0qd+y0jszEtoY48r0Peq5dwMEkIlCOu6Q5f41lfkswXuKtYrhgoTpLnyIcHm24Uhqx+5Tqm2InSwLhE6Q==", 737 | "dev": true, 738 | "license": "MIT", 739 | "engines": { 740 | "node": ">=8" 741 | } 742 | }, 743 | "node_modules/lint-staged/node_modules/picomatch": { 744 | "version": "2.3.1", 745 | "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-2.3.1.tgz", 746 | "integrity": "sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA==", 747 | "dev": true, 748 | "license": "MIT", 749 | "engines": { 750 | "node": ">=8.6" 751 | }, 752 | "funding": { 753 | "url": "https://github.com/sponsors/jonschlinkert" 754 | } 755 | }, 756 | "node_modules/lint-staged/node_modules/pidtree": { 757 | "version": "0.5.0", 758 | "resolved": "https://registry.npmjs.org/pidtree/-/pidtree-0.5.0.tgz", 759 | "integrity": "sha512-9nxspIM7OpZuhBxPg73Zvyq7j1QMPMPsGKTqRc2XOaFQauDvoNz9fM1Wdkjmeo7l9GXOZiRs97sPkuayl39wjA==", 760 | "dev": true, 761 | "license": "MIT", 762 | "bin": { 763 | "pidtree": "bin/pidtree.js" 764 | }, 765 | "engines": { 766 | "node": ">=0.10" 767 | } 768 | }, 769 | "node_modules/lint-staged/node_modules/restore-cursor": { 770 | "version": "3.1.0", 771 | "resolved": "https://registry.npmjs.org/restore-cursor/-/restore-cursor-3.1.0.tgz", 772 | "integrity": "sha512-l+sSefzHpj5qimhFSE5a8nufZYAM3sBSVMAPtYkmC+4EH2anSGaEMXSD0izRQbu9nfyQ9y5JrVmp7E8oZrUjvA==", 773 | "dev": true, 774 | "license": "MIT", 775 | "dependencies": { 776 | "onetime": "^5.1.0", 777 | "signal-exit": "^3.0.2" 778 | }, 779 | "engines": { 780 | "node": ">=8" 781 | } 782 | }, 783 | "node_modules/lint-staged/node_modules/rfdc": { 784 | "version": "1.4.1", 785 | "resolved": "https://registry.npmjs.org/rfdc/-/rfdc-1.4.1.tgz", 786 | "integrity": "sha512-q1b3N5QkRUWUl7iyylaaj3kOpIT0N2i9MqIEQXP73GVsN9cw3fdx8X63cEmWhJGi2PPCF23Ijp7ktmd39rawIA==", 787 | "dev": true, 788 | "license": "MIT" 789 | }, 790 | "node_modules/lint-staged/node_modules/rxjs": { 791 | "version": "7.8.2", 792 | "resolved": "https://registry.npmjs.org/rxjs/-/rxjs-7.8.2.tgz", 793 | "integrity": "sha512-dhKf903U/PQZY6boNNtAGdWbG85WAbjT/1xYoZIC7FAY0yWapOBQVsVrDl58W86//e1VpMNBtRV4MaXfdMySFA==", 794 | "dev": true, 795 | "license": "Apache-2.0", 796 | "dependencies": { 797 | "tslib": "^2.1.0" 798 | } 799 | }, 800 | "node_modules/lint-staged/node_modules/shebang-command": { 801 | "version": "2.0.0", 802 | "resolved": "https://registry.npmjs.org/shebang-command/-/shebang-command-2.0.0.tgz", 803 | "integrity": "sha512-kHxr2zZpYtdmrN1qDjrrX/Z1rR1kG8Dx+gkpK1G4eXmvXswmcE1hTWBWYUzlraYw1/yZp6YuDY77YtvbN0dmDA==", 804 | "dev": true, 805 | "license": "MIT", 806 | "dependencies": { 807 | "shebang-regex": "^3.0.0" 808 | }, 809 | "engines": { 810 | "node": ">=8" 811 | } 812 | }, 813 | "node_modules/lint-staged/node_modules/shebang-regex": { 814 | "version": "3.0.0", 815 | "resolved": "https://registry.npmjs.org/shebang-regex/-/shebang-regex-3.0.0.tgz", 816 | "integrity": "sha512-7++dFhtcx3353uBaq8DDR4NuxBetBzC7ZQOhmTQInHEd6bSrXdiEyzCvG07Z44UYdLShWUyXt5M/yhz8ekcb1A==", 817 | "dev": true, 818 | "license": "MIT", 819 | "engines": { 820 | "node": ">=8" 821 | } 822 | }, 823 | "node_modules/lint-staged/node_modules/signal-exit": { 824 | "version": "3.0.7", 825 | "resolved": "https://registry.npmjs.org/signal-exit/-/signal-exit-3.0.7.tgz", 826 | "integrity": "sha512-wnD2ZE+l+SPC/uoS0vXeE9L1+0wuaMqKlfz9AMUo38JsyLSBWSFcHR1Rri62LZc12vLr1gb3jl7iwQhgwpAbGQ==", 827 | "dev": true, 828 | "license": "ISC" 829 | }, 830 | "node_modules/lint-staged/node_modules/slice-ansi": { 831 | "version": "5.0.0", 832 | "resolved": "https://registry.npmjs.org/slice-ansi/-/slice-ansi-5.0.0.tgz", 833 | "integrity": "sha512-FC+lgizVPfie0kkhqUScwRu1O/lF6NOgJmlCgK+/LYxDCTk8sGelYaHDhFcDN+Sn3Cv+3VSa4Byeo+IMCzpMgQ==", 834 | "dev": true, 835 | "license": "MIT", 836 | "dependencies": { 837 | "ansi-styles": "^6.0.0", 838 | "is-fullwidth-code-point": "^4.0.0" 839 | }, 840 | "engines": { 841 | "node": ">=12" 842 | }, 843 | "funding": { 844 | "url": "https://github.com/chalk/slice-ansi?sponsor=1" 845 | } 846 | }, 847 | "node_modules/lint-staged/node_modules/string-argv": { 848 | "version": "0.3.2", 849 | "resolved": "https://registry.npmjs.org/string-argv/-/string-argv-0.3.2.tgz", 850 | "integrity": "sha512-aqD2Q0144Z+/RqG52NeHEkZauTAUWJO8c6yTftGJKO3Tja5tUgIfmIl6kExvhtxSDP7fXB6DvzkfMpCd/F3G+Q==", 851 | "dev": true, 852 | "license": "MIT", 853 | "engines": { 854 | "node": ">=0.6.19" 855 | } 856 | }, 857 | "node_modules/lint-staged/node_modules/string-width": { 858 | "version": "5.1.2", 859 | "resolved": "https://registry.npmjs.org/string-width/-/string-width-5.1.2.tgz", 860 | "integrity": "sha512-HnLOCR3vjcY8beoNLtcjZ5/nxn2afmME6lhrDrebokqMap+XbeW8n9TXpPDOqdGK5qcI3oT0GKTW6wC7EMiVqA==", 861 | "dev": true, 862 | "license": "MIT", 863 | "dependencies": { 864 | "eastasianwidth": "^0.2.0", 865 | "emoji-regex": "^9.2.2", 866 | "strip-ansi": "^7.0.1" 867 | }, 868 | "engines": { 869 | "node": ">=12" 870 | }, 871 | "funding": { 872 | "url": "https://github.com/sponsors/sindresorhus" 873 | } 874 | }, 875 | "node_modules/lint-staged/node_modules/strip-ansi": { 876 | "version": "7.1.0", 877 | "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-7.1.0.tgz", 878 | "integrity": "sha512-iq6eVVI64nQQTRYq2KtEg2d2uU7LElhTJwsH4YzIHZshxlgZms/wIc4VoDQTlG/IvVIrBKG06CrZnp0qv7hkcQ==", 879 | "dev": true, 880 | "license": "MIT", 881 | "dependencies": { 882 | "ansi-regex": "^6.0.1" 883 | }, 884 | "engines": { 885 | "node": ">=12" 886 | }, 887 | "funding": { 888 | "url": "https://github.com/chalk/strip-ansi?sponsor=1" 889 | } 890 | }, 891 | "node_modules/lint-staged/node_modules/strip-final-newline": { 892 | "version": "2.0.0", 893 | "resolved": "https://registry.npmjs.org/strip-final-newline/-/strip-final-newline-2.0.0.tgz", 894 | "integrity": "sha512-BrpvfNAE3dcvq7ll3xVumzjKjZQ5tI1sEUIKr3Uoks0XUl45St3FlatVqef9prk4jRDzhW6WZg+3bk93y6pLjA==", 895 | "dev": true, 896 | "license": "MIT", 897 | "engines": { 898 | "node": ">=6" 899 | } 900 | }, 901 | "node_modules/lint-staged/node_modules/supports-color": { 902 | "version": "9.4.0", 903 | "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-9.4.0.tgz", 904 | "integrity": "sha512-VL+lNrEoIXww1coLPOmiEmK/0sGigko5COxI09KzHc2VJXJsQ37UaQ+8quuxjDeA7+KnLGTWRyOXSLLR2Wb4jw==", 905 | "dev": true, 906 | "license": "MIT", 907 | "engines": { 908 | "node": ">=12" 909 | }, 910 | "funding": { 911 | "url": "https://github.com/chalk/supports-color?sponsor=1" 912 | } 913 | }, 914 | "node_modules/lint-staged/node_modules/through": { 915 | "version": "2.3.8", 916 | "resolved": "https://registry.npmjs.org/through/-/through-2.3.8.tgz", 917 | "integrity": "sha512-w89qg7PI8wAdvX60bMDP+bFoD5Dvhm9oLheFp5O4a2QF0cSBGsBX4qZmadPMvVqlLJBBci+WqGGOAPvcDeNSVg==", 918 | "dev": true, 919 | "license": "MIT" 920 | }, 921 | "node_modules/lint-staged/node_modules/to-regex-range": { 922 | "version": "5.0.1", 923 | "resolved": "https://registry.npmjs.org/to-regex-range/-/to-regex-range-5.0.1.tgz", 924 | "integrity": "sha512-65P7iz6X5yEr1cwcgvQxbbIw7Uk3gOy5dIdtZ4rDveLqhrdJP+Li/Hx6tyK0NEb+2GCyneCMJiGqrADCSNk8sQ==", 925 | "dev": true, 926 | "license": "MIT", 927 | "dependencies": { 928 | "is-number": "^7.0.0" 929 | }, 930 | "engines": { 931 | "node": ">=8.0" 932 | } 933 | }, 934 | "node_modules/lint-staged/node_modules/tslib": { 935 | "version": "2.8.1", 936 | "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.8.1.tgz", 937 | "integrity": "sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w==", 938 | "dev": true, 939 | "license": "0BSD" 940 | }, 941 | "node_modules/lint-staged/node_modules/type-fest": { 942 | "version": "0.21.3", 943 | "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-0.21.3.tgz", 944 | "integrity": "sha512-t0rzBq87m3fVcduHDUFhKmyyX+9eo6WQjZvf51Ea/M0Q7+T374Jp1aUiyUl0GKxp8M/OETVHSDvmkyPgvX+X2w==", 945 | "dev": true, 946 | "license": "(MIT OR CC0-1.0)", 947 | "engines": { 948 | "node": ">=10" 949 | }, 950 | "funding": { 951 | "url": "https://github.com/sponsors/sindresorhus" 952 | } 953 | }, 954 | "node_modules/lint-staged/node_modules/which": { 955 | "version": "2.0.2", 956 | "resolved": "https://registry.npmjs.org/which/-/which-2.0.2.tgz", 957 | "integrity": "sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA==", 958 | "dev": true, 959 | "license": "ISC", 960 | "dependencies": { 961 | "isexe": "^2.0.0" 962 | }, 963 | "bin": { 964 | "node-which": "bin/node-which" 965 | }, 966 | "engines": { 967 | "node": ">= 8" 968 | } 969 | }, 970 | "node_modules/lint-staged/node_modules/wrap-ansi": { 971 | "version": "7.0.0", 972 | "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-7.0.0.tgz", 973 | "integrity": "sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q==", 974 | "dev": true, 975 | "license": "MIT", 976 | "dependencies": { 977 | "ansi-styles": "^4.0.0", 978 | "string-width": "^4.1.0", 979 | "strip-ansi": "^6.0.0" 980 | }, 981 | "engines": { 982 | "node": ">=10" 983 | }, 984 | "funding": { 985 | "url": "https://github.com/chalk/wrap-ansi?sponsor=1" 986 | } 987 | }, 988 | "node_modules/lint-staged/node_modules/wrap-ansi/node_modules/ansi-regex": { 989 | "version": "5.0.1", 990 | "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz", 991 | "integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==", 992 | "dev": true, 993 | "license": "MIT", 994 | "engines": { 995 | "node": ">=8" 996 | } 997 | }, 998 | "node_modules/lint-staged/node_modules/wrap-ansi/node_modules/ansi-styles": { 999 | "version": "4.3.0", 1000 | "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", 1001 | "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", 1002 | "dev": true, 1003 | "license": "MIT", 1004 | "dependencies": { 1005 | "color-convert": "^2.0.1" 1006 | }, 1007 | "engines": { 1008 | "node": ">=8" 1009 | }, 1010 | "funding": { 1011 | "url": "https://github.com/chalk/ansi-styles?sponsor=1" 1012 | } 1013 | }, 1014 | "node_modules/lint-staged/node_modules/wrap-ansi/node_modules/emoji-regex": { 1015 | "version": "8.0.0", 1016 | "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-8.0.0.tgz", 1017 | "integrity": "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==", 1018 | "dev": true, 1019 | "license": "MIT" 1020 | }, 1021 | "node_modules/lint-staged/node_modules/wrap-ansi/node_modules/is-fullwidth-code-point": { 1022 | "version": "3.0.0", 1023 | "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-3.0.0.tgz", 1024 | "integrity": "sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg==", 1025 | "dev": true, 1026 | "license": "MIT", 1027 | "engines": { 1028 | "node": ">=8" 1029 | } 1030 | }, 1031 | "node_modules/lint-staged/node_modules/wrap-ansi/node_modules/string-width": { 1032 | "version": "4.2.3", 1033 | "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.3.tgz", 1034 | "integrity": "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==", 1035 | "dev": true, 1036 | "license": "MIT", 1037 | "dependencies": { 1038 | "emoji-regex": "^8.0.0", 1039 | "is-fullwidth-code-point": "^3.0.0", 1040 | "strip-ansi": "^6.0.1" 1041 | }, 1042 | "engines": { 1043 | "node": ">=8" 1044 | } 1045 | }, 1046 | "node_modules/lint-staged/node_modules/wrap-ansi/node_modules/strip-ansi": { 1047 | "version": "6.0.1", 1048 | "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz", 1049 | "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==", 1050 | "dev": true, 1051 | "license": "MIT", 1052 | "dependencies": { 1053 | "ansi-regex": "^5.0.1" 1054 | }, 1055 | "engines": { 1056 | "node": ">=8" 1057 | } 1058 | }, 1059 | "node_modules/lint-staged/node_modules/yaml": { 1060 | "version": "1.10.2", 1061 | "resolved": "https://registry.npmjs.org/yaml/-/yaml-1.10.2.tgz", 1062 | "integrity": "sha512-r3vXyErRCYJ7wg28yvBY5VSoAF8ZvlcW9/BwUzEtUsjvX/DKs24dIkuwjtuprwJJHsbyUbLApepYTR1BN4uHrg==", 1063 | "dev": true, 1064 | "license": "ISC", 1065 | "engines": { 1066 | "node": ">= 6" 1067 | } 1068 | } 1069 | } 1070 | } 1071 | -------------------------------------------------------------------------------- /packages/schematics/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "@ionic/angular-toolkit", 3 | "version": "12.2.0", 4 | "license": "MIT", 5 | "description": "Schematics for @ionic/angular apps.", 6 | "homepage": "https://ionicframework.com/", 7 | "author": "Ionic Team (https://ionicframework.com)", 8 | "scripts": { 9 | "build": "tsc", 10 | "lint": "true", 11 | "watch": "tsc -w", 12 | "prepublishOnly": "npm run build" 13 | }, 14 | "repository": { 15 | "type": "git", 16 | "url": "https://github.com/ionic-team/angular-toolkit.git" 17 | }, 18 | "bugs": { 19 | "url": "https://github.com/ionic-team/angular-toolkit/issues" 20 | }, 21 | "keywords": [ 22 | "angular", 23 | "Angular CLI", 24 | "blueprints", 25 | "code generation", 26 | "devkit", 27 | "schematics", 28 | "ionic", 29 | "ionic framework", 30 | "ionicframework" 31 | ], 32 | "dependencies": { 33 | "@angular-devkit/core": "^19.0.0", 34 | "@angular-devkit/schematics": "^19.0.0", 35 | "@schematics/angular": "^19.0.0" 36 | }, 37 | "devDependencies": { 38 | "lint-staged": "^12.0.2", 39 | "typescript": ">=5.5.0 <5.9.0" 40 | }, 41 | "eslintConfig": { 42 | "extends": [ 43 | "../../package.json" 44 | ] 45 | }, 46 | "schematics": "./collection.json", 47 | "gitHead": "35dbfaa5d93cb165387321a3f8a0f43e604f5d02" 48 | } 49 | -------------------------------------------------------------------------------- /packages/schematics/page/files/__name@dasherize@if-flat__/__name@dasherize__-routing.module.ts: -------------------------------------------------------------------------------- 1 | import { NgModule } from '@angular/core'; 2 | import { Routes, RouterModule } from '@angular/router'; 3 | 4 | import { <%= classify(name) %>Page } from './<%= dasherize(name) %>.page'; 5 | 6 | const routes: Routes = [ 7 | { 8 | path: '', 9 | component: <%= classify(name) %>Page 10 | } 11 | ]; 12 | 13 | @NgModule({ 14 | imports: [RouterModule.forChild(routes)], 15 | exports: [RouterModule], 16 | }) 17 | export class <%= classify(name) %>PageRoutingModule {} 18 | -------------------------------------------------------------------------------- /packages/schematics/page/files/__name@dasherize@if-flat__/__name@dasherize__.module.ts: -------------------------------------------------------------------------------- 1 | import { NgModule } from '@angular/core'; 2 | import { CommonModule } from '@angular/common'; 3 | import { FormsModule } from '@angular/forms'; 4 | 5 | import { IonicModule } from '@ionic/angular'; 6 | 7 | import { <%= classify(name) %>PageRoutingModule } from './<%= dasherize(name) %>-routing.module'; 8 | 9 | import { <%= classify(name) %>Page } from './<%= dasherize(name) %>.page'; 10 | 11 | @NgModule({ 12 | imports: [ 13 | CommonModule, 14 | FormsModule, 15 | IonicModule, 16 | <%= classify(name) %>PageRoutingModule 17 | ], 18 | declarations: [<%= classify(name) %>Page] 19 | }) 20 | export class <%= classify(name) %>PageModule {} 21 | -------------------------------------------------------------------------------- /packages/schematics/page/files/__name@dasherize@if-flat__/__name@dasherize__.page.__styleext__: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ionic-team/angular-toolkit/2146fa2f9337451289d6fd412c2d8595778b2e54/packages/schematics/page/files/__name@dasherize@if-flat__/__name@dasherize__.page.__styleext__ -------------------------------------------------------------------------------- /packages/schematics/page/files/__name@dasherize@if-flat__/__name@dasherize__.page.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | <%= name %> 4 | 5 | 6 | 7 | 8 | 9 | 10 | <%= name %> 11 | 12 | 13 | 14 | -------------------------------------------------------------------------------- /packages/schematics/page/files/__name@dasherize@if-flat__/__name@dasherize__.page.spec.ts: -------------------------------------------------------------------------------- 1 | import { ComponentFixture, TestBed } from '@angular/core/testing'; 2 | import { <%= classify(name) %>Page } from './<%= dasherize(name) %>.page'; 3 | 4 | describe('<%= classify(name) %>Page', () => { 5 | let component: <%= classify(name) %>Page; 6 | let fixture: ComponentFixture<<%= classify(name) %>Page>; 7 | 8 | beforeEach(() => { 9 | fixture = TestBed.createComponent(<%= classify(name) %>Page); 10 | component = fixture.componentInstance; 11 | fixture.detectChanges(); 12 | }); 13 | 14 | it('should create', () => { 15 | expect(component).toBeTruthy(); 16 | }); 17 | }); 18 | -------------------------------------------------------------------------------- /packages/schematics/page/files/__name@dasherize@if-flat__/__name@dasherize__.page.ts: -------------------------------------------------------------------------------- 1 | import { Component, OnInit } from '@angular/core';<% if(routePath) { %> 2 | import { ActivatedRoute, Params } from '@angular/router';<% } %><% if(standalone) {%> 3 | import { CommonModule } from '@angular/common'; 4 | import { FormsModule } from '@angular/forms'; 5 | import { IonContent, IonHeader, IonTitle, IonToolbar } from '@ionic/angular/standalone';<%} %> 6 | 7 | @Component({ 8 | selector: '<%= selector %>', 9 | templateUrl: './<%= dasherize(name) %>.page.html', 10 | styleUrls: ['./<%= dasherize(name) %>.page.<%= styleext %>'],<% if(standalone) {%> 11 | standalone: true, 12 | imports: [IonContent, IonHeader, IonTitle, IonToolbar, CommonModule, FormsModule]<%} %> 13 | }) 14 | export class <%= classify(name) %>Page implements OnInit {<% if(routePath) { %> 15 | 16 | params: Params;<% } %> 17 | 18 | constructor(<% if(routePath) { %>private route: ActivatedRoute<% } %>) { } 19 | 20 | ngOnInit() {<% if(routePath) { %> 21 | this.params = this.route.snapshot.params;<% } %> 22 | } 23 | 24 | } 25 | -------------------------------------------------------------------------------- /packages/schematics/page/index.ts: -------------------------------------------------------------------------------- 1 | import { strings } from '@angular-devkit/core'; 2 | import type { Rule, Tree } from '@angular-devkit/schematics'; 3 | import { 4 | SchematicsException, 5 | apply, 6 | branchAndMerge, 7 | chain, 8 | filter, 9 | mergeWith, 10 | move, 11 | noop, 12 | template, 13 | url, 14 | } from '@angular-devkit/schematics'; 15 | import { parseName } from '@schematics/angular/utility/parse-name'; 16 | import { validateHtmlSelector } from '@schematics/angular/utility/validation'; 17 | import { buildDefaultPath, getWorkspace } from '@schematics/angular/utility/workspace'; 18 | 19 | import { buildSelector } from '../util'; 20 | 21 | import { addRoute, addRouteToNgModule, findRoutingModuleFromOptions } from './route-utils'; 22 | import type { Schema as PageOptions } from './schema'; 23 | 24 | export default function (options: PageOptions): Rule { 25 | return async (host: Tree) => { 26 | if (!options.project) { 27 | throw new SchematicsException('Option (project) is required.'); 28 | } 29 | 30 | const workspace = await getWorkspace(host); 31 | const project = workspace.projects.get(options.project); 32 | if (project && options.path === undefined) { 33 | options.path = buildDefaultPath(project); 34 | } 35 | 36 | if (!options.standalone) { 37 | options.module = findRoutingModuleFromOptions(host, options); 38 | } 39 | 40 | const parsedPath = parseName(options.path as string, options.name); 41 | options.name = parsedPath.name; 42 | options.path = parsedPath.path; 43 | options.selector = options.selector ? options.selector : buildSelector(options, project?.prefix ?? 'app'); 44 | 45 | validateHtmlSelector(options.selector); 46 | 47 | const templateSource = apply(url('./files'), [ 48 | options.spec ? noop() : filter((p) => !p.endsWith('.spec.ts')), 49 | options.standalone ? filter((p) => !p.endsWith('module.ts')) : noop(), 50 | template({ 51 | ...strings, 52 | 'if-flat': (s: string) => (options.flat ? '' : s), 53 | ...options, 54 | }), 55 | move(parsedPath.path), 56 | ]); 57 | 58 | return chain([ 59 | branchAndMerge( 60 | chain([options.standalone ? addRoute(options) : addRouteToNgModule(options), mergeWith(templateSource)]) 61 | ), 62 | ]); 63 | }; 64 | } 65 | -------------------------------------------------------------------------------- /packages/schematics/page/route-utils/index.ts: -------------------------------------------------------------------------------- 1 | import type { Path } from '@angular-devkit/core'; 2 | import { join, normalize, strings } from '@angular-devkit/core'; 3 | import type { DirEntry, Rule, Tree } from '@angular-devkit/schematics'; 4 | import { SchematicsException } from '@angular-devkit/schematics'; 5 | import type { ModuleOptions } from '@schematics/angular/utility/find-module'; 6 | import { buildRelativePath } from '@schematics/angular/utility/find-module'; 7 | import * as ts from 'typescript'; 8 | 9 | import { findNodes } from '../../util/ast-util'; 10 | import type { Change } from '../../util/change'; 11 | import { InsertChange } from '../../util/change'; 12 | import type { Schema as PageOptions } from '../schema'; 13 | 14 | export function findRoutingModuleFromOptions(host: Tree, options: ModuleOptions): Path | undefined { 15 | // eslint-disable-next-line no-prototype-builtins 16 | if (options.hasOwnProperty('skipImport') && options.skipImport) { 17 | return undefined; 18 | } 19 | 20 | if (!options.module) { 21 | const pathToCheck = (options.path || '') + (options.flat ? '' : '/' + strings.dasherize(options.name)); 22 | 23 | return normalize(findRoutingModule(host, pathToCheck)); 24 | } else { 25 | const modulePath = normalize('/' + options.path + '/' + options.module); 26 | const moduleBaseName = normalize(modulePath).split('/').pop(); 27 | 28 | if (host.exists(modulePath)) { 29 | return normalize(modulePath); 30 | } else if (host.exists(modulePath + '.ts')) { 31 | return normalize(modulePath + '.ts'); 32 | } else if (host.exists(modulePath + '.module.ts')) { 33 | return normalize(modulePath + '.module.ts'); 34 | } else if (host.exists(modulePath + '/' + moduleBaseName + '.module.ts')) { 35 | return normalize(modulePath + '/' + moduleBaseName + '.module.ts'); 36 | } else { 37 | throw new Error('Specified module does not exist'); 38 | } 39 | } 40 | } 41 | 42 | export function findRoutingModule(host: Tree, generateDir: string): Path { 43 | let dir: DirEntry | null = host.getDir('/' + generateDir); 44 | 45 | const routingModuleRe = /-routing\.module\.ts/; 46 | 47 | while (dir) { 48 | const matches = dir.subfiles.filter((p) => routingModuleRe.test(p)); 49 | 50 | if (matches.length === 1) { 51 | return join(dir.path, matches[0]); 52 | } else if (matches.length > 1) { 53 | throw new Error( 54 | 'More than one module matches. Use skip-import option to skip importing the component into the closest module.' 55 | ); 56 | } 57 | 58 | dir = dir.parent; 59 | } 60 | 61 | throw new Error('Could not find an NgModule. Use the skip-import option to skip importing in NgModule.'); 62 | } 63 | 64 | export function addRouteToNgModule(options: PageOptions): Rule { 65 | const { module } = options; 66 | 67 | if (!module) { 68 | throw new SchematicsException('module option is required.'); 69 | } 70 | 71 | return (host) => { 72 | const text = host.read(module); 73 | 74 | if (!text) { 75 | throw new SchematicsException(`File ${module} does not exist.`); 76 | } 77 | 78 | const sourceText = text.toString('utf8'); 79 | const source = ts.createSourceFile(module, sourceText, ts.ScriptTarget.Latest, true); 80 | 81 | const pagePath = 82 | `/${options.path}/` + 83 | (options.flat ? '' : `${strings.dasherize(options.name)}/`) + 84 | `${strings.dasherize(options.name)}.module`; 85 | 86 | const relativePath = buildRelativePath(module, pagePath); 87 | 88 | const routePath = strings.dasherize(options.routePath ? options.routePath : options.name); 89 | const ngModuleName = `${strings.classify(options.name)}PageModule`; 90 | const changes = addRouteToRoutesArray(source, module, routePath, relativePath, ngModuleName); 91 | const recorder = host.beginUpdate(module); 92 | 93 | for (const change of changes) { 94 | if (change instanceof InsertChange) { 95 | recorder.insertLeft(change.pos, change.toAdd); 96 | } 97 | } 98 | 99 | host.commitUpdate(recorder); 100 | 101 | return host; 102 | }; 103 | } 104 | 105 | export function addRouteToRoutesArray( 106 | source: ts.SourceFile, 107 | ngModulePath: string, 108 | routePath: string, 109 | routeLoadChildren: string, 110 | ngModuleName: string 111 | ): Change[] { 112 | const keywords = findNodes(source, ts.SyntaxKind.VariableStatement); 113 | 114 | for (const keyword of keywords) { 115 | if (ts.isVariableStatement(keyword)) { 116 | const [declaration] = keyword.declarationList.declarations; 117 | 118 | if (ts.isVariableDeclaration(declaration) && declaration.initializer && declaration.name.getText() === 'routes') { 119 | const node = declaration.initializer.getChildAt(1); 120 | const lastRouteNode = node.getLastToken(); 121 | 122 | if (!lastRouteNode) { 123 | return []; 124 | } 125 | 126 | const changes: Change[] = []; 127 | let trailingCommaFound = false; 128 | 129 | if (lastRouteNode.kind === ts.SyntaxKind.CommaToken) { 130 | trailingCommaFound = true; 131 | } else { 132 | changes.push(new InsertChange(ngModulePath, lastRouteNode.getEnd(), ',')); 133 | } 134 | 135 | changes.push( 136 | new InsertChange( 137 | ngModulePath, 138 | lastRouteNode.getEnd() + 1, 139 | ` {\n path: '${routePath}',\n loadChildren: () => import('${routeLoadChildren}').then( m => m.${ngModuleName})\n }${ 140 | trailingCommaFound ? ',' : '' 141 | }\n` 142 | ) 143 | ); 144 | 145 | return changes; 146 | } 147 | } 148 | } 149 | 150 | return []; 151 | } 152 | 153 | // Standalone functions 154 | export function addRouteToRoutesFile( 155 | source: ts.SourceFile, 156 | routesFilePath: string, 157 | routePath: string, 158 | relativePath: string, 159 | componentName: string 160 | ): Change[] { 161 | const keywords = findNodes(source, ts.SyntaxKind.VariableStatement); 162 | 163 | for (const keyword of keywords) { 164 | if (ts.isVariableStatement(keyword)) { 165 | const [declaration] = keyword.declarationList.declarations; 166 | 167 | if (ts.isVariableDeclaration(declaration) && declaration.initializer && declaration.name.getText() === 'routes') { 168 | const node = declaration.initializer.getChildAt(1); 169 | const lastRouteNode = node.getLastToken(); 170 | 171 | if (!lastRouteNode) { 172 | return []; 173 | } 174 | 175 | const changes: Change[] = []; 176 | let trailingCommaFound = false; 177 | 178 | if (lastRouteNode.kind === ts.SyntaxKind.CommaToken) { 179 | trailingCommaFound = true; 180 | } else { 181 | changes.push(new InsertChange(routesFilePath, lastRouteNode.getEnd(), ',')); 182 | } 183 | 184 | changes.push( 185 | new InsertChange( 186 | routesFilePath, 187 | lastRouteNode.getEnd() + 1, 188 | ` {\n path: '${routePath}',\n loadComponent: () => import('${relativePath}').then( m => m.${componentName})\n }${ 189 | trailingCommaFound ? ',' : '' 190 | }\n` 191 | ) 192 | ); 193 | 194 | return changes; 195 | } 196 | } 197 | } 198 | 199 | return []; 200 | } 201 | 202 | export function findRoutesFile(host: Tree, options: PageOptions): Path | null { 203 | const pathToCheck = (options.path || '') + (options.flat ? '' : '/' + strings.dasherize(options.name)); 204 | let dir: DirEntry | null = host.getDir('/' + pathToCheck); 205 | const routesRe = /.routes\.ts/; 206 | 207 | while (dir) { 208 | const matches = dir.subfiles.filter((p) => routesRe.test(p)); 209 | if (matches.length === 1) { 210 | return join(dir.path, matches[0]); 211 | } else if (matches.length > 1) { 212 | throw new Error('Could not find your routes file. Use the skip-import option to skip importing.'); 213 | } 214 | dir = dir.parent; 215 | } 216 | throw new Error('Could not find your routes file. Use the skip-import option to skip importing.'); 217 | } 218 | 219 | export function addRoute(options: PageOptions): Rule { 220 | return (host) => { 221 | const routesFile = findRoutesFile(host, options) as string; 222 | const text = host.read(routesFile); 223 | if (!text) { 224 | throw new SchematicsException(`File ${routesFile} does not exist.`); 225 | } 226 | // 227 | const sourceText = text.toString('utf8'); 228 | const source = ts.createSourceFile(routesFile, sourceText, ts.ScriptTarget.Latest, true); 229 | 230 | const pagePath = 231 | `/${options.path}/` + 232 | (options.flat ? '' : `${strings.dasherize(options.name)}/`) + 233 | `${strings.dasherize(options.name)}.page`; 234 | 235 | const relativePath = buildRelativePath(routesFile, pagePath); 236 | 237 | const routePath = strings.dasherize(options.routePath ? options.routePath : options.name); 238 | const componentImport = `${strings.classify(options.name)}Page`; 239 | const changes = addRouteToRoutesFile(source, routesFile, routePath, relativePath, componentImport); 240 | 241 | const recorder = host.beginUpdate(routesFile); 242 | for (const change of changes) { 243 | if (change instanceof InsertChange) { 244 | recorder.insertLeft(change.pos, change.toAdd); 245 | } 246 | } 247 | 248 | host.commitUpdate(recorder); 249 | 250 | return host; 251 | }; 252 | } 253 | -------------------------------------------------------------------------------- /packages/schematics/page/schema.d.ts: -------------------------------------------------------------------------------- 1 | export interface Schema { 2 | path?: string; 3 | project?: string; 4 | name: string; 5 | prefix?: string; 6 | styleext?: string; 7 | spec?: boolean; 8 | flat?: boolean; 9 | selector?: string; 10 | module?: string; 11 | routePath?: string; 12 | standalone?: boolean; 13 | } 14 | -------------------------------------------------------------------------------- /packages/schematics/page/schema.json: -------------------------------------------------------------------------------- 1 | { 2 | "$schema": "http://json-schema.org/schema", 3 | "$id": "SchematicsIonicAngularPage", 4 | "title": "@ionic/angular Page Options Schema", 5 | "type": "object", 6 | "properties": { 7 | "path": { 8 | "type": "string", 9 | "format": "path", 10 | "description": "The path to create the page", 11 | "visible": false, 12 | "$default": { 13 | "$source": "workingDirectory" 14 | } 15 | }, 16 | "project": { 17 | "type": "string", 18 | "description": "The name of the project", 19 | "$default": { 20 | "$source": "projectName" 21 | } 22 | }, 23 | "name": { 24 | "type": "string", 25 | "description": "The name of the page", 26 | "$default": { 27 | "$source": "argv", 28 | "index": 0 29 | } 30 | }, 31 | "prefix": { 32 | "type": "string", 33 | "description": "The prefix to apply to generated selectors", 34 | "alias": "p", 35 | "oneOf": [ 36 | { 37 | "maxLength": 0 38 | }, 39 | { 40 | "minLength": 1, 41 | "format": "html-selector" 42 | } 43 | ] 44 | }, 45 | "styleext": { 46 | "type": "string", 47 | "description": "The file extension of the style file for the page", 48 | "default": "css" 49 | }, 50 | "spec": { 51 | "type": "boolean", 52 | "description": "Specifies if a spec file is generated", 53 | "default": true 54 | }, 55 | "flat": { 56 | "type": "boolean", 57 | "description": "Flag to indicate if a dir is created", 58 | "default": false 59 | }, 60 | "selector": { 61 | "type": "string", 62 | "format": "html-selector", 63 | "description": "The selector to use for the page" 64 | }, 65 | "module": { 66 | "type": "string", 67 | "description": "Allows specification of the declaring module", 68 | "alias": "m" 69 | }, 70 | "routePath": { 71 | "type": "string", 72 | "description": "The path to use for the route of the page", 73 | "default": "" 74 | }, 75 | "standalone": { 76 | "type": "boolean", 77 | "description": "Specifies if the page should be standalone", 78 | "default": false 79 | } 80 | }, 81 | "required": [] 82 | } 83 | -------------------------------------------------------------------------------- /packages/schematics/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "../../tsconfig.json", 3 | "include": ["**/*.ts"], 4 | "exclude": ["**/*.spec.ts","**/files/**/*"] 5 | } 6 | -------------------------------------------------------------------------------- /packages/schematics/util/ast-util.ts: -------------------------------------------------------------------------------- 1 | import * as ts from 'typescript'; 2 | 3 | import type { Change } from './change'; 4 | import { InsertChange, NoopChange } from './change'; 5 | 6 | /* tslint:disable */ 7 | export function insertImport( 8 | source: ts.SourceFile, 9 | fileToEdit: string, 10 | symbolName: string, 11 | fileName: string, 12 | isDefault = false 13 | ): Change { 14 | const rootNode = source; 15 | const allImports = findNodes(rootNode, ts.SyntaxKind.ImportDeclaration); 16 | 17 | // get nodes that map to import statements from the file fileName 18 | const relevantImports = allImports.filter((node) => { 19 | // StringLiteral of the ImportDeclaration is the import file (fileName in this case). 20 | const importFiles = node 21 | .getChildren() 22 | .filter((child) => child.kind === ts.SyntaxKind.StringLiteral) 23 | .map((n) => (n as ts.StringLiteral).text); 24 | 25 | return importFiles.filter((file) => file === fileName).length === 1; 26 | }); 27 | 28 | if (relevantImports.length > 0) { 29 | let importsAsterisk = false; 30 | // imports from import file 31 | const imports: ts.Node[] = []; 32 | relevantImports.forEach((n) => { 33 | Array.prototype.push.apply(imports, findNodes(n, ts.SyntaxKind.Identifier)); 34 | if (findNodes(n, ts.SyntaxKind.AsteriskToken).length > 0) { 35 | importsAsterisk = true; 36 | } 37 | }); 38 | 39 | // if imports * from fileName, don't add symbolName 40 | if (importsAsterisk) { 41 | return new NoopChange(); 42 | } 43 | 44 | const importTextNodes = imports.filter((n) => (n as ts.Identifier).text === symbolName); 45 | 46 | // insert import if it's not there 47 | if (importTextNodes.length === 0) { 48 | const fallbackPos = 49 | findNodes(relevantImports[0], ts.SyntaxKind.CloseBraceToken)[0].getStart() || 50 | findNodes(relevantImports[0], ts.SyntaxKind.FromKeyword)[0].getStart(); 51 | 52 | return insertAfterLastOccurrence(imports, `, ${symbolName}`, fileToEdit, fallbackPos); 53 | } 54 | 55 | return new NoopChange(); 56 | } 57 | 58 | // no such import declaration exists 59 | const useStrict = findNodes(rootNode, ts.SyntaxKind.StringLiteral).filter((n) => n.getText() === 'use strict'); 60 | let fallbackPos = 0; 61 | if (useStrict.length > 0) { 62 | fallbackPos = useStrict[0].end; 63 | } 64 | const open = isDefault ? '' : '{ '; 65 | const close = isDefault ? '' : ' }'; 66 | // if there are no imports or 'use strict' statement, insert import at beginning of file 67 | const insertAtBeginning = allImports.length === 0 && useStrict.length === 0; 68 | const separator = insertAtBeginning ? '' : ';\n'; 69 | const toInsert = 70 | `${separator}import ${open}${symbolName}${close}` + ` from '${fileName}'${insertAtBeginning ? ';\n' : ''}`; 71 | 72 | return insertAfterLastOccurrence(allImports, toInsert, fileToEdit, fallbackPos, ts.SyntaxKind.StringLiteral); 73 | } 74 | 75 | //Safe 76 | export function findNodes(node: any, kind: ts.SyntaxKind, max = Infinity, recursive = false): ts.Node[] { 77 | if (!node || max == 0) { 78 | return []; 79 | } 80 | 81 | const arr: ts.Node[] = []; 82 | if (node.kind === kind) { 83 | arr.push(node); 84 | max--; 85 | } 86 | if (max > 0 && (recursive || node.kind !== kind)) { 87 | for (const child of node.getChildren()) { 88 | findNodes(child, kind, max).forEach((node) => { 89 | if (max > 0) { 90 | arr.push(node); 91 | } 92 | max--; 93 | }); 94 | 95 | if (max <= 0) { 96 | break; 97 | } 98 | } 99 | } 100 | 101 | return arr; 102 | } 103 | 104 | export function getSourceNodes(sourceFile: ts.SourceFile): ts.Node[] { 105 | const nodes: ts.Node[] = [sourceFile]; 106 | const result = []; 107 | 108 | while (nodes.length > 0) { 109 | const node = nodes.shift(); 110 | 111 | if (node) { 112 | result.push(node); 113 | if (node.getChildCount(sourceFile) >= 0) { 114 | nodes.unshift(...node.getChildren()); 115 | } 116 | } 117 | } 118 | 119 | return result; 120 | } 121 | 122 | export function findNode(node: ts.Node, kind: ts.SyntaxKind, text: string): ts.Node | null { 123 | if (node.kind === kind && node.getText() === text) { 124 | // throw new Error(node.getText()); 125 | return node; 126 | } 127 | 128 | let foundNode: ts.Node | null = null; 129 | ts.forEachChild(node, (childNode) => { 130 | foundNode = foundNode || findNode(childNode, kind, text); 131 | }); 132 | 133 | return foundNode; 134 | } 135 | 136 | function nodesByPosition(first: ts.Node, second: ts.Node): number { 137 | return first.getStart() - second.getStart(); 138 | } 139 | 140 | export function insertAfterLastOccurrence( 141 | nodes: ts.Node[], 142 | toInsert: string, 143 | file: string, 144 | fallbackPos: number, 145 | syntaxKind?: ts.SyntaxKind 146 | ): Change { 147 | let lastItem: ts.Node | undefined; 148 | for (const node of nodes) { 149 | if (!lastItem || lastItem.getStart() < node.getStart()) { 150 | lastItem = node; 151 | } 152 | } 153 | if (syntaxKind && lastItem) { 154 | lastItem = findNodes(lastItem, syntaxKind).sort(nodesByPosition).pop(); 155 | } 156 | if (!lastItem && fallbackPos == undefined) { 157 | throw new Error(`tried to insert ${toInsert} as first occurence with no fallback position`); 158 | } 159 | const lastItemPosition: number = lastItem ? lastItem.getEnd() : fallbackPos; 160 | 161 | return new InsertChange(file, lastItemPosition, toInsert); 162 | } 163 | 164 | export function getContentOfKeyLiteral(_source: ts.SourceFile, node: ts.Node): string | null { 165 | if (node.kind == ts.SyntaxKind.Identifier) { 166 | return (node as ts.Identifier).text; 167 | } else if (node.kind == ts.SyntaxKind.StringLiteral) { 168 | return (node as ts.StringLiteral).text; 169 | } else { 170 | return null; 171 | } 172 | } 173 | 174 | function _angularImportsFromNode(node: ts.ImportDeclaration): { 175 | [name: string]: string; 176 | } { 177 | const ms = node.moduleSpecifier; 178 | let modulePath: string; 179 | switch (ms.kind) { 180 | case ts.SyntaxKind.StringLiteral: 181 | modulePath = (ms as ts.StringLiteral).text; 182 | break; 183 | default: 184 | return {}; 185 | } 186 | 187 | if (!modulePath.startsWith('@angular/')) { 188 | return {}; 189 | } 190 | 191 | if (node.importClause) { 192 | if (node.importClause.name) { 193 | // This is of the form `import Name from 'path'`. Ignore. 194 | return {}; 195 | } else if (node.importClause.namedBindings) { 196 | const nb = node.importClause.namedBindings; 197 | if (nb.kind == ts.SyntaxKind.NamespaceImport) { 198 | // This is of the form `import * as name from 'path'`. Return `name.`. 199 | return { 200 | [nb.name.text + '.']: modulePath, 201 | }; 202 | } else { 203 | // This is of the form `import {a,b,c} from 'path'` 204 | const namedImports = nb; 205 | 206 | return namedImports.elements 207 | .map((is: ts.ImportSpecifier) => (is.propertyName ? is.propertyName.text : is.name.text)) 208 | .reduce((acc: { [name: string]: string }, curr: string) => { 209 | acc[curr] = modulePath; 210 | 211 | return acc; 212 | }, {}); 213 | } 214 | } 215 | 216 | return {}; 217 | } else { 218 | // This is of the form `import 'path';`. Nothing to do. 219 | return {}; 220 | } 221 | } 222 | 223 | export function getDecoratorMetadata(source: ts.SourceFile, identifier: string, module: string): ts.Node[] { 224 | const angularImports: { [name: string]: string } = findNodes(source, ts.SyntaxKind.ImportDeclaration) 225 | .map((node) => _angularImportsFromNode(node as ts.ImportDeclaration)) 226 | .reduce((acc: { [name: string]: string }, current: { [name: string]: string }) => { 227 | for (const key of Object.keys(current)) { 228 | acc[key] = current[key]; 229 | } 230 | 231 | return acc; 232 | }, {}); 233 | 234 | return getSourceNodes(source) 235 | .filter((node) => { 236 | return ( 237 | node.kind == ts.SyntaxKind.Decorator && (node as ts.Decorator).expression.kind == ts.SyntaxKind.CallExpression 238 | ); 239 | }) 240 | .map((node) => (node as ts.Decorator).expression as ts.CallExpression) 241 | .filter((expr) => { 242 | if (expr.expression.kind == ts.SyntaxKind.Identifier) { 243 | const id = expr.expression as ts.Identifier; 244 | 245 | return id.text == identifier && angularImports[id.text] === module; 246 | } else if (expr.expression.kind == ts.SyntaxKind.PropertyAccessExpression) { 247 | // This covers foo.NgModule when importing * as foo. 248 | const paExpr = expr.expression as ts.PropertyAccessExpression; 249 | // If the left expression is not an identifier, just give up at that point. 250 | if (paExpr.expression.kind !== ts.SyntaxKind.Identifier) { 251 | return false; 252 | } 253 | 254 | const id = paExpr.name.text; 255 | const moduleId = (paExpr.expression as ts.Identifier).text; 256 | 257 | return id === identifier && angularImports[moduleId + '.'] === module; 258 | } 259 | 260 | return false; 261 | }) 262 | .filter((expr) => expr.arguments[0] && expr.arguments[0].kind == ts.SyntaxKind.ObjectLiteralExpression) 263 | .map((expr) => expr.arguments[0] as ts.ObjectLiteralExpression); 264 | } 265 | 266 | function findClassDeclarationParent(node: ts.Node): ts.ClassDeclaration | undefined { 267 | if (ts.isClassDeclaration(node)) { 268 | return node; 269 | } 270 | 271 | return node.parent && findClassDeclarationParent(node.parent); 272 | } 273 | 274 | export function getFirstNgModuleName(source: ts.SourceFile): string | undefined { 275 | // First, find the @NgModule decorators. 276 | const ngModulesMetadata = getDecoratorMetadata(source, 'NgModule', '@angular/core'); 277 | if (ngModulesMetadata.length === 0) { 278 | return undefined; 279 | } 280 | 281 | // Then walk parent pointers up the AST, looking for the ClassDeclaration parent of the NgModule 282 | // metadata. 283 | const moduleClass = findClassDeclarationParent(ngModulesMetadata[0]); 284 | if (!moduleClass || !moduleClass.name) { 285 | return undefined; 286 | } 287 | 288 | // Get the class name of the module ClassDeclaration. 289 | return moduleClass.name.text; 290 | } 291 | 292 | export function getMetadataField(node: ts.ObjectLiteralExpression, metadataField: string): ts.ObjectLiteralElement[] { 293 | return ( 294 | node.properties 295 | .filter((prop) => ts.isPropertyAssignment(prop)) 296 | // Filter out every fields that's not "metadataField". Also handles string literals 297 | // (but not expressions). 298 | .filter(({ name }) => { 299 | return ( 300 | (ts.isIdentifier(name as ts.Node) || ts.isStringLiteral(name as ts.Node)) && name?.getText() === metadataField 301 | ); 302 | }) 303 | ); 304 | } 305 | 306 | export function addSymbolToNgModuleMetadata( 307 | source: ts.SourceFile, 308 | ngModulePath: string, 309 | metadataField: string, 310 | symbolName: string, 311 | importPath: string | null = null 312 | ): Change[] { 313 | const nodes = getDecoratorMetadata(source, 'NgModule', '@angular/core'); 314 | let node: any = nodes[0]; // tslint:disable-line:no-any 315 | 316 | // Find the decorator declaration. 317 | if (!node) { 318 | return []; 319 | } 320 | 321 | // Get all the children property assignment of object literals. 322 | const matchingProperties = getMetadataField(node as ts.ObjectLiteralExpression, metadataField); 323 | 324 | // Get the last node of the array literal. 325 | if (!matchingProperties) { 326 | return []; 327 | } 328 | if (matchingProperties.length == 0) { 329 | // We haven't found the field in the metadata declaration. Insert a new field. 330 | const expr = node as ts.ObjectLiteralExpression; 331 | let position: number; 332 | let toInsert: string; 333 | if (expr.properties.length == 0) { 334 | position = expr.getEnd() - 1; 335 | toInsert = ` ${metadataField}: [${symbolName}]\n`; 336 | } else { 337 | node = expr.properties[expr.properties.length - 1]; 338 | position = node.getEnd(); 339 | // Get the indentation of the last element, if any. 340 | const text = node.getFullText(source); 341 | const matches = text.match(/^\r?\n\s*/); 342 | if (matches && matches.length > 0) { 343 | toInsert = `,${matches[0]}${metadataField}: [${symbolName}]`; 344 | } else { 345 | toInsert = `, ${metadataField}: [${symbolName}]`; 346 | } 347 | } 348 | if (importPath !== null) { 349 | return [ 350 | new InsertChange(ngModulePath, position, toInsert), 351 | insertImport(source, ngModulePath, symbolName.replace(/\..*$/, ''), importPath), 352 | ]; 353 | } else { 354 | return [new InsertChange(ngModulePath, position, toInsert)]; 355 | } 356 | } 357 | const assignment = matchingProperties[0] as ts.PropertyAssignment; 358 | 359 | // If it's not an array, nothing we can do really. 360 | if (assignment.initializer.kind !== ts.SyntaxKind.ArrayLiteralExpression) { 361 | return []; 362 | } 363 | 364 | const arrLiteral = assignment.initializer as ts.ArrayLiteralExpression; 365 | if (arrLiteral.elements.length == 0) { 366 | // Forward the property. 367 | node = arrLiteral; 368 | } else { 369 | node = arrLiteral.elements; 370 | } 371 | 372 | if (!node) { 373 | // tslint:disable-next-line: no-console 374 | console.error('No app module found. Please add your new class to your component.'); 375 | 376 | return []; 377 | } 378 | 379 | if (Array.isArray(node)) { 380 | const nodeArray = node as ts.Node[]; 381 | const symbolsArray = nodeArray.map((node) => node.getText()); 382 | if (symbolsArray.includes(symbolName)) { 383 | return []; 384 | } 385 | 386 | node = node[node.length - 1]; 387 | } 388 | 389 | let toInsert: string; 390 | let position = node.getEnd(); 391 | if (node.kind == ts.SyntaxKind.ObjectLiteralExpression) { 392 | // We haven't found the field in the metadata declaration. Insert a new 393 | // field. 394 | const expr = node as ts.ObjectLiteralExpression; 395 | if (expr.properties.length == 0) { 396 | position = expr.getEnd() - 1; 397 | toInsert = ` ${symbolName}\n`; 398 | } else { 399 | // Get the indentation of the last element, if any. 400 | const text = node.getFullText(source); 401 | if (text.match(/^\r?\r?\n/)) { 402 | toInsert = `,${text.match(/^\r?\n\s*/)[0]}${symbolName}`; 403 | } else { 404 | toInsert = `, ${symbolName}`; 405 | } 406 | } 407 | } else if (node.kind == ts.SyntaxKind.ArrayLiteralExpression) { 408 | // We found the field but it's empty. Insert it just before the `]`. 409 | position--; 410 | toInsert = `${symbolName}`; 411 | } else { 412 | // Get the indentation of the last element, if any. 413 | const text = node.getFullText(source); 414 | if (text.match(/^\r?\n/)) { 415 | toInsert = `,${text.match(/^\r?\n(\r?)\s*/)[0]}${symbolName}`; 416 | } else { 417 | toInsert = `, ${symbolName}`; 418 | } 419 | } 420 | if (importPath !== null) { 421 | return [ 422 | new InsertChange(ngModulePath, position, toInsert), 423 | insertImport(source, ngModulePath, symbolName.replace(/\..*$/, ''), importPath), 424 | ]; 425 | } 426 | 427 | return [new InsertChange(ngModulePath, position, toInsert)]; 428 | } 429 | 430 | export function addDeclarationToModule( 431 | source: ts.SourceFile, 432 | modulePath: string, 433 | classifiedName: string, 434 | importPath: string 435 | ): Change[] { 436 | return addSymbolToNgModuleMetadata(source, modulePath, 'declarations', classifiedName, importPath); 437 | } 438 | 439 | export function addImportToModule( 440 | source: ts.SourceFile, 441 | modulePath: string, 442 | classifiedName: string, 443 | importPath: string 444 | ): Change[] { 445 | return addSymbolToNgModuleMetadata(source, modulePath, 'imports', classifiedName, importPath); 446 | } 447 | 448 | export function addProviderToModule( 449 | source: ts.SourceFile, 450 | modulePath: string, 451 | classifiedName: string, 452 | importPath: string 453 | ): Change[] { 454 | return addSymbolToNgModuleMetadata(source, modulePath, 'providers', classifiedName, importPath); 455 | } 456 | 457 | export function addExportToModule( 458 | source: ts.SourceFile, 459 | modulePath: string, 460 | classifiedName: string, 461 | importPath: string 462 | ): Change[] { 463 | return addSymbolToNgModuleMetadata(source, modulePath, 'exports', classifiedName, importPath); 464 | } 465 | 466 | export function addBootstrapToModule( 467 | source: ts.SourceFile, 468 | modulePath: string, 469 | classifiedName: string, 470 | importPath: string 471 | ): Change[] { 472 | return addSymbolToNgModuleMetadata(source, modulePath, 'bootstrap', classifiedName, importPath); 473 | } 474 | 475 | export function addEntryComponentToModule( 476 | source: ts.SourceFile, 477 | modulePath: string, 478 | classifiedName: string, 479 | importPath: string 480 | ): Change[] { 481 | return addSymbolToNgModuleMetadata(source, modulePath, 'entryComponents', classifiedName, importPath); 482 | } 483 | 484 | export function isImported(source: ts.SourceFile, classifiedName: string, importPath: string): boolean { 485 | const allNodes = getSourceNodes(source); 486 | const matchingNodes = allNodes 487 | .filter((node) => node.kind === ts.SyntaxKind.ImportDeclaration) 488 | .filter((imp) => (imp as ts.ImportDeclaration).moduleSpecifier.kind === ts.SyntaxKind.StringLiteral) 489 | .filter((imp) => { 490 | return ((imp as ts.ImportDeclaration).moduleSpecifier as ts.StringLiteral).text === importPath; 491 | }) 492 | .filter((imp) => { 493 | if (!(imp as ts.ImportDeclaration).importClause) { 494 | return false; 495 | } 496 | const nodes = findNodes((imp as ts.ImportDeclaration).importClause, ts.SyntaxKind.ImportSpecifier).filter( 497 | (n) => n.getText() === classifiedName 498 | ); 499 | 500 | return nodes.length > 0; 501 | }); 502 | 503 | return matchingNodes.length > 0; 504 | } 505 | 506 | export function getEnvironmentExportName(source: ts.SourceFile): string | null { 507 | // Initial value is `null` as we don't know yet if the user 508 | // has imported `environment` into the root module or not. 509 | let environmentExportName: string | null = null; 510 | 511 | const allNodes = getSourceNodes(source); 512 | 513 | allNodes 514 | .filter((node) => node.kind === ts.SyntaxKind.ImportDeclaration) 515 | .filter( 516 | (declaration) => 517 | (declaration as ts.ImportDeclaration).moduleSpecifier.kind === ts.SyntaxKind.StringLiteral && 518 | (declaration as ts.ImportDeclaration).importClause !== undefined 519 | ) 520 | .map((declaration) => 521 | // If `importClause` property is defined then the first 522 | // child will be `NamedImports` object (or `namedBindings`). 523 | ((declaration as ts.ImportDeclaration).importClause as ts.ImportClause).getChildAt(0) 524 | ) 525 | // Find those `NamedImports` object that contains `environment` keyword 526 | // in its text. E.g. `{ environment as env }`. 527 | .filter((namedImports) => (namedImports as ts.NamedImports).getText().includes('environment')) 528 | .forEach((namedImports) => { 529 | for (const specifier of (namedImports as ts.NamedImports).elements) { 530 | // `propertyName` is defined if the specifier 531 | // has an aliased import. 532 | const name = specifier.propertyName || specifier.name; 533 | 534 | // Find specifier that contains `environment` keyword in its text. 535 | // Whether it's `environment` or `environment as env`. 536 | if (name.text.includes('environment')) { 537 | environmentExportName = specifier.name.text; 538 | } 539 | } 540 | }); 541 | 542 | return environmentExportName; 543 | } 544 | 545 | export function getRouterModuleDeclaration(source: ts.SourceFile): ts.Expression | undefined { 546 | const result = getDecoratorMetadata(source, 'NgModule', '@angular/core'); 547 | const node = result[0] as ts.ObjectLiteralExpression; 548 | const matchingProperties = getMetadataField(node, 'imports'); 549 | 550 | if (!matchingProperties) { 551 | return; 552 | } 553 | 554 | const assignment = matchingProperties[0] as ts.PropertyAssignment; 555 | 556 | if (assignment.initializer.kind !== ts.SyntaxKind.ArrayLiteralExpression) { 557 | return; 558 | } 559 | 560 | const arrLiteral = assignment.initializer as ts.ArrayLiteralExpression; 561 | 562 | return arrLiteral.elements 563 | .filter((el) => el.kind === ts.SyntaxKind.CallExpression) 564 | .find((el) => (el as ts.Identifier).getText().startsWith('RouterModule')); 565 | } 566 | 567 | export function addRouteDeclarationToModule(source: ts.SourceFile, fileToAdd: string, routeLiteral: string): Change { 568 | const routerModuleExpr = getRouterModuleDeclaration(source); 569 | if (!routerModuleExpr) { 570 | throw new Error(`Couldn't find a route declaration in ${fileToAdd}.`); 571 | } 572 | const scopeConfigMethodArgs = (routerModuleExpr as ts.CallExpression).arguments; 573 | if (!scopeConfigMethodArgs.length) { 574 | const { line } = source.getLineAndCharacterOfPosition(routerModuleExpr.getStart()); 575 | throw new Error(`The router module method doesn't have arguments ` + `at line ${line} in ${fileToAdd}`); 576 | } 577 | 578 | let routesArr: ts.ArrayLiteralExpression | undefined; 579 | const routesArg = scopeConfigMethodArgs[0]; 580 | 581 | // Check if the route declarations array is 582 | // an inlined argument of RouterModule or a standalone variable 583 | if (ts.isArrayLiteralExpression(routesArg)) { 584 | routesArr = routesArg; 585 | } else { 586 | const routesVarName = routesArg.getText(); 587 | let routesVar; 588 | if (routesArg.kind === ts.SyntaxKind.Identifier) { 589 | routesVar = source.statements 590 | .filter((s: ts.Statement) => s.kind === ts.SyntaxKind.VariableStatement) 591 | .find((v) => { 592 | return (v as ts.VariableStatement).declarationList.declarations[0].name.getText() === routesVarName; 593 | }); 594 | } 595 | 596 | if (!routesVar) { 597 | const { line } = source.getLineAndCharacterOfPosition(routesArg.getStart()); 598 | throw new Error( 599 | `No route declaration array was found that corresponds ` + `to router module at line ${line} in ${fileToAdd}` 600 | ); 601 | } 602 | 603 | routesArr = findNodes(routesVar, ts.SyntaxKind.ArrayLiteralExpression, 1)[0] as ts.ArrayLiteralExpression; 604 | } 605 | 606 | const occurrencesCount = routesArr.elements.length; 607 | const text = routesArr.getFullText(source); 608 | 609 | let route: string = routeLiteral; 610 | let insertPos = routesArr.elements.pos; 611 | 612 | if (occurrencesCount > 0) { 613 | const lastRouteLiteral = [...routesArr.elements].pop() as ts.Expression; 614 | const lastRouteIsWildcard = 615 | ts.isObjectLiteralExpression(lastRouteLiteral) && 616 | lastRouteLiteral.properties.some( 617 | (n) => 618 | ts.isPropertyAssignment(n) && 619 | ts.isIdentifier(n.name) && 620 | n.name.text === 'path' && 621 | ts.isStringLiteral(n.initializer) && 622 | n.initializer.text === '**' 623 | ); 624 | 625 | const indentation = text.match(/\r?\n(\r?)\s*/) || []; 626 | const routeText = `${indentation[0] || ' '}${routeLiteral}`; 627 | 628 | // Add the new route before the wildcard route 629 | // otherwise we'll always redirect to the wildcard route 630 | if (lastRouteIsWildcard) { 631 | insertPos = lastRouteLiteral.pos; 632 | route = `${routeText},`; 633 | } else { 634 | insertPos = lastRouteLiteral.end; 635 | route = `,${routeText}`; 636 | } 637 | } 638 | 639 | return new InsertChange(fileToAdd, insertPos, route); 640 | } 641 | -------------------------------------------------------------------------------- /packages/schematics/util/change.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * @license 3 | * Copyright Google Inc. All Rights Reserved. 4 | * 5 | * Use of this source code is governed by an MIT-style license that can be 6 | * found in the LICENSE file at https://angular.io/license 7 | */ 8 | export interface Host { 9 | write(path: string, content: string): Promise; 10 | read(path: string): Promise; 11 | } 12 | 13 | export interface Change { 14 | apply(host: Host): Promise; 15 | 16 | // The file this change should be applied to. Some changes might not apply to 17 | // a file (maybe the config). 18 | readonly path: string | null; 19 | 20 | // The order this change should be applied. Normally the position inside the file. 21 | // Changes are applied from the bottom of a file to the top. 22 | readonly order: number; 23 | 24 | // The description of this change. This will be outputted in a dry or verbose run. 25 | readonly description: string; 26 | } 27 | 28 | /** 29 | * An operation that does nothing. 30 | */ 31 | export class NoopChange implements Change { 32 | description = 'No operation.'; 33 | order = Infinity; 34 | path = null; 35 | apply(): Promise { 36 | return Promise.resolve(); 37 | } 38 | } 39 | 40 | /** 41 | * Will add text to the source code. 42 | */ 43 | export class InsertChange implements Change { 44 | order: number; 45 | description: string; 46 | 47 | constructor(public path: string, public pos: number, public toAdd: string) { 48 | if (pos < 0) { 49 | throw new Error('Negative positions are invalid'); 50 | } 51 | this.description = `Inserted ${toAdd} into position ${pos} of ${path}`; 52 | this.order = pos; 53 | } 54 | 55 | /** 56 | * This method does not insert spaces if there is none in the original string. 57 | */ 58 | apply(host: Host): Promise { 59 | return host.read(this.path).then((content) => { 60 | const prefix = content.substring(0, this.pos); 61 | const suffix = content.substring(this.pos); 62 | 63 | return host.write(this.path, `${prefix}${this.toAdd}${suffix}`); 64 | }); 65 | } 66 | } 67 | 68 | /** 69 | * Will remove text from the source code. 70 | */ 71 | export class RemoveChange implements Change { 72 | order: number; 73 | description: string; 74 | 75 | constructor(public path: string, private pos: number, private toRemove: string) { 76 | if (pos < 0) { 77 | throw new Error('Negative positions are invalid'); 78 | } 79 | this.description = `Removed ${toRemove} into position ${pos} of ${path}`; 80 | this.order = pos; 81 | } 82 | 83 | apply(host: Host): Promise { 84 | return host.read(this.path).then((content) => { 85 | const prefix = content.substring(0, this.pos); 86 | const suffix = content.substring(this.pos + this.toRemove.length); 87 | 88 | // TODO: throw error if toRemove doesn't match removed string. 89 | return host.write(this.path, `${prefix}${suffix}`); 90 | }); 91 | } 92 | } 93 | 94 | /** 95 | * Will replace text from the source code. 96 | */ 97 | export class ReplaceChange implements Change { 98 | order: number; 99 | description: string; 100 | 101 | constructor(public path: string, private pos: number, private oldText: string, private newText: string) { 102 | if (pos < 0) { 103 | throw new Error('Negative positions are invalid'); 104 | } 105 | this.description = `Replaced ${oldText} into position ${pos} of ${path} with ${newText}`; 106 | this.order = pos; 107 | } 108 | 109 | apply(host: Host): Promise { 110 | return host.read(this.path).then((content) => { 111 | const prefix = content.substring(0, this.pos); 112 | const suffix = content.substring(this.pos + this.oldText.length); 113 | const text = content.substring(this.pos, this.pos + this.oldText.length); 114 | 115 | if (text !== this.oldText) { 116 | return Promise.reject(new Error(`Invalid replace: "${text}" != "${this.oldText}".`)); 117 | } 118 | 119 | // TODO: throw error if oldText doesn't match removed string. 120 | return host.write(this.path, `${prefix}${this.newText}${suffix}`); 121 | }); 122 | } 123 | } 124 | -------------------------------------------------------------------------------- /packages/schematics/util/index.ts: -------------------------------------------------------------------------------- 1 | import { strings } from '@angular-devkit/core'; 2 | 3 | export function buildSelector(options: any, projectPrefix: string): string { 4 | let selector = strings.dasherize(options.name); 5 | 6 | if (options.prefix) { 7 | selector = `${options.prefix}-${selector}`; 8 | } else if (options.prefix === undefined && projectPrefix) { 9 | selector = `${projectPrefix}-${selector}`; 10 | } 11 | 12 | return selector; 13 | } 14 | -------------------------------------------------------------------------------- /tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "allowUnreachableCode": false, 4 | "baseUrl": ".", 5 | "declaration": false, 6 | "importHelpers": true, 7 | "module": "commonjs", 8 | "moduleResolution": "node", 9 | "noFallthroughCasesInSwitch": true, 10 | "noUnusedLocals": true, 11 | "pretty": true, 12 | "preserveSymlinks": true, 13 | "skipLibCheck": true, 14 | "strict": true, 15 | "target": "es2018", 16 | "lib": ["es2018", "dom"], 17 | "plugins": [{ "name": "typescript-eslint-language-service" }] 18 | }, 19 | "include": ["**/*.ts"], 20 | "exclude": ["node_modules", "**/__tests__/*.ts", "schematics/*/files"] 21 | } 22 | --------------------------------------------------------------------------------