├── .circleci └── config.yml ├── .editorconfig ├── .github └── workflows │ └── main.yml ├── .gitignore ├── .vscode ├── launch.json └── tasks.json ├── LICENSE ├── README.md ├── docs ├── README_contributors.md └── ng-deploy-starter-project.jpg └── src ├── .prettierignore ├── .prettierrc ├── README.md ├── __snapshots__ └── ng-add.spec.ts.snap ├── builders.json ├── collection.json ├── deploy ├── actions.spec.ts ├── actions.ts ├── builder.ts └── schema.json ├── engine ├── engine.spec.ts └── engine.ts ├── index.ts ├── interfaces.ts ├── ng-add-schema.json ├── ng-add.spec.ts ├── ng-add.ts ├── package-lock.json ├── package.json ├── public_api.ts ├── tsconfig.build.json ├── tsconfig.json └── utils.ts /.circleci/config.yml: -------------------------------------------------------------------------------- 1 | version: 2 2 | 3 | jobs: 4 | build: 5 | docker: 6 | - image: circleci/node:14-browsers 7 | 8 | steps: 9 | - checkout 10 | 11 | - restore_cache: 12 | key: dependency-cache-{{ checksum "src/package.json" }} 13 | - run: cd src && npm i --silent 14 | - save_cache: 15 | key: dependency-cache-{{ checksum "src/package.json" }} 16 | paths: 17 | - src/node_modules 18 | - run: cd src && npm run build 19 | - run: cd src && npm run test 20 | - run: cd src/dist && sudo npm link 21 | 22 | - run: sudo -E npm install -g @angular/cli@next 23 | - run: sudo -E ng new your-angular-project --defaults 24 | - run: cd your-angular-project && sudo -E npm link @angular-schule/ngx-deploy-starter 25 | - run: cd your-angular-project && sudo -E ng add @angular-schule/ngx-deploy-starter 26 | - run: sudo -E mkdir /example-folder 27 | - run: cd your-angular-project && sudo -E ng deploy 28 | -------------------------------------------------------------------------------- /.editorconfig: -------------------------------------------------------------------------------- 1 | # Editor configuration, see http://editorconfig.org 2 | root = true 3 | 4 | [*] 5 | charset = utf-8 6 | indent_style = space 7 | indent_size = 2 8 | insert_final_newline = true 9 | trim_trailing_whitespace = true 10 | 11 | [*.md] 12 | max_line_length = off 13 | trim_trailing_whitespace = false 14 | -------------------------------------------------------------------------------- /.github/workflows/main.yml: -------------------------------------------------------------------------------- 1 | name: Build and test 2 | 3 | on: [push] 4 | 5 | jobs: 6 | build-and-deploy: 7 | runs-on: ubuntu-latest 8 | 9 | steps: 10 | - name: Checkout 11 | uses: actions/checkout@v2 12 | 13 | - name: Use Node.js 14.x 14 | uses: actions/setup-node@v1 15 | with: 16 | node-version: 14.x 17 | 18 | - name: Prepare 19 | run: | 20 | cd src 21 | npm install --silent 22 | npm run build 23 | npm run test 24 | cd dist 25 | npm link 26 | cd .. 27 | cd .. 28 | npm install -g @angular/cli@next 29 | ng new your-angular-project --defaults 30 | cd your-angular-project 31 | npm link @angular-schule/ngx-deploy-starter 32 | ng add @angular-schule/ngx-deploy-starter 33 | 34 | - name: Deploy 35 | run: | 36 | mkdir $GITHUB_WORKSPACE/example-folder 37 | cd your-angular-project 38 | ng deploy --target-dir=$GITHUB_WORKSPACE/example-folder 39 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | node_modules 2 | .tmp 3 | .sass-cache 4 | .DS_Store 5 | .bash_history 6 | *.swp 7 | *.swo 8 | *.d.ts 9 | 10 | *.classpath 11 | *.project 12 | *.settings/ 13 | *.classpath 14 | *.project 15 | *.settings/ 16 | 17 | .vim/bundle 18 | nvim/autoload 19 | nvim/plugged 20 | nvim/doc 21 | nvim/swaps 22 | nvim/colors 23 | dist 24 | 25 | /src/mini-testdrive/404.html 26 | /src/mini-testdrive/CNAME 27 | .angulardoc.json 28 | 29 | # Coverage directory used by tools like istanbul 30 | coverage 31 | -------------------------------------------------------------------------------- /.vscode/launch.json: -------------------------------------------------------------------------------- 1 | { 2 | "version": "0.2.0", 3 | "configurations": [ 4 | { 5 | "name": "Debug Jest Tests", 6 | "type": "node", 7 | "request": "launch", 8 | "runtimeArgs": [ 9 | "--inspect-brk", 10 | "${workspaceRoot}/src/node_modules/.bin/jest", 11 | "--runInBand" 12 | ], 13 | "cwd": "${workspaceFolder}/src", 14 | "console": "integratedTerminal", 15 | "internalConsoleOptions": "neverOpen", 16 | "port": 9229 17 | } 18 | ] 19 | } 20 | -------------------------------------------------------------------------------- /.vscode/tasks.json: -------------------------------------------------------------------------------- 1 | { 2 | // See https://go.microsoft.com/fwlink/?LinkId=733558 3 | // for the documentation about the tasks.json format 4 | "version": "2.0.0", 5 | "tasks": [ 6 | { 7 | "type": "npm", 8 | "script": "build", 9 | "path": "src/", 10 | "label": "npm build" 11 | } 12 | ] 13 | } 14 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2019-2021 Johannes Hoppe 4 | Copyright (c) 2019 Minko Gechev 5 | 6 | Permission is hereby granted, free of charge, to any person obtaining a copy of 7 | this software and associated documentation files (the "Software"), to deal in 8 | the Software without restriction, including without limitation the rights to 9 | use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of 10 | the Software, and to permit persons to whom the Software is furnished to do so, 11 | subject to the following conditions: 12 | 13 | The above copyright notice and this permission notice shall be included in all 14 | copies or substantial portions of the Software. 15 | 16 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 17 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS 18 | FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR 19 | COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER 20 | IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN 21 | CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # @angular-schule/ngx-deploy-starter 🚀 2 | 3 | [![NPM version][npm-image]][npm-url] 4 | [![The MIT License](https://img.shields.io/badge/license-MIT-orange.svg?color=blue&style=flat-square)](http://opensource.org/licenses/MIT) 5 | 6 | ![Banner](docs/ng-deploy-starter-project.jpg) 7 | 8 | ## About 9 | 10 | This is a sample project that helps you to implement your own **deployment builder** (`ng deploy`) for the Angular CLI. 11 | The groundwork of this starter was provided by Minko Gechev's [ngx-gh project](https://github.com/mgechev/ngx-gh). 12 | 13 | This project has the following purposes: 14 | 15 | **Goal 1:** To promote the adoption of `ng deploy` by providing a blueprint for new builders. 16 | **Goal 2:** To clarify various questions and to standardise the experience of the existing builders. 17 | 18 | We hope for an inspiring discussion, pull requests and questions. 19 | 20 | **If you don't know `ng deploy` yet, learn more about this command here: 21 | [👉 Blogpost: All you need to know about `ng deploy`](https://angular.schule/blog/2019-08-ng-deploy)** 22 | 23 | 24 | ## Projects based on ngx-deploy-starter 25 | 26 | - [angular-cli-ghpages](https://github.com/angular-schule/angular-cli-ghpages) – Deploy your Angular app to **GitHub pages** directly from the Angular CLI! 🚀 27 | - [ngx-deploy-docker](https://github.com/kauppfbi/ngx-deploy-docker) – Deploy your Angular Application to a **Docker registry** directly from the Angular CLI! 🚀 28 | - [ngx-deploy-npm](https://github.com/bikecoders/ngx-deploy-npm) – Deploy your Angular Package to **NPM** directly from the Angular CLI! 🚀 29 | - [ngx-deploy-ftp](https://github.com/bohoffi/ngx-deploy-ftp) – Deploy Angular apps to an **FTP remote** using the Angular CLI! 🚀 30 | 31 | ## Goal 1: How to make your own deploy builder 32 | 33 | We are there to assist you in creating a builder. 34 | 35 | 1. fork this repository 36 | 2. adjust the `package.json` 37 | 3. search and replace for the string `@angular-schule/ngx-deploy-starter` and `ngx-deploy-starter` and choose your own name. 38 | 4. search and replace for the string `to the file system` and name your deploy target. 39 | 5. add your deployment code to `src/engine/engine.ts`, take care of the tests 40 | 6. follow the instructions from the [contributors README](docs/README_contributors.md) for build, test and publishing. 41 | 42 | You are free to customise this project according to your needs. 43 | Please keep the spirit of Open Source alive and use the MIT or a compatible license. 44 | 45 | ## Goal 2: Best practices for deployment builders 46 | 47 | This project also aims to be a reference for the existing builders. 48 | Recommendations are made through RFCs (Request for Comments), and we are very pleased about their adoption. 49 | [Here is a list of all existing RFCs](https://github.com/angular-schule/ngx-deploy-starter/discussions?discussions_q=RFC+in%3Atitle). 50 | 51 | ## License 52 | 53 | Code released under the [MIT license](LICENSE). 54 | 55 | [npm-url]: https://www.npmjs.com/package/@angular-schule/ngx-deploy-starter 56 | [npm-image]: https://badge.fury.io/js/%40angular-schule%2Fngx-deploy-starter.svg 57 | -------------------------------------------------------------------------------- /docs/README_contributors.md: -------------------------------------------------------------------------------- 1 | # @angular-schule/ngx-deploy-starter: README for contributors 2 | 3 | - [How to start ](#how-to-start) 4 | - [Local development](#local-development) 5 | - [1. Angular CLI](#1-angular-cli) 6 | - [2. npm link](#2-npm-link) 7 | - [3. Adding to an Angular project -- ng add](#3-adding-to-an-angular-project----ng-add) 8 | - [4. Testing](#4-testing) 9 | - [5. Debugging](#5-debugging) 10 | - [Publish to NPM](#publish-to-npm) 11 | - [Usage of Prettier Formatter](#usage-of-prettier-formatter) 12 | 13 | ## How to start 14 | 15 | tl;dr – execute this: 16 | 17 | ``` 18 | cd src 19 | npm i 20 | npm run build 21 | npm test 22 | ``` 23 | 24 | ## Local development 25 | 26 | If you want to try the latest package locally without installing it from NPM, use the following instructions. 27 | This may be useful when you want to try the latest non-published version of this library or you want to make a contribution. 28 | 29 | Follow the instructions for [checking and updating the Angular CLI version](#angular-cli) and then link the package. 30 | 31 | ### 1. Optional: Latest Angular version 32 | 33 | This builder requires the method `getTargetOptions()` from the Angular DevKit which was introduced [here](https://github.com/angular/angular-cli/pull/13825/files). 34 | All Angular projects with Angular 9 and greater are supposed to be compatible. (Actually it works with some versions of 8.x too, but you want to be up to date anyway, don't you?) 35 | Execute the next three steps, if your test project is still older. 36 | 37 | 1. Install the latest version of the Angular CLI. 38 | 39 | ```sh 40 | npm install -g @angular/cli 41 | ``` 42 | 43 | 2. Run `ng version`, to make sure you have installed Angular v9.0.0 or greater. 44 | 45 | 3. Update your existing project using the command: 46 | 47 | ```sh 48 | ng update @angular/cli @angular/core 49 | ``` 50 | 51 | ### 2. npm link 52 | 53 | Use the following instructions to make `@angular-schule/ngx-deploy-starter` available locally via `npm link`. 54 | 55 | 1. Clone the project 56 | 57 | ```sh 58 | git clone https://github.com/angular-schule/ngx-deploy-starter.git 59 | cd ngx-deploy-starter 60 | ``` 61 | 62 | 2. Install the dependencies 63 | 64 | ```sh 65 | cd src 66 | npm install 67 | ``` 68 | 69 | 3. Build the project: 70 | 71 | ```sh 72 | npm run build 73 | ``` 74 | 75 | 4. Create a local npm link: 76 | 77 | ```sh 78 | cd dist 79 | npm link 80 | ``` 81 | 82 | Read more about the `link` feature in the [official NPM documentation](https://docs.npmjs.com/cli/link). 83 | 84 | ### 3. Adding to an Angular project -- ng add 85 | 86 | Once you have completed the previous steps to `npm link` the local copy of `@angular-schule/ngx-deploy-starter`, follow these steps to use it in a local Angular project. 87 | 88 | 1. Enter the project directory 89 | 90 | ```sh 91 | cd your-angular-project 92 | ``` 93 | 94 | 2. Add the local version of `@angular-schule/ngx-deploy-starter`. 95 | 96 | ```sh 97 | npm link @angular-schule/ngx-deploy-starter 98 | ``` 99 | 100 | 3. Now execute the `ng-add` schematic. 101 | 102 | ```sh 103 | ng add @angular-schule/ngx-deploy-starter 104 | ``` 105 | 106 | 4. You can now deploy your angular app to GitHub pages. 107 | 108 | ```sh 109 | ng deploy 110 | ``` 111 | 112 | Or with the old builder syntax: 113 | 114 | ```sh 115 | ng run your-angular-project:deploy 116 | ``` 117 | 118 | 5. You can remove the link later by running `npm unlink` 119 | 120 | 6. We can debug the deployment with VSCode within `your-angular-project`, too. 121 | Go to `your-angular-project/node_modules/@angular-schule/ngx-deploy-starter/deploy/actions.js` and place a breakpoint there. 122 | Now you can debug with the following `launch.json` file: 123 | 124 | ```json 125 | { 126 | "version": "0.2.0", 127 | "configurations": [ 128 | { 129 | "type": "node", 130 | "request": "launch", 131 | "name": "Debug ng deploy", 132 | "skipFiles": ["/**"], 133 | "program": "${workspaceFolder}/node_modules/@angular/cli/bin/ng", 134 | "cwd": "${workspaceFolder}", 135 | "sourceMaps": true, 136 | "args": ["deploy", "--no-build"] 137 | } 138 | ] 139 | } 140 | ``` 141 | 142 | ### 4. Testing 143 | 144 | Testing is done with [Jest](https://jestjs.io/). 145 | To run the tests: 146 | 147 | ```sh 148 | cd ngx-deploy-starter/src 149 | npm test 150 | ``` 151 | 152 | ### 5. Debugging 153 | 154 | To debug your deployer you need to: 155 | 156 | 1. Place `debugger` statement, where you want your deployer stops. 157 | 2. Follow the steps of [npm link](#2-npm-link) described here. compile, link and install linked in a local project 158 | 3. Now, on the project that you linked the deployer, run it on debug mode using: 159 | - | Normal Command | Command on Debug Mode | 160 | | :--------------------- | :------------------------------------------------------------------------ | 161 | | `ng deploy` | `node --inspect-brk ./node_modules/@angular/cli/bin/ng deploy` | 162 | | `ng add YOUR_DEPLOYER` | `node --inspect-brk ./node_modules/@angular/cli/bin/ng add YOUR_DEPLOYER` | 163 | 164 | 4. Use your favorite [Inspector Client](https://nodejs.org/de/docs/guides/debugging-getting-started/#inspector-clients) to debug 165 | 166 | > This is the standard procedure to debug a NodeJs project. If you need more information you can read the official Docs of NodeJs to learn more about it. 167 | > 168 | > _https://nodejs.org/de/docs/guides/debugging-getting-started/_ 169 | 170 | ## Publish to NPM 171 | 172 | ``` 173 | cd ngx-deploy-starter/src 174 | npm run prettier 175 | npm run build 176 | npm run test 177 | npm publish dist --access public 178 | ``` 179 | 180 | ## Keeping track of all the forks 181 | 182 | [ngx-deploy-starter](https://github.com/angular-schule/ngx-deploy-starter/) and 183 | [angular-cli-ghpages](https://github.com/angular-schule/angular-cli-ghpages/) (both developed by Johannes Hoppe) are follow-up projects of the deprecated [ngx-gh demo](https://github.com/mgechev/ngx-gh). 184 | This project was a follow-up of the deploy schematics from the [angularfire](https://github.com/angular/angularfire/) project. 185 | 186 | To stay in sync with the stuff the Angular team is doing, you might want to keep an eye on the following files: 187 | 188 | - [builder.ts](https://github.com/angular/angularfire/blob/master/src/schematics/deploy/builder.ts) 189 | - [actions.ts](https://github.com/angular/angularfire/blob/master/src/schematics/deploy/actions.ts) 190 | -------------------------------------------------------------------------------- /docs/ng-deploy-starter-project.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/angular-schule/ngx-deploy-starter/235128fdbcb664a660787c090742111b4f485fc3/docs/ng-deploy-starter-project.jpg -------------------------------------------------------------------------------- /src/.prettierignore: -------------------------------------------------------------------------------- 1 | dist 2 | -------------------------------------------------------------------------------- /src/.prettierrc: -------------------------------------------------------------------------------- 1 | { 2 | "singleQuote": true, 3 | "trailingComma": "none", 4 | "arrowParens": "avoid" 5 | } 6 | -------------------------------------------------------------------------------- /src/README.md: -------------------------------------------------------------------------------- 1 | # @angular-schule/ngx-deploy-starter 2 | 3 | **Deploy your Angular app to the file system directly from the Angular CLI! 🚀** 4 | 5 | > **Warning:** 6 | > This is a sample project that helps you to implement your own deployment builder (`ng deploy`) for the Angular CLI. 7 | > The actual "deployment" is only a simple copy to another folder in the file system. 8 | > 9 | > **Learn more at 10 | > https://github.com/angular-schule/ngx-deploy-starter** 11 | 12 | ## Usage 13 | 14 | Add `@angular-schule/ngx-deploy-starter` to your project. 15 | 16 | ```bash 17 | ng add @angular-schule/ngx-deploy-starter 18 | ``` 19 | 20 | Deploy your project to the file system. 21 | 22 | ``` 23 | ng deploy [options] 24 | ``` 25 | 26 | ## Options 27 | 28 | The following options are also available. 29 | 30 | #### --build-target 31 | 32 | - **optional** 33 | - Default: `undefined` (string) 34 | - Example: 35 | - `ng deploy` – Angular project is built in `production` mode 36 | - `ng deploy --build-target=test` – Angular project is using the build configuration `test` (this configuration must exist in the `angular.json` file) 37 | 38 | If no `buildTarget` is set, the `production` build of the default project will be chosen. 39 | The `buildTarget` simply points to an existing build configuration for your project, as specified in the `configurations` section of `angular.json`. 40 | Most projects have a default configuration and a production configuration (commonly activated by using the `--prod` flag) but it is possible to specify as many build configurations as needed. 41 | 42 | This is equivalent to calling the command `ng build --configuration=XXX`. 43 | This command has no effect if the option `--no-build` is active. 44 | 45 | **⚠️ BREAKING CHANGE (v1)** 46 | 47 | This option was called `--configuration` in previous versions. 48 | 49 | BEFORE (_does not work_): 50 | 51 | ``` 52 | ng deploy --configuration=test 53 | ``` 54 | 55 | NOW: 56 | 57 | ``` 58 | ng deploy --build-target=test 59 | ``` 60 | 61 | #### --no-build 62 | 63 | - **optional** 64 | - Default: `false` (string) 65 | - Example: 66 | - `ng deploy` – Angular project is build in production mode before the deployment 67 | - `ng deploy --no-build` – Angular project is NOT build 68 | 69 | Skip build process during deployment. 70 | This can be used when you are sure that you haven't changed anything and want to deploy with the latest artifact. 71 | This command causes the `--build-target` setting to have no effect. 72 | 73 | #### --target-dir 74 | 75 | - **optional** 76 | - Default: `/example-folder` (string) 77 | - Example: 78 | - `ng deploy` -- App is "deployed" to the example folder (if existing) 79 | - `ng deploy --target=/var/www/html` -- App is "deployed" to another folder 80 | 81 | > **This is one of the options you can freely choose according to your needs.** 82 | 83 | #### --base-href 84 | 85 | - **optional** 86 | - Default: `undefined` (string) 87 | - Example: 88 | - `ng deploy` -- `` remains unchanged in your `index.html` 89 | - `ng deploy --base-href=/the-repositoryname/` -- `` is added to your `index.html` 90 | 91 | Specifies the base URL for the application being built. 92 | Same as `ng build --base-href=/XXX/` 93 | 94 | > **This is an example how to override the workspace set of options.** 95 | 96 | ## License 97 | 98 | Code released under the [MIT license](LICENSE). 99 | 100 |
101 | 102 | ## 🚀 Powered by [ngx-deploy-starter](https://github.com/angular-schule/ngx-deploy-starter) 103 | -------------------------------------------------------------------------------- /src/__snapshots__/ng-add.spec.ts.snap: -------------------------------------------------------------------------------- 1 | // Jest Snapshot v1, https://goo.gl/fbAQLP 2 | 3 | exports[`ng-add generating files generates new files if starting from scratch 1`] = ` 4 | "{ 5 | \\"version\\": 1, 6 | \\"defaultProject\\": \\"THEPROJECT\\", 7 | \\"projects\\": { 8 | \\"THEPROJECT\\": { 9 | \\"projectType\\": \\"application\\", 10 | \\"root\\": \\"PROJECTROOT\\", 11 | \\"architect\\": { 12 | \\"build\\": { 13 | \\"options\\": { 14 | \\"outputPath\\": \\"dist/THEPROJECT\\" 15 | } 16 | }, 17 | \\"deploy\\": { 18 | \\"builder\\": \\"@angular-schule/ngx-deploy-starter:deploy\\" 19 | } 20 | } 21 | }, 22 | \\"OTHERPROJECT\\": { 23 | \\"projectType\\": \\"application\\", 24 | \\"root\\": \\"PROJECTROOT\\", 25 | \\"architect\\": { 26 | \\"build\\": { 27 | \\"options\\": { 28 | \\"outputPath\\": \\"dist/OTHERPROJECT\\" 29 | } 30 | } 31 | } 32 | } 33 | } 34 | }" 35 | `; 36 | 37 | exports[`ng-add generating files overrides existing files 1`] = ` 38 | "{ 39 | \\"version\\": 1, 40 | \\"defaultProject\\": \\"THEPROJECT\\", 41 | \\"projects\\": { 42 | \\"THEPROJECT\\": { 43 | \\"projectType\\": \\"application\\", 44 | \\"root\\": \\"PROJECTROOT\\", 45 | \\"architect\\": { 46 | \\"build\\": { 47 | \\"options\\": { 48 | \\"outputPath\\": \\"dist/THEPROJECT\\" 49 | } 50 | }, 51 | \\"deploy\\": { 52 | \\"builder\\": \\"@angular-schule/ngx-deploy-starter:deploy\\" 53 | } 54 | } 55 | }, 56 | \\"OTHERPROJECT\\": { 57 | \\"projectType\\": \\"application\\", 58 | \\"root\\": \\"PROJECTROOT\\", 59 | \\"architect\\": { 60 | \\"build\\": { 61 | \\"options\\": { 62 | \\"outputPath\\": \\"dist/OTHERPROJECT\\" 63 | } 64 | }, 65 | \\"deploy\\": { 66 | \\"builder\\": \\"@angular-schule/ngx-deploy-starter:deploy\\" 67 | } 68 | } 69 | } 70 | } 71 | }" 72 | `; 73 | -------------------------------------------------------------------------------- /src/builders.json: -------------------------------------------------------------------------------- 1 | { 2 | "$schema": "@angular-devkit/architect/src/builders-schema.json", 3 | "builders": { 4 | "deploy": { 5 | "implementation": "./deploy/builder", 6 | "schema": "./deploy/schema.json", 7 | "description": "Deploy builder" 8 | } 9 | } 10 | } 11 | -------------------------------------------------------------------------------- /src/collection.json: -------------------------------------------------------------------------------- 1 | { 2 | "$schema": "@angular-devkit/schematics/collection-schema.json", 3 | "schematics": { 4 | "ng-add": { 5 | "description": "Add @angular-schule/ngx-deploy-starter deploy schematic", 6 | "factory": "./ng-add#ngAdd", 7 | "schema": "./ng-add-schema.json", 8 | "aliases": ["install"] 9 | } 10 | } 11 | } 12 | -------------------------------------------------------------------------------- /src/deploy/actions.spec.ts: -------------------------------------------------------------------------------- 1 | import { 2 | BuilderContext, 3 | BuilderOutput, 4 | BuilderRun, 5 | ScheduleOptions, 6 | Target 7 | } from '@angular-devkit/architect/src'; 8 | import { JsonObject, logging } from '@angular-devkit/core'; 9 | import { BuildTarget } from 'interfaces'; 10 | 11 | import deploy from './actions'; 12 | 13 | let context: BuilderContext; 14 | const mockEngine = { run: (_: string, __: any, __2: any) => Promise.resolve() }; 15 | 16 | const PROJECT = 'pirojok-project'; 17 | const BUILD_TARGET: BuildTarget = { 18 | name: `${PROJECT}:build:production` 19 | }; 20 | 21 | describe('Deploy Angular apps', () => { 22 | beforeEach(() => initMocks()); 23 | 24 | it('should invoke the builder', async () => { 25 | const spy = spyOn(context, 'scheduleTarget').and.callThrough(); 26 | await deploy(mockEngine, context, BUILD_TARGET, {}); 27 | 28 | expect(spy).toHaveBeenCalledWith( 29 | { 30 | target: 'build', 31 | configuration: 'production', 32 | project: PROJECT 33 | }, 34 | {} 35 | ); 36 | }); 37 | 38 | it('should invoke the builder with the baseHref', async () => { 39 | const spy = spyOn(context, 'scheduleTarget').and.callThrough(); 40 | await deploy(mockEngine, context, BUILD_TARGET, { baseHref: '/folder' }); 41 | 42 | expect(spy).toHaveBeenCalledWith( 43 | { 44 | target: 'build', 45 | configuration: 'production', 46 | project: PROJECT 47 | }, 48 | { baseHref: '/folder' } 49 | ); 50 | }); 51 | 52 | it('should invoke engine.run', async () => { 53 | const spy = spyOn(mockEngine, 'run').and.callThrough(); 54 | await deploy(mockEngine, context, BUILD_TARGET, {}); 55 | 56 | expect(spy).toHaveBeenCalledWith('dist/some-folder', {}, context.logger); 57 | }); 58 | 59 | describe('error handling', () => { 60 | it('throws if there is no target project', async () => { 61 | context.target = undefined; 62 | try { 63 | await deploy(mockEngine, context, BUILD_TARGET, {}); 64 | fail(); 65 | } catch (e) { 66 | expect(e.message).toMatch(/Cannot execute the build target/); 67 | } 68 | }); 69 | 70 | it('throws if app building fails', async () => { 71 | context.scheduleTarget = ( 72 | _: Target, 73 | __?: JsonObject, 74 | ___?: ScheduleOptions 75 | ) => 76 | Promise.resolve({ 77 | result: Promise.resolve(createBuilderOutputMock(false)) 78 | } as BuilderRun); 79 | try { 80 | await deploy(mockEngine, context, BUILD_TARGET, {}); 81 | fail(); 82 | } catch (e) { 83 | expect(e.message).toEqual('Error while building the app.'); 84 | } 85 | }); 86 | }); 87 | }); 88 | 89 | const initMocks = () => { 90 | context = { 91 | target: { 92 | configuration: 'production', 93 | project: PROJECT, 94 | target: 'foo' 95 | }, 96 | builder: { 97 | builderName: 'mock', 98 | description: 'mock', 99 | optionSchema: false 100 | }, 101 | currentDirectory: 'cwd', 102 | id: 1, 103 | logger: new logging.NullLogger() as any, 104 | workspaceRoot: 'cwd', 105 | addTeardown: _ => {}, 106 | validateOptions: _ => Promise.resolve({} as any), 107 | getBuilderNameForTarget: () => Promise.resolve(''), 108 | analytics: null as any, 109 | getTargetOptions: (_: Target) => 110 | Promise.resolve({ 111 | outputPath: 'dist/some-folder' 112 | }), 113 | reportProgress: (_: number, __?: number, ___?: string) => {}, 114 | reportStatus: (_: string) => {}, 115 | reportRunning: () => {}, 116 | scheduleBuilder: (_: string, __?: JsonObject, ___?: ScheduleOptions) => 117 | Promise.resolve({} as BuilderRun), 118 | scheduleTarget: (_: Target, __?: JsonObject, ___?: ScheduleOptions) => 119 | Promise.resolve({ 120 | result: Promise.resolve(createBuilderOutputMock(true)) 121 | } as BuilderRun) 122 | } as any; 123 | }; 124 | 125 | const createBuilderOutputMock = (success: boolean): BuilderOutput => { 126 | return { 127 | info: { info: null }, 128 | // unfortunately error is undefined in case of a build errors 129 | error: (undefined as unknown) as string, 130 | success: success, 131 | target: {} as Target 132 | }; 133 | }; 134 | -------------------------------------------------------------------------------- /src/deploy/actions.ts: -------------------------------------------------------------------------------- 1 | import { 2 | BuilderContext, 3 | targetFromTargetString 4 | } from '@angular-devkit/architect'; 5 | import { logging } from '@angular-devkit/core'; 6 | 7 | import { Schema } from './schema'; 8 | import { BuildTarget } from '../interfaces'; 9 | 10 | export default async function deploy( 11 | engine: { 12 | run: ( 13 | dir: string, 14 | options: Schema, 15 | logger: logging.LoggerApi 16 | ) => Promise; 17 | }, 18 | context: BuilderContext, 19 | buildTarget: BuildTarget, 20 | options: Schema 21 | ) { 22 | // 1. BUILD 23 | if (options.noBuild) { 24 | context.logger.info(`📦 Skipping build`); 25 | } else { 26 | if (!context.target) { 27 | throw new Error('Cannot execute the build target'); 28 | } 29 | 30 | const overrides = { 31 | ...(options.baseHref && { baseHref: options.baseHref }) 32 | }; 33 | 34 | context.logger.info(`📦 Building "${context.target.project}"`); 35 | context.logger.info(`📦 Build target "${buildTarget.name}"`); 36 | 37 | const build = await context.scheduleTarget( 38 | targetFromTargetString(buildTarget.name), 39 | { 40 | ...buildTarget.options, 41 | ...overrides 42 | } 43 | ); 44 | const buildResult = await build.result; 45 | 46 | if (!buildResult.success) { 47 | throw new Error('Error while building the app.'); 48 | } 49 | } 50 | 51 | // 2. DEPLOYMENT 52 | const buildOptions = await context.getTargetOptions( 53 | targetFromTargetString(buildTarget.name) 54 | ); 55 | if (!buildOptions.outputPath || typeof buildOptions.outputPath !== 'string') { 56 | throw new Error( 57 | `Cannot read the output path option of the Angular project '${buildTarget.name}' in angular.json` 58 | ); 59 | } 60 | 61 | await engine.run( 62 | buildOptions.outputPath, 63 | options, 64 | (context.logger as unknown) as logging.LoggerApi 65 | ); 66 | } 67 | -------------------------------------------------------------------------------- /src/deploy/builder.ts: -------------------------------------------------------------------------------- 1 | import { 2 | BuilderContext, 3 | BuilderOutput, 4 | createBuilder 5 | } from '@angular-devkit/architect'; 6 | 7 | import * as engine from '../engine/engine'; 8 | import deploy from './actions'; 9 | import { Schema } from './schema'; 10 | 11 | // Call the createBuilder() function to create a builder. This mirrors 12 | // createJobHandler() but add typings specific to Architect Builders. 13 | export default createBuilder( 14 | async (options: Schema, context: BuilderContext): Promise => { 15 | if (!context.target) { 16 | throw new Error('Cannot deploy the application without a target'); 17 | } 18 | 19 | const buildTarget = { 20 | name: options.buildTarget || `${context.target.project}:build:production` 21 | }; 22 | 23 | try { 24 | await deploy(engine, context, buildTarget, options); 25 | } catch (e) { 26 | context.logger.error('❌ An error occurred when trying to deploy:'); 27 | context.logger.error(e.message); 28 | return { success: false }; 29 | } 30 | 31 | return { success: true }; 32 | } 33 | ); 34 | -------------------------------------------------------------------------------- /src/deploy/schema.json: -------------------------------------------------------------------------------- 1 | { 2 | "$id": "Schema", 3 | "title": "schema", 4 | "description": "Deployment of Angular CLI applications to the file system", 5 | "properties": { 6 | "baseHref": { 7 | "type": "string", 8 | "description": "This is an example how to override the workspace set of options. --- Base url for the application being built. Same as `ng build --base-href=/XXX/`." 9 | }, 10 | "buildTarget": { 11 | "type": "string", 12 | "description": "A named build target, as specified in the `configurations` section of angular.json. Each named target is accompanied by a configuration of option defaults for that target. This is equivalent to calling the command `ng build --configuration=XXX`." 13 | }, 14 | "noBuild": { 15 | "type": "boolean", 16 | "default": false, 17 | "description": "Skip build process during deployment." 18 | }, 19 | "targetDir": { 20 | "type": "string", 21 | "description": "This is one of the options you can freely choose according to your needs. --- We will 'deploy' to this folder." 22 | } 23 | } 24 | } 25 | -------------------------------------------------------------------------------- /src/engine/engine.spec.ts: -------------------------------------------------------------------------------- 1 | describe('engine', () => { 2 | it('should copy directory', () => { 3 | // TODO: really write test here! 4 | expect(1 + 1).toEqual(2); 5 | }); 6 | }); 7 | -------------------------------------------------------------------------------- /src/engine/engine.ts: -------------------------------------------------------------------------------- 1 | import { logging } from '@angular-devkit/core'; 2 | import * as fse from 'fs-extra'; 3 | 4 | import { Schema } from '../deploy/schema'; 5 | 6 | // TODO: add your deployment code here! 7 | export async function run( 8 | dir: string, 9 | options: Schema, 10 | logger: logging.LoggerApi 11 | ) { 12 | try { 13 | options.targetDir = options.targetDir || '/example-folder'; 14 | 15 | if (!(await fse.pathExists(options.targetDir))) { 16 | throw new Error(`Target directory ${options.targetDir} does not exist!`); 17 | } 18 | 19 | await fse.copy(dir, options.targetDir); 20 | 21 | logger.info( 22 | '🌟 Successfully published via @angular-schule/ngx-deploy-starter! Have a nice day!' 23 | ); 24 | } catch (error) { 25 | logger.error('❌ An error occurred!'); 26 | throw error; 27 | } 28 | } 29 | -------------------------------------------------------------------------------- /src/index.ts: -------------------------------------------------------------------------------- 1 | export * from './public_api'; 2 | -------------------------------------------------------------------------------- /src/interfaces.ts: -------------------------------------------------------------------------------- 1 | export interface WorkspaceProject { 2 | projectType?: string; 3 | architect?: Record< 4 | string, 5 | { builder: string; options?: Record } 6 | >; 7 | } 8 | 9 | export interface Workspace { 10 | defaultProject?: string; 11 | projects: Record; 12 | } 13 | 14 | export interface BuildTarget { 15 | name: string; 16 | options?: { [name: string]: any }; 17 | } 18 | -------------------------------------------------------------------------------- /src/ng-add-schema.json: -------------------------------------------------------------------------------- 1 | { 2 | "$schema": "http://json-schema.org/schema", 3 | "$id": "ngx-deploy-starter-ng-add-schematic", 4 | "title": "ngx-deploy-starter ng-add schematic", 5 | "type": "object", 6 | "properties": {}, 7 | "required": [], 8 | "additionalProperties": false 9 | } 10 | -------------------------------------------------------------------------------- /src/ng-add.spec.ts: -------------------------------------------------------------------------------- 1 | import { SchematicContext, Tree } from '@angular-devkit/schematics'; 2 | 3 | import { ngAdd } from './ng-add'; 4 | 5 | const PROJECT_NAME = 'THEPROJECT'; 6 | const PROJECT_ROOT = 'PROJECTROOT'; 7 | const OTHER_PROJECT_NAME = 'OTHERPROJECT'; 8 | 9 | describe('ng-add', () => { 10 | describe('generating files', () => { 11 | let tree: Tree; 12 | 13 | beforeEach(() => { 14 | tree = Tree.empty(); 15 | tree.create('angular.json', JSON.stringify(generateAngularJson())); 16 | }); 17 | 18 | it('generates new files if starting from scratch', async () => { 19 | const result = await ngAdd({ 20 | project: PROJECT_NAME 21 | })(tree, {} as SchematicContext); 22 | 23 | const actual = result.read('angular.json')!.toString(); 24 | expect(prettifyJSON(actual)).toMatchSnapshot(); 25 | }); 26 | 27 | it('overrides existing files', async () => { 28 | const tempTree = await ngAdd({ 29 | project: PROJECT_NAME 30 | })(tree, {} as SchematicContext); 31 | 32 | const result = await ngAdd({ 33 | project: OTHER_PROJECT_NAME 34 | })(tempTree, {} as SchematicContext); 35 | 36 | const actual = result.read('angular.json')!.toString(); 37 | 38 | expect(prettifyJSON(actual)).toMatchSnapshot(); 39 | }); 40 | }); 41 | 42 | describe('error handling', () => { 43 | it('should fail if project not defined', async () => { 44 | const tree = Tree.empty(); 45 | const angularJSON = generateAngularJson(); 46 | delete angularJSON.defaultProject; 47 | tree.create('angular.json', JSON.stringify(angularJSON)); 48 | 49 | await expect( 50 | ngAdd({ 51 | project: '' 52 | })(tree, {} as SchematicContext) 53 | ).rejects.toThrowError( 54 | 'No Angular project selected and no default project in the workspace' 55 | ); 56 | }); 57 | 58 | it('should throw if angular.json not found', async () => { 59 | await expect( 60 | ngAdd({ 61 | project: PROJECT_NAME 62 | })(Tree.empty(), {} as SchematicContext) 63 | ).rejects.toThrowError('Unable to determine format for workspace path.'); 64 | }); 65 | 66 | it('should throw if angular.json can not be parsed', async () => { 67 | const tree = Tree.empty(); 68 | tree.create('angular.json', 'hi'); 69 | 70 | await expect( 71 | ngAdd({ 72 | project: PROJECT_NAME 73 | })(tree, {} as SchematicContext) 74 | ).rejects.toThrowError('Invalid JSON character: "h" at 0:0.'); 75 | }); 76 | 77 | it('should throw if specified project does not exist', async () => { 78 | const tree = Tree.empty(); 79 | tree.create('angular.json', JSON.stringify({ version: 1, projects: {} })); 80 | 81 | await expect( 82 | ngAdd({ 83 | project: PROJECT_NAME 84 | })(tree, {} as SchematicContext) 85 | ).rejects.toThrowError( 86 | 'The specified Angular project is not defined in this workspace' 87 | ); 88 | }); 89 | 90 | it('should throw if specified project is not application', async () => { 91 | const tree = Tree.empty(); 92 | tree.create( 93 | 'angular.json', 94 | JSON.stringify({ 95 | version: 1, 96 | projects: { [PROJECT_NAME]: { projectType: 'invalid' } } 97 | }) 98 | ); 99 | 100 | await expect( 101 | ngAdd({ 102 | project: PROJECT_NAME 103 | })(tree, {} as SchematicContext) 104 | ).rejects.toThrowError( 105 | 'Deploy requires an Angular project type of "application" in angular.json' 106 | ); 107 | }); 108 | 109 | it('should throw if app does not have architect configured', async () => { 110 | const tree = Tree.empty(); 111 | tree.create( 112 | 'angular.json', 113 | JSON.stringify({ 114 | version: 1, 115 | projects: { [PROJECT_NAME]: { projectType: 'application' } } 116 | }) 117 | ); 118 | 119 | await expect( 120 | ngAdd({ 121 | project: PROJECT_NAME 122 | })(tree, {} as SchematicContext) 123 | ).rejects.toThrowError( 124 | 'Cannot read the output path (architect.build.options.outputPath) of the Angular project "THEPROJECT" in angular.json' 125 | ); 126 | }); 127 | }); 128 | }); 129 | 130 | function prettifyJSON(json: string) { 131 | return JSON.stringify(JSON.parse(json), null, 2); 132 | } 133 | 134 | function generateAngularJson() { 135 | return { 136 | version: 1, 137 | defaultProject: PROJECT_NAME as string | undefined, 138 | projects: { 139 | [PROJECT_NAME]: { 140 | projectType: 'application', 141 | root: PROJECT_ROOT, 142 | architect: { 143 | build: { 144 | options: { 145 | outputPath: 'dist/' + PROJECT_NAME 146 | } 147 | } 148 | } 149 | }, 150 | [OTHER_PROJECT_NAME]: { 151 | projectType: 'application', 152 | root: PROJECT_ROOT, 153 | architect: { 154 | build: { 155 | options: { 156 | outputPath: 'dist/' + OTHER_PROJECT_NAME 157 | } 158 | } 159 | } 160 | } 161 | } 162 | }; 163 | } 164 | -------------------------------------------------------------------------------- /src/ng-add.ts: -------------------------------------------------------------------------------- 1 | import { workspaces } from '@angular-devkit/core'; 2 | import { 3 | SchematicContext, 4 | SchematicsException, 5 | Tree 6 | } from '@angular-devkit/schematics'; 7 | import { createHost } from './utils'; 8 | 9 | interface NgAddOptions { 10 | project: string; 11 | } 12 | 13 | export const ngAdd = (options: NgAddOptions) => async ( 14 | tree: Tree, 15 | _context: SchematicContext 16 | ) => { 17 | const host = createHost(tree); 18 | const { workspace } = await workspaces.readWorkspace('/', host); 19 | 20 | if (!options.project) { 21 | if (workspace.extensions.defaultProject) { 22 | options.project = workspace.extensions.defaultProject as string; 23 | } else { 24 | throw new SchematicsException( 25 | 'No Angular project selected and no default project in the workspace' 26 | ); 27 | } 28 | } 29 | 30 | const project = workspace.projects.get(options.project); 31 | if (!project) { 32 | throw new SchematicsException( 33 | 'The specified Angular project is not defined in this workspace' 34 | ); 35 | } 36 | 37 | if (project.extensions.projectType !== 'application') { 38 | throw new SchematicsException( 39 | `Deploy requires an Angular project type of "application" in angular.json` 40 | ); 41 | } 42 | 43 | if (!project.targets.get('build')?.options?.outputPath) { 44 | throw new SchematicsException( 45 | `Cannot read the output path (architect.build.options.outputPath) of the Angular project "${options.project}" in angular.json` 46 | ); 47 | } 48 | 49 | project.targets.add({ 50 | name: 'deploy', 51 | builder: '@angular-schule/ngx-deploy-starter:deploy', 52 | options: {} 53 | }); 54 | 55 | workspaces.writeWorkspace(workspace, host); 56 | return tree; 57 | }; 58 | -------------------------------------------------------------------------------- /src/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "@angular-schule/ngx-deploy-starter", 3 | "version": "1.0.0", 4 | "description": "Deployment from the Angular CLI to the file system. This is a sample project that helps you to implement your own deployment builder (`ng deploy`) for the Angular CLI.", 5 | "main": "index.js", 6 | "scripts": { 7 | "prebuild": "rimraf dist && json2ts deploy/schema.json > deploy/schema.d.ts", 8 | "build": "tsc -p tsconfig.build.json", 9 | "postbuild": "copyfiles README.md builders.json collection.json ng-add-schema.json package.json ngx-deploy-starter deploy/schema.json dist", 10 | "test": "jest", 11 | "prettier": "prettier --write ." 12 | }, 13 | "schematics": "./collection.json", 14 | "builders": "./builders.json", 15 | "ng-add": { 16 | "save": "devDependencies" 17 | }, 18 | "repository": { 19 | "type": "git", 20 | "url": "git+https://github.com/angular-schule/ngx-deploy-starter.git" 21 | }, 22 | "keywords": [ 23 | "angular", 24 | "cli", 25 | "angular-cli", 26 | "deploy", 27 | "ng deploy", 28 | "ng-deploy" 29 | ], 30 | "author": { 31 | "name": "Johannes Hoppe", 32 | "email": "johannes.hoppe@haushoppe-its.de" 33 | }, 34 | "contributors": [ 35 | "Minko Gechev " 36 | ], 37 | "license": "MIT", 38 | "bugs": { 39 | "url": "https://github.com/angular-schule/ngx-deploy-starter/issues" 40 | }, 41 | "homepage": "https://github.com/angular-schule/ngx-deploy-starter/#readme", 42 | "devDependencies": { 43 | "@angular-devkit/architect": ">= 0.900 < 0.1400", 44 | "@angular-devkit/core": "^9.0.0 || ^10.0.0 || ^11.0.0 || ^12.0.0 || ^13.0.0", 45 | "@angular-devkit/schematics": "^9.0.0 || ^10.0.0 || ^11.0.0 || ^12.0.0 || ^13.0.0", 46 | "@types/fs-extra": "^9.0.4", 47 | "@types/jest": "^26.0.15", 48 | "@types/node": "^14.14.7", 49 | "copyfiles": "^2.4.0", 50 | "husky": "^4.3.5", 51 | "jest": "^26.6.3", 52 | "json-schema-to-typescript": "^9.1.1", 53 | "prettier": "2.1.2", 54 | "pretty-quick": "^3.1.0", 55 | "rimraf": "^3.0.2", 56 | "ts-jest": "^26.4.4", 57 | "typescript": ">=4.0.0 <4.1.0" 58 | }, 59 | "peerDependencies": { 60 | "@angular-devkit/architect": ">= 0.900 < 0.1400", 61 | "@angular-devkit/core": "^9.0.0 || ^10.0.0 || ^11.0.0 || ^12.0.0 || ^13.0.0", 62 | "@angular-devkit/schematics": "^9.0.0 || ^10.0.0 || ^11.0.0 || ^12.0.0 || ^13.0.0" 63 | }, 64 | "dependencies": { 65 | "fs-extra": "^9.0.1" 66 | }, 67 | "husky": { 68 | "hooks": { 69 | "pre-commit": "pretty-quick --staged" 70 | } 71 | }, 72 | "jest": { 73 | "transform": { 74 | "^.+\\.tsx?$": "ts-jest" 75 | }, 76 | "testRegex": "(/test/.*|(\\.|/)(test|spec))\\.(jsx?|tsx?)$", 77 | "moduleFileExtensions": [ 78 | "ts", 79 | "tsx", 80 | "js", 81 | "jsx", 82 | "json", 83 | "node" 84 | ], 85 | "testPathIgnorePatterns": [ 86 | "/node_modules/", 87 | "/dist/" 88 | ] 89 | } 90 | } 91 | -------------------------------------------------------------------------------- /src/public_api.ts: -------------------------------------------------------------------------------- 1 | export * from './ng-add'; 2 | export * from './deploy/actions'; 3 | export * from './deploy/builder'; 4 | -------------------------------------------------------------------------------- /src/tsconfig.build.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "./tsconfig.json", 3 | "exclude": ["node_modules", "test", "**/*spec.ts"] 4 | } 5 | -------------------------------------------------------------------------------- /src/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "baseUrl": ".", 4 | "experimentalDecorators": true, 5 | "emitDecoratorMetadata": true, 6 | "module": "commonjs", 7 | "target": "es2015", 8 | "noImplicitAny": false, 9 | "outDir": "dist", 10 | "rootDir": ".", 11 | "sourceMap": true, 12 | "inlineSources": true, 13 | "declaration": false, 14 | "removeComments": true, 15 | "strictNullChecks": true, 16 | "lib": [ 17 | "es2015", 18 | "dom", 19 | "es2015.promise", 20 | "es2015.collection", 21 | "es2015.iterable" 22 | ], 23 | "skipLibCheck": true, 24 | "moduleResolution": "node", 25 | "esModuleInterop": true 26 | }, 27 | "angularCompilerOptions": { 28 | "skipTemplateCodegen": true, 29 | "strictMetadataEmit": true, 30 | "enableSummariesForJit": false 31 | }, 32 | "exclude": ["node_modules", "dist"], 33 | "typeRoots": ["node_modules/@types"] 34 | } 35 | -------------------------------------------------------------------------------- /src/utils.ts: -------------------------------------------------------------------------------- 1 | import { virtualFs, workspaces } from '@angular-devkit/core'; 2 | import { SchematicsException, Tree } from '@angular-devkit/schematics'; 3 | 4 | export function createHost(tree: Tree): workspaces.WorkspaceHost { 5 | return { 6 | async readFile(path: string): Promise { 7 | const data = tree.read(path); 8 | if (!data) { 9 | throw new SchematicsException('File not found.'); 10 | } 11 | return virtualFs.fileBufferToString(data); 12 | }, 13 | async writeFile(path: string, data: string): Promise { 14 | return tree.overwrite(path, data); 15 | }, 16 | async isDirectory(path: string): Promise { 17 | return !tree.exists(path) && tree.getDir(path).subfiles.length > 0; 18 | }, 19 | async isFile(path: string): Promise { 20 | return tree.exists(path); 21 | } 22 | }; 23 | } 24 | --------------------------------------------------------------------------------