From 4a92fa94713b423a73f549a66320ee4d6fef852b Mon Sep 17 00:00:00 2001 From: Keen Yee Liau Date: Mon, 28 Jan 2019 11:03:04 -0800 Subject: [PATCH] refactor(bazel): Create `ng-add` schematic for Bazel (#28436) The logic to create additional files needed for Bazel are currently hosted in `ng new`. Such files include the main.*.ts files needed for AOT and a different angular.json to use Bazel builder, among others. This commit refactors the logic into `ng add` so that it can be used to perform the same modifications in an existing project. Users could do so by running `ng add @angular/bazel`. With this change, `ng new` effectively becomes an orchestrator that runs the original `ng new` followed by `ng add @angular/bazel`. PR Close #28436 --- .../bazel-schematics/angular.json.original | 141 ----------- integration/bazel-schematics/test.sh | 5 +- integration/bazel-schematics/yarn.lock | 159 ++++++------ packages/bazel/BUILD.bazel | 1 + packages/bazel/src/schematics/BUILD.bazel | 1 + .../bazel-workspace/files/yarn.lock | 0 .../src/schematics/bazel-workspace/index.ts | 4 + .../schematics/bazel-workspace/index_spec.ts | 24 +- packages/bazel/src/schematics/collection.json | 33 +-- .../bazel/src/schematics/ng-add/BUILD.bazel | 36 +++ .../files/main.dev.ts.template | 0 .../files/main.prod.ts.template | 0 packages/bazel/src/schematics/ng-add/index.ts | 232 ++++++++++++++++++ .../bazel/src/schematics/ng-add/index_spec.ts | 171 +++++++++++++ .../bazel/src/schematics/ng-add/schema.d.ts | 13 + .../bazel/src/schematics/ng-add/schema.json | 20 ++ .../bazel/src/schematics/ng-new/BUILD.bazel | 4 +- packages/bazel/src/schematics/ng-new/index.ts | 198 +-------------- .../bazel/src/schematics/ng-new/index_spec.ts | 85 +------ .../bazel/src/schematics/ng-new/schema.json | 32 ++- 20 files changed, 617 insertions(+), 542 deletions(-) delete mode 100644 integration/bazel-schematics/angular.json.original delete mode 100644 packages/bazel/src/schematics/bazel-workspace/files/yarn.lock create mode 100644 packages/bazel/src/schematics/ng-add/BUILD.bazel rename packages/bazel/src/schematics/{ng-new => ng-add}/files/main.dev.ts.template (100%) rename packages/bazel/src/schematics/{ng-new => ng-add}/files/main.prod.ts.template (100%) create mode 100755 packages/bazel/src/schematics/ng-add/index.ts create mode 100644 packages/bazel/src/schematics/ng-add/index_spec.ts create mode 100644 packages/bazel/src/schematics/ng-add/schema.d.ts create mode 100755 packages/bazel/src/schematics/ng-add/schema.json mode change 100755 => 100644 packages/bazel/src/schematics/ng-new/index.ts mode change 100755 => 100644 packages/bazel/src/schematics/ng-new/schema.json diff --git a/integration/bazel-schematics/angular.json.original b/integration/bazel-schematics/angular.json.original deleted file mode 100644 index fdb73942f0..0000000000 --- a/integration/bazel-schematics/angular.json.original +++ /dev/null @@ -1,141 +0,0 @@ -{ - "$schema": "./node_modules/@angular/cli/lib/config/schema.json", - "version": 1, - "newProjectRoot": "projects", - "projects": { - "demo": { - "root": "", - "sourceRoot": "src", - "projectType": "application", - "prefix": "app", - "schematics": {}, - "architect": { - "build": { - "builder": "@angular-devkit/build-angular:browser", - "options": { - "outputPath": "dist/demo", - "index": "src/index.html", - "main": "src/main.ts", - "polyfills": "src/polyfills.ts", - "tsConfig": "src/tsconfig.app.json", - "assets": [ - "src/favicon.ico", - "src/assets" - ], - "styles": [ - "src/styles.scss" - ], - "scripts": [] - }, - "configurations": { - "production": { - "fileReplacements": [ - { - "replace": "src/environments/environment.ts", - "with": "src/environments/environment.prod.ts" - } - ], - "optimization": true, - "outputHashing": "all", - "sourceMap": false, - "extractCss": true, - "namedChunks": false, - "aot": true, - "extractLicenses": true, - "vendorChunk": false, - "buildOptimizer": true, - "budgets": [ - { - "type": "initial", - "maximumWarning": "2mb", - "maximumError": "5mb" - } - ] - } - } - }, - "serve": { - "builder": "@angular-devkit/build-angular:dev-server", - "options": { - "browserTarget": "demo:build" - }, - "configurations": { - "production": { - "browserTarget": "demo:build:production" - }, - "ci": { - "progress": false - } - } - }, - "extract-i18n": { - "builder": "@angular-devkit/build-angular:extract-i18n", - "options": { - "browserTarget": "demo:build" - } - }, - "test": { - "builder": "@angular-devkit/build-angular:karma", - "options": { - "main": "src/test.ts", - "polyfills": "src/polyfills.ts", - "tsConfig": "src/tsconfig.spec.json", - "karmaConfig": "src/karma.conf.js", - "styles": [ - "src/styles.scss" - ], - "scripts": [], - "assets": [ - "src/favicon.ico", - "src/assets" - ] - } - }, - "lint": { - "builder": "@angular-devkit/build-angular:tslint", - "options": { - "tsConfig": [ - "src/tsconfig.app.json", - "src/tsconfig.spec.json" - ], - "exclude": [ - "**/node_modules/**" - ] - } - } - } - }, - "demo-e2e": { - "root": "e2e/", - "projectType": "application", - "prefix": "", - "architect": { - "e2e": { - "builder": "@angular-devkit/build-angular:protractor", - "options": { - "protractorConfig": "e2e/protractor.conf.js", - "devServerTarget": "demo:serve" - }, - "configurations": { - "production": { - "devServerTarget": "demo:serve:production" - }, - "ci": { - "devServerTarget": "demo:serve:ci" - } - } - }, - "lint": { - "builder": "@angular-devkit/build-angular:tslint", - "options": { - "tsConfig": "e2e/tsconfig.e2e.json", - "exclude": [ - "**/node_modules/**" - ] - } - } - } - } - }, - "defaultProject": "demo" -} diff --git a/integration/bazel-schematics/test.sh b/integration/bazel-schematics/test.sh index de1ebbefdb..e781e81dd8 100755 --- a/integration/bazel-schematics/test.sh +++ b/integration/bazel-schematics/test.sh @@ -5,6 +5,7 @@ set -eux -o pipefail function testBazel() { # Set up bazel version + ng version rm -rf demo # Create project ng new demo --collection=@angular/bazel --defaults --skip-git --style=scss @@ -21,11 +22,11 @@ function testBazel() { function testNonBazel() { # Replace angular.json that uses Bazel builder with the default generated by CLI - cp ../angular.json.original ./angular.json + mv ./angular.json.bak ./angular.json rm -rf dist src/main.dev.ts src/main.prod.ts ng build --progress=false ng test --progress=false --watch=false - ng e2e --configuration=ci --webdriver-update=false + ng e2e --configuration=production --webdriver-update=false } testBazel diff --git a/integration/bazel-schematics/yarn.lock b/integration/bazel-schematics/yarn.lock index daf440e6eb..051182105d 100644 --- a/integration/bazel-schematics/yarn.lock +++ b/integration/bazel-schematics/yarn.lock @@ -29,12 +29,12 @@ rxjs "6.3.3" source-map "0.7.3" -"@angular-devkit/core@7.1.3", "@angular-devkit/core@^7.0.4": - version "7.1.3" - resolved "https://registry.yarnpkg.com/@angular-devkit/core/-/core-7.1.3.tgz#3d82a7e99aeb6259ff941d82b65759cb1c397042" - integrity sha512-pGBInxmuR5DAhZ1RSfIlkv7cdgh3EDNXXea9ZObEuI9MuFsIWUKODT5oKbRrsOWM6IqwNmx68VEW+xQm2DXyJw== +"@angular-devkit/core@7.2.3", "@angular-devkit/core@^7.0.4": + version "7.2.3" + resolved "https://registry.yarnpkg.com/@angular-devkit/core/-/core-7.2.3.tgz#03fad4edcbfbf5b2f0d35a121eca45b1273b238a" + integrity sha512-NnN8O+97nJAxqD2zVTDlU8dSzrGCZmqYMDqDoRJJChYxAgmGwP4lhb+Jyi5D34tPxgKRTnjTOwC+G7D+WrXSDQ== dependencies: - ajv "6.5.3" + ajv "6.6.2" chokidar "2.0.4" fast-json-stable-stringify "2.0.0" rxjs "6.3.3" @@ -51,12 +51,12 @@ rxjs "6.3.3" source-map "0.7.3" -"@angular-devkit/schematics@7.1.3": - version "7.1.3" - resolved "https://registry.yarnpkg.com/@angular-devkit/schematics/-/schematics-7.1.3.tgz#30d03fde5bb27d6606d9a6e055188382408670d6" - integrity sha512-Snmfog/n5k1PWdDaI+Top1F978vlXZFTvxHRPzlMCGhGsY+LMOpeRLVHADI+WP1q1LZ+2BjLELZVA2GP35AH8A== +"@angular-devkit/schematics@7.2.3": + version "7.2.3" + resolved "https://registry.yarnpkg.com/@angular-devkit/schematics/-/schematics-7.2.3.tgz#e85269fc44d87fd79314875084ecdecdc42020b5" + integrity sha512-zhnI1zxEcjx4OZ0yIzAAOujS8fzXE6nxjyC1viqliQbbjbikA6WIL7UT0jQXZKDsRBl8VXOyutBOaeOXuqktTQ== dependencies: - "@angular-devkit/core" "7.1.3" + "@angular-devkit/core" "7.2.3" rxjs "6.3.3" "@angular-devkit/schematics@7.3.0-rc.0", "@angular-devkit/schematics@^7.3.0-rc.0": @@ -139,13 +139,13 @@ typescript "3.2.2" "@schematics/angular@^7.0.4": - version "7.1.3" - resolved "https://registry.yarnpkg.com/@schematics/angular/-/angular-7.1.3.tgz#2cdbc6471358cf429fbea7ab192edc7246c87bd5" - integrity sha512-6Wq6vNjGTc0zmudPogTjiegtTUc0pORTCxI39iinUM+5iemMrCLYKJmYLi5mPFU4OG/Q2fnT06A3dYUorhtLkA== + version "7.2.3" + resolved "https://registry.yarnpkg.com/@schematics/angular/-/angular-7.2.3.tgz#e08baf642fad4ff7b27a2724379c4583a5a381b5" + integrity sha512-hKp+qaM8YU55+JukteXOVY2N5IQBDIIIXkyPcikbC2GBXUeNOMiPqw9au9sjHrgtFp+SVGoaFzzz9+MOCc1gig== dependencies: - "@angular-devkit/core" "7.1.3" - "@angular-devkit/schematics" "7.1.3" - typescript "3.1.6" + "@angular-devkit/core" "7.2.3" + "@angular-devkit/schematics" "7.2.3" + typescript "3.2.2" "@schematics/update@0.13.0-rc.0": version "0.13.0-rc.0" @@ -208,6 +208,16 @@ ajv@6.5.3: json-schema-traverse "^0.4.1" uri-js "^4.2.2" +ajv@6.6.2: + version "6.6.2" + resolved "https://registry.yarnpkg.com/ajv/-/ajv-6.6.2.tgz#caceccf474bf3fc3ce3b147443711a24063cc30d" + integrity sha512-FBHEW6Jf5TB9MGBgUUA9XHkTbjXYfAUjY43ACMfmdMRHniyoMHjHjzD50OK8LGDWQwp4rWEsIq5kEqq7rvIM1g== + dependencies: + fast-deep-equal "^2.0.1" + fast-json-stable-stringify "^2.0.0" + json-schema-traverse "^0.4.1" + uri-js "^4.2.2" + ajv@6.7.0: version "6.7.0" resolved "https://registry.yarnpkg.com/ajv/-/ajv-6.7.0.tgz#e3ce7bb372d6577bb1839f1dfdfcbf5ad2948d96" @@ -219,9 +229,9 @@ ajv@6.7.0: uri-js "^4.2.2" ansi-escapes@^3.0.0: - version "3.1.0" - resolved "https://registry.yarnpkg.com/ansi-escapes/-/ansi-escapes-3.1.0.tgz#f73207bb81207d75fd6c83f125af26eea378ca30" - integrity sha512-UgAb8H9D41AQnu/PbWlCofQVcnV4Gs2bBJi9eZPxfU/hgglFh3SMDMENRIqdr7H6XFnXdoknctFByVsCOotTVw== + version "3.2.0" + resolved "https://registry.yarnpkg.com/ansi-escapes/-/ansi-escapes-3.2.0.tgz#8780b98ff9dbf5638152d1f1fe5c1d7b4442976b" + integrity sha512-cBhpre4ma+U0T1oM5fXg7Dy1Jw7zzwv7lt/GoCpr+hDQJoYnKVPLL4dCvSEFMmQurOQvSrwT7SL/DAlhBI97RQ== ansi-regex@^2.0.0: version "2.1.1" @@ -383,27 +393,7 @@ bytebuffer@~5: dependencies: long "~3" -cacache@^11.0.1: - version "11.3.1" - resolved "https://registry.yarnpkg.com/cacache/-/cacache-11.3.1.tgz#d09d25f6c4aca7a6d305d141ae332613aa1d515f" - integrity sha512-2PEw4cRRDu+iQvBTTuttQifacYjLPhET+SYO/gEFMy8uhi+jlJREDAjSF5FWSdV/Aw5h18caHA7vMTw2c+wDzA== - dependencies: - bluebird "^3.5.1" - chownr "^1.0.1" - figgy-pudding "^3.1.0" - glob "^7.1.2" - graceful-fs "^4.1.11" - lru-cache "^4.1.3" - mississippi "^3.0.0" - mkdirp "^0.5.1" - move-concurrently "^1.0.1" - promise-inflight "^1.0.1" - rimraf "^2.6.2" - ssri "^6.0.0" - unique-filename "^1.1.0" - y18n "^4.0.0" - -cacache@^11.3.2: +cacache@^11.0.1, cacache@^11.3.2: version "11.3.2" resolved "https://registry.yarnpkg.com/cacache/-/cacache-11.3.2.tgz#2d81e308e3d258ca38125b676b98b2ac9ce69bfa" integrity sha512-E0zP4EPGDOaT2chM08Als91eYnf8Z+eH1awwwVsngUmgppfM5jjJ8l3z5vO5p5w/I3LsiXawb1sW0VY65pQABg== @@ -444,9 +434,9 @@ camelcase@^2.0.1: integrity sha1-fB0W1nmhu+WcoCys7PsBHiAfWh8= chalk@^2.0.0: - version "2.4.1" - resolved "https://registry.yarnpkg.com/chalk/-/chalk-2.4.1.tgz#18c49ab16a037b6eb0152cc83e3471338215b66e" - integrity sha512-ObN6h1v2fTJSmUXoS3nMQ92LbDK9be4TV+6G+omQlGJFdcUX5heKi1LZ1YnRMIgwTLEj3E24bT6tYni50rlCfQ== + version "2.4.2" + resolved "https://registry.yarnpkg.com/chalk/-/chalk-2.4.2.tgz#cd42541677a54333cf541a49108c1432b44c9424" + integrity sha512-Mti+f9lpJNcwF4tWV8/OrTTtF1gZi+f8FqlyAdouralcFWFQWF2+NgCHShjkCb+IFBLq9buZwE1xckQU4peSuQ== dependencies: ansi-styles "^3.2.1" escape-string-regexp "^1.0.5" @@ -477,7 +467,7 @@ chokidar@2.0.4: optionalDependencies: fsevents "^1.2.2" -chownr@^1.0.1, chownr@^1.1.1: +chownr@^1.1.1: version "1.1.1" resolved "https://registry.yarnpkg.com/chownr/-/chownr-1.1.1.tgz#54726b8b8fff4df053c42187e801fb4412df1494" integrity sha512-j38EvO5+LHX84jlo6h4UzmOwi0UgW61WRyPtJz4qaadK5eY3BTS5TY/S1Stc3Uk2lIM6TPevAlULiEJwie860g== @@ -770,7 +760,7 @@ fast-json-stable-stringify@2.0.0, fast-json-stable-stringify@^2.0.0: resolved "https://registry.yarnpkg.com/fast-json-stable-stringify/-/fast-json-stable-stringify-2.0.0.tgz#d5142c0caee6b1189f87d3a76111064f86c8bbf2" integrity sha1-1RQsDK7msRifh9OnYREGT4bIu/I= -figgy-pudding@^3.1.0, figgy-pudding@^3.4.1, figgy-pudding@^3.5.1: +figgy-pudding@^3.4.1, figgy-pudding@^3.5.1: version "3.5.1" resolved "https://registry.yarnpkg.com/figgy-pudding/-/figgy-pudding-3.5.1.tgz#862470112901c727a0e495a80744bd5baa1d6790" integrity sha512-vNKxJHTEKNThjfrdJwHc7brvM6eVevuO5nTj6ez8ZQ1qbXTvGthucRF7S4vf2cr71QVnT70V34v0S1DyQsti0w== @@ -843,9 +833,9 @@ fs.realpath@^1.0.0: integrity sha1-FQStJSMVjKpA20onh8sBQRmU6k8= fsevents@^1.2.2: - version "1.2.4" - resolved "https://registry.yarnpkg.com/fsevents/-/fsevents-1.2.4.tgz#f41dcb1af2582af3692da36fc55cbd8e1041c426" - integrity sha512-z8H8/diyk76B7q5wg+Ud0+CqzcAF3mBBI/bA5ne5zrRUUIvNkJY//D3BqyH571KuAC4Nr7Rw7CjWX4r0y9DvNg== + version "1.2.7" + resolved "https://registry.yarnpkg.com/fsevents/-/fsevents-1.2.7.tgz#4851b664a3783e52003b3c66eb0eee1074933aa4" + integrity sha512-Pxm6sI2MeBD7RdD12RYsqaP0nMiwx8eZBXCa6z2L+mRHm2DYrOYwihmhjpkdjUHwQhslWQjRpEgNq4XvBmaAuw== dependencies: nan "^2.9.2" node-pre-gyp "^0.10.0" @@ -889,7 +879,7 @@ glob-parent@^3.1.0: is-glob "^3.1.0" path-dirname "^1.0.0" -glob@^7.0.0, glob@^7.0.5, glob@^7.1.2, glob@^7.1.3: +glob@^7.0.0, glob@^7.0.5, glob@^7.1.3: version "7.1.3" resolved "https://registry.yarnpkg.com/glob/-/glob-7.1.3.tgz#3960832d3f1574108342dafd3a67b332c0969df1" integrity sha512-vcfuiIxogLV4DlGBHIUOwI0IbrJ8HWPc4MU7HzviGeNho/UJDfi6B5p3sHeWIQ0KGIU0Jpxi5ZHxemQfLkkAwQ== @@ -1042,9 +1032,9 @@ inquirer@6.2.1: through "^2.3.6" interpret@^1.0.0: - version "1.1.0" - resolved "https://registry.yarnpkg.com/interpret/-/interpret-1.1.0.tgz#7ed1b1410c6a0e0f78cf95d3b8440c63f78b8614" - integrity sha1-ftGxQQxqDg94z5XTuEQMY/eLhhQ= + version "1.2.0" + resolved "https://registry.yarnpkg.com/interpret/-/interpret-1.2.0.tgz#d5061a6224be58e8083985f5014d844359576296" + integrity sha512-mT34yGKMNceBQUoVn7iCDKDntA7SC6gycMAWzGx1z/CMCTV7b2AAtXlo3nRyHZ1FelRkQbQjprHSYGwzLtkVbw== invert-kv@^1.0.0: version "1.0.0" @@ -1435,9 +1425,9 @@ mute-stream@0.0.7: integrity sha1-MHXOk7whuPq0PhvE2n6BFe0ee6s= nan@^2.9.2: - version "2.11.1" - resolved "https://registry.yarnpkg.com/nan/-/nan-2.11.1.tgz#90e22bccb8ca57ea4cd37cc83d3819b52eea6766" - integrity sha512-iji6k87OSXa0CcrLl9z+ZiYSuR2o+c0bGuNmXdrhTQTakxytAFsC56SArGYoiHlJlFoHSnvmhpceZJaXkVuOtA== + version "2.12.1" + resolved "https://registry.yarnpkg.com/nan/-/nan-2.12.1.tgz#7b1aa193e9aa86057e3c7bbd0ac448e770925552" + integrity sha512-JY7V6lRkStKcKTvHO5NVSQRv+RV+FIL5pvDoLiAtSL9pKlC5x9PKQcZDsq7m4FO4d57mkhC6Z+QhAh3Jdk5JFw== nanomatch@^1.2.9: version "1.2.13" @@ -1531,9 +1521,9 @@ npm-package-arg@6.1.0, npm-package-arg@^6.0.0, npm-package-arg@^6.1.0: validate-npm-package-name "^3.0.0" npm-packlist@^1.1.12, npm-packlist@^1.1.6: - version "1.1.12" - resolved "https://registry.yarnpkg.com/npm-packlist/-/npm-packlist-1.1.12.tgz#22bde2ebc12e72ca482abd67afc51eb49377243a" - integrity sha512-WJKFOVMeAlsU/pjXuqVdzU0WfgtIBCupkEVwn+1Y0ERAbUfWw8R4GjgVbaKnUjRoD2FoQbHOCbOyT5Mbs9Lw4g== + version "1.2.0" + resolved "https://registry.yarnpkg.com/npm-packlist/-/npm-packlist-1.2.0.tgz#55a60e793e272f00862c7089274439a4cc31fc7f" + integrity sha512-7Mni4Z8Xkx0/oegoqlcao/JpPCPEMtUvsmB0q7mgvlMinykJLSRTYuFqoQLYgGY8biuxIeiHO+QNJKbCfljewQ== dependencies: ignore-walk "^3.0.1" npm-bundled "^1.0.1" @@ -1548,9 +1538,9 @@ npm-pick-manifest@^2.2.3: semver "^5.4.1" npm-registry-fetch@^3.8.0: - version "3.8.0" - resolved "https://registry.yarnpkg.com/npm-registry-fetch/-/npm-registry-fetch-3.8.0.tgz#aa7d9a7c92aff94f48dba0984bdef4bd131c88cc" - integrity sha512-hrw8UMD+Nob3Kl3h8Z/YjmKamb1gf7D1ZZch2otrIXM3uFLB5vjEY6DhMlq80z/zZet6eETLbOXcuQudCB3Zpw== + version "3.9.0" + resolved "https://registry.yarnpkg.com/npm-registry-fetch/-/npm-registry-fetch-3.9.0.tgz#44d841780e2833f06accb34488f8c7450d1a6856" + integrity sha512-srwmt8YhNajAoSAaDWndmZgx89lJwIZ1GWxOuckH4Coek4uHv5S+o/l9FLQe/awA+JwTnj4FJHldxhlXdZEBmw== dependencies: JSONStream "^1.3.4" bluebird "^3.5.1" @@ -1710,7 +1700,7 @@ path-is-absolute@^1.0.0: resolved "https://registry.yarnpkg.com/path-is-absolute/-/path-is-absolute-1.0.1.tgz#174b9268735534ffbc7ace6bf53a5a9e1b5c5f5f" integrity sha1-F0uSaHNVNP+8es5r9TpanhtcX18= -path-parse@^1.0.5: +path-parse@^1.0.6: version "1.0.6" resolved "https://registry.yarnpkg.com/path-parse/-/path-parse-1.0.6.tgz#d62dbb5679405d72c4737ec58600e9ddcf06d24c" integrity sha512-GSmOT2EbHrINBf9SR7CDELwlJ8AENk3Qn7OikK4nFYAu3Ote2+JYNVvkpAEQm3/TLNEJFD/xZJjzyxg3KBWOzw== @@ -1858,11 +1848,11 @@ resolve-url@^0.2.1: integrity sha1-LGN/53yJOv0qZj/iGqkIAGjiBSo= resolve@^1.1.6: - version "1.8.1" - resolved "https://registry.yarnpkg.com/resolve/-/resolve-1.8.1.tgz#82f1ec19a423ac1fbd080b0bab06ba36e84a7a26" - integrity sha512-AicPrAC7Qu1JxPCZ9ZgCZlY35QgFnNqc+0LtbRNxnVw4TXvjQ72wnuL9JQcEBgXkI9JM8MsT9kaQoHcpCRJOYA== + version "1.10.0" + resolved "https://registry.yarnpkg.com/resolve/-/resolve-1.10.0.tgz#3bdaaeaf45cc07f375656dfd2e54ed0810b101ba" + integrity sha512-3sUr9aq5OfSg2S9pNtPA9hL1FVEAjvfOC4leW0SNf/mpnaakz2a9femSd6LqAww2RaFctwyf1lCqnTHuF1rxDg== dependencies: - path-parse "^1.0.5" + path-parse "^1.0.6" restore-cursor@^2.0.0: version "2.0.0" @@ -1883,11 +1873,11 @@ retry@^0.10.0: integrity sha1-52OI0heZLCUnUCQdPTlW/tmNj/Q= rimraf@^2.5.4, rimraf@^2.6.1, rimraf@^2.6.2: - version "2.6.2" - resolved "https://registry.yarnpkg.com/rimraf/-/rimraf-2.6.2.tgz#2ed8150d24a16ea8651e6d6ef0f47c4158ce7a36" - integrity sha512-lreewLK/BlghmxtfH36YYVg1i8IAce4TI7oao75I1g245+6BctqTVQiBP3YUJ9C6DQOXJmkYR9X9fCLtCOJc5w== + version "2.6.3" + resolved "https://registry.yarnpkg.com/rimraf/-/rimraf-2.6.3.tgz#b2d104fe0d8fb27cf9e0a1cda8262dd3833c6cab" + integrity sha512-mwqeW5XsA2qAejG46gYdENaxXjx9onRNCfn7L0duuP4hCuTIi/QO7PDK07KJfp1d+izWPrzEJDcSqBa0OZQriA== dependencies: - glob "^7.0.5" + glob "^7.1.3" run-async@^2.2.0: version "2.3.0" @@ -1983,10 +1973,10 @@ signal-exit@^3.0.0, signal-exit@^3.0.2: resolved "https://registry.yarnpkg.com/signal-exit/-/signal-exit-3.0.2.tgz#b5fdc08f1287ea1178628e415e25132b73646c6d" integrity sha1-tf3AjxKH6hF4Yo5BXiUTK3NkbG0= -smart-buffer@^4.0.1: - version "4.0.1" - resolved "https://registry.yarnpkg.com/smart-buffer/-/smart-buffer-4.0.1.tgz#07ea1ca8d4db24eb4cac86537d7d18995221ace3" - integrity sha512-RFqinRVJVcCAL9Uh1oVqE6FZkqsyLiVOYEZ20TqIOjuX7iFVJ+zsbs4RIghnw/pTs7mZvt8ZHhvm1ZUrR4fykg== +smart-buffer@4.0.2: + version "4.0.2" + resolved "https://registry.yarnpkg.com/smart-buffer/-/smart-buffer-4.0.2.tgz#5207858c3815cc69110703c6b94e46c15634395d" + integrity sha512-JDhEpTKzXusOqXZ0BUIdH+CjFdO/CR3tLlf5CN34IypI+xMmXW1uB16OOY8z3cICbJlDAVJzNbwBhNO0wt9OAw== snapdragon-node@^2.0.1: version "2.1.1" @@ -2027,12 +2017,12 @@ socks-proxy-agent@^4.0.0: socks "~2.2.0" socks@~2.2.0: - version "2.2.2" - resolved "https://registry.yarnpkg.com/socks/-/socks-2.2.2.tgz#f061219fc2d4d332afb4af93e865c84d3fa26e2b" - integrity sha512-g6wjBnnMOZpE0ym6e0uHSddz9p3a+WsBaaYQaBaSCJYvrC4IXykQR9MNGjLQf38e9iIIhp3b1/Zk8YZI3KGJ0Q== + version "2.2.3" + resolved "https://registry.yarnpkg.com/socks/-/socks-2.2.3.tgz#7399ce11e19b2a997153c983a9ccb6306721f2dc" + integrity sha512-+2r83WaRT3PXYoO/1z+RDEBE7Z2f9YcdQnJ0K/ncXXbV5gJ6wYfNAebYFYiiUjM6E4JyXnPY8cimwyvFYHVUUA== dependencies: ip "^1.1.5" - smart-buffer "^4.0.1" + smart-buffer "4.0.2" source-map-resolve@^0.5.0: version "0.5.2" @@ -2095,9 +2085,9 @@ spdx-expression-parse@^3.0.0: spdx-license-ids "^3.0.0" spdx-license-ids@^3.0.0: - version "3.0.2" - resolved "https://registry.yarnpkg.com/spdx-license-ids/-/spdx-license-ids-3.0.2.tgz#a59efc09784c2a5bada13cfeaf5c75dd214044d2" - integrity sha512-qky9CVt0lVIECkEsYbNILVnPvycuEBkXoMFLRWsREkomQLevYhtRKC+R91a5TOAQ3bCMjikRwhyaRqj1VYatYg== + version "3.0.3" + resolved "https://registry.yarnpkg.com/spdx-license-ids/-/spdx-license-ids-3.0.3.tgz#81c0ce8f21474756148bbb5f3bfc0f36bf15d76e" + integrity sha512-uBIcIl3Ih6Phe3XHK1NqboJLdGfwr1UN3k6wSD1dZpmPsIkb8AGNbZYJ1fOBk834+Gxy8rpfDxrS6XLEMZMY2g== split-string@^3.0.1, split-string@^3.0.2: version "3.1.0" @@ -2280,11 +2270,6 @@ typedarray@^0.0.6: resolved "https://registry.yarnpkg.com/typedarray/-/typedarray-0.0.6.tgz#867ac74e3864187b1d3d47d996a78ec5c8830777" integrity sha1-hnrHTjhkGHsdPUfZlqeOxciDB3c= -typescript@3.1.6: - version "3.1.6" - resolved "https://registry.yarnpkg.com/typescript/-/typescript-3.1.6.tgz#b6543a83cfc8c2befb3f4c8fba6896f5b0c9be68" - integrity sha512-tDMYfVtvpb96msS1lDX9MEdHrW4yOuZ4Kdc4Him9oU796XldPYF/t2+uKoX0BBa0hXXwDlqYQbXY5Rzjzc5hBA== - typescript@3.2.2: version "3.2.2" resolved "https://registry.yarnpkg.com/typescript/-/typescript-3.2.2.tgz#fe8101c46aa123f8353523ebdcf5730c2ae493e5" @@ -2300,7 +2285,7 @@ union-value@^1.0.0: is-extendable "^0.1.1" set-value "^0.4.3" -unique-filename@^1.1.0, unique-filename@^1.1.1: +unique-filename@^1.1.1: version "1.1.1" resolved "https://registry.yarnpkg.com/unique-filename/-/unique-filename-1.1.1.tgz#1d69769369ada0583103a1e6ae87681b56573230" integrity sha512-Vmp0jIp2ln35UTXuryvjzkjGdRyf9b2lTXuSYUiPmzRcl3FDtYqAwOnTJkAngD9SWhnoJzDbTKwaOrZ+STtxNQ== diff --git a/packages/bazel/BUILD.bazel b/packages/bazel/BUILD.bazel index 03f97b9fe0..31c10a2cde 100644 --- a/packages/bazel/BUILD.bazel +++ b/packages/bazel/BUILD.bazel @@ -23,6 +23,7 @@ npm_package( "//packages/bazel/src/ngc-wrapped:ngc_lib", "//packages/bazel/src/protractor/utils", "//packages/bazel/src/schematics/bazel-workspace", + "//packages/bazel/src/schematics/ng-add", "//packages/bazel/src/schematics/ng-new", ], ) diff --git a/packages/bazel/src/schematics/BUILD.bazel b/packages/bazel/src/schematics/BUILD.bazel index 5770199cc3..bc65567b2d 100644 --- a/packages/bazel/src/schematics/BUILD.bazel +++ b/packages/bazel/src/schematics/BUILD.bazel @@ -15,6 +15,7 @@ jasmine_node_test( bootstrap = ["angular/tools/testing/init_node_spec.js"], deps = [ "//packages/bazel/src/schematics/bazel-workspace:test", + "//packages/bazel/src/schematics/ng-add:test", "//packages/bazel/src/schematics/ng-new:test", "//tools/testing:node", ], diff --git a/packages/bazel/src/schematics/bazel-workspace/files/yarn.lock b/packages/bazel/src/schematics/bazel-workspace/files/yarn.lock deleted file mode 100644 index e69de29bb2..0000000000 diff --git a/packages/bazel/src/schematics/bazel-workspace/index.ts b/packages/bazel/src/schematics/bazel-workspace/index.ts index c2cd5f65df..f5f73bae3a 100644 --- a/packages/bazel/src/schematics/bazel-workspace/index.ts +++ b/packages/bazel/src/schematics/bazel-workspace/index.ts @@ -86,6 +86,10 @@ export default function(options: BazelWorkspaceOptions): Rule { } }); + if (!host.exists('yarn.lock')) { + host.create('yarn.lock', ''); + } + const workspaceVersions = { 'RULES_NODEJS_VERSION': '0.16.5', 'RULES_TYPESCRIPT_VERSION': '0.22.1', diff --git a/packages/bazel/src/schematics/bazel-workspace/index_spec.ts b/packages/bazel/src/schematics/bazel-workspace/index_spec.ts index 77b9fc6062..056f1c6ed5 100644 --- a/packages/bazel/src/schematics/bazel-workspace/index_spec.ts +++ b/packages/bazel/src/schematics/bazel-workspace/index_spec.ts @@ -29,17 +29,19 @@ describe('Bazel-workspace Schematic', () => { expect(files).toContain('/yarn.lock'); }); - it('should find existing Angular version', () => { - let host = new UnitTestTree(new HostTree); - host.create('/node_modules/@angular/core/package.json', JSON.stringify({ - name: '@angular/core', - version: '6.6.6', - })); - const options = {...defaultOptions}; - host = schematicRunner.runSchematic('bazel-workspace', options, host); - expect(host.files).toContain('/WORKSPACE'); - const workspace = host.readContent('/WORKSPACE'); - expect(workspace).toMatch('ANGULAR_VERSION = "6.6.6"'); + it('should generate empty yarn.lock file', () => { + const host = schematicRunner.runSchematic('bazel-workspace', defaultOptions); + expect(host.files).toContain('/yarn.lock'); + expect(host.readContent('/yarn.lock')).toBe(''); + }); + + it('should not replace yarn.lock if it exists', () => { + let host = new UnitTestTree(new HostTree()); + host.create('yarn.lock', 'some content'); + expect(host.files).toContain('/yarn.lock'); + host = schematicRunner.runSchematic('bazel-workspace', defaultOptions, host); + expect(host.files).toContain('/yarn.lock'); + expect(host.readContent('/yarn.lock')).toBe('some content'); }); it('should have the correct entry_module for devserver', () => { diff --git a/packages/bazel/src/schematics/collection.json b/packages/bazel/src/schematics/collection.json index 5e5d5eff46..534f02cd62 100644 --- a/packages/bazel/src/schematics/collection.json +++ b/packages/bazel/src/schematics/collection.json @@ -1,17 +1,22 @@ { - "name": "@angular/bazel", - "version": "0.1", - "schematics": { - "ng-new": { - "factory": "./ng-new", - "schema": "./ng-new/schema.json", - "description": "Create an Angular project that builds with Bazel." - }, - "bazel-workspace": { - "factory": "./bazel-workspace", - "schema": "./bazel-workspace/schema.json", - "description": "Setup Bazel workspace", - "hidden": true - } + "name": "@angular/bazel", + "version": "0.1", + "schematics": { + "ng-add": { + "factory": "./ng-add", + "schema": "ng-add/schema.json", + "description": "Add Bazel build files and configurations to a project" + }, + "ng-new": { + "factory": "./ng-new", + "schema": "./ng-new/schema.json", + "description": "Create an Angular project that builds with Bazel." + }, + "bazel-workspace": { + "factory": "./bazel-workspace", + "schema": "./bazel-workspace/schema.json", + "description": "Setup Bazel workspace", + "hidden": true } } +} diff --git a/packages/bazel/src/schematics/ng-add/BUILD.bazel b/packages/bazel/src/schematics/ng-add/BUILD.bazel new file mode 100644 index 0000000000..a9fe9e35fe --- /dev/null +++ b/packages/bazel/src/schematics/ng-add/BUILD.bazel @@ -0,0 +1,36 @@ +package(default_visibility = ["//visibility:public"]) + +load("//tools:defaults.bzl", "ts_library") + +ts_library( + name = "ng-add", + srcs = [ + "index.ts", + "schema.d.ts", + ], + data = glob(["files/**/*"]) + [ + "schema.json", + ], + deps = [ + "//packages/bazel/src/schematics/bazel-workspace", + "@ngdeps//@angular-devkit/core", + "@ngdeps//@angular-devkit/schematics", + "@ngdeps//@schematics/angular", + "@ngdeps//typescript", + ], +) + +ts_library( + name = "test", + testonly = True, + srcs = [ + "index_spec.ts", + ], + data = [ + "//packages/bazel/src/schematics:package_assets", + ], + deps = [ + ":ng-add", + "@ngdeps//@angular-devkit/schematics", + ], +) diff --git a/packages/bazel/src/schematics/ng-new/files/main.dev.ts.template b/packages/bazel/src/schematics/ng-add/files/main.dev.ts.template similarity index 100% rename from packages/bazel/src/schematics/ng-new/files/main.dev.ts.template rename to packages/bazel/src/schematics/ng-add/files/main.dev.ts.template diff --git a/packages/bazel/src/schematics/ng-new/files/main.prod.ts.template b/packages/bazel/src/schematics/ng-add/files/main.prod.ts.template similarity index 100% rename from packages/bazel/src/schematics/ng-new/files/main.prod.ts.template rename to packages/bazel/src/schematics/ng-add/files/main.prod.ts.template diff --git a/packages/bazel/src/schematics/ng-add/index.ts b/packages/bazel/src/schematics/ng-add/index.ts new file mode 100755 index 0000000000..8ae2acba3c --- /dev/null +++ b/packages/bazel/src/schematics/ng-add/index.ts @@ -0,0 +1,232 @@ +/** + * @license + * Copyright Google Inc. All Rights Reserved. + * + * Use of this source code is governed by an MIT-style license that can be + * found in the LICENSE file at https://angular.io/license + * + * @fileoverview Schematics for ng-new project that builds with Bazel. + */ + +import {SchematicContext, apply, applyTemplates, chain, mergeWith, move, Rule, schematic, Tree, url, SchematicsException, UpdateRecorder,} from '@angular-devkit/schematics'; +import {parseJsonAst, JsonAstObject, strings, JsonValue} from '@angular-devkit/core'; +import {findPropertyInAstObject, insertPropertyInAstObjectInOrder} from '@schematics/angular/utility/json-utils'; +import {validateProjectName} from '@schematics/angular/utility/validation'; +import {getWorkspacePath} from '@schematics/angular/utility/config'; +import {Schema} from './schema'; + +/** + * Packages that build under Bazel require additional dev dependencies. This + * function adds those dependencies to "devDependencies" section in + * package.json. + */ +function addDevDependenciesToPackageJson(options: Schema) { + return (host: Tree) => { + const packageJson = 'package.json'; + if (!host.exists(packageJson)) { + throw new Error(`Could not find ${packageJson}`); + } + const packageJsonContent = host.read(packageJson); + if (!packageJsonContent) { + throw new Error('Failed to read package.json content'); + } + const jsonAst = parseJsonAst(packageJsonContent.toString()) as JsonAstObject; + const deps = findPropertyInAstObject(jsonAst, 'dependencies') as JsonAstObject; + const devDeps = findPropertyInAstObject(jsonAst, 'devDependencies') as JsonAstObject; + + const angularCoreNode = findPropertyInAstObject(deps, '@angular/core'); + if (!angularCoreNode) { + throw new Error('@angular/core dependency not found in package.json'); + } + const angularCoreVersion = angularCoreNode.value as string; + + const devDependencies: {[k: string]: string} = { + '@angular/bazel': angularCoreVersion, + // TODO(kyliau): Consider moving this to latest-versions.ts + '@bazel/bazel': '^0.22.1', + '@bazel/ibazel': '^0.9.0', + '@bazel/karma': '^0.22.1', + '@bazel/typescript': '^0.22.1', + }; + + const recorder = host.beginUpdate(packageJson); + for (const packageName of Object.keys(devDependencies)) { + const version = devDependencies[packageName]; + const indent = 4; + insertPropertyInAstObjectInOrder(recorder, devDeps, packageName, version, indent); + } + host.commitUpdate(recorder); + return host; + }; +} + +/** + * Append main.dev.ts and main.prod.ts to src directory. These files are needed + * by Bazel for devserver and prodserver, respectively. They are different from + * main.ts generated by CLI because they use platformBrowser (AOT) instead of + * platformBrowserDynamic (JIT). + */ +function addDevAndProdMainForAot(options: Schema) { + return (host: Tree) => { + return mergeWith(apply(url('./files'), [ + applyTemplates({ + utils: strings, + ...options, + 'dot': '.', + }), + move('/src'), + ])); + }; +} + +/** + * Append '/bazel-out' to the gitignore file. + */ +function updateGitignore() { + return (host: Tree) => { + const gitignore = '/.gitignore'; + if (!host.exists(gitignore)) { + return host; + } + const gitIgnoreContent = host.read(gitignore).toString(); + if (gitIgnoreContent.includes('\n/bazel-out\n')) { + return host; + } + const compiledOutput = '# compiled output\n'; + const index = gitIgnoreContent.indexOf(compiledOutput); + const insertionIndex = index >= 0 ? index + compiledOutput.length : gitIgnoreContent.length; + const recorder = host.beginUpdate(gitignore); + recorder.insertRight(insertionIndex, '/bazel-out\n'); + host.commitUpdate(recorder); + return host; + }; +} + +function replacePropertyInAstObject( + recorder: UpdateRecorder, node: JsonAstObject, propertyName: string, value: JsonValue, + indent: number) { + const property = findPropertyInAstObject(node, propertyName); + if (property === null) { + throw new Error(`Property ${propertyName} does not exist in JSON object`); + } + const {start, text} = property; + recorder.remove(start.offset, text.length); + const indentStr = '\n' + + ' '.repeat(indent); + const content = JSON.stringify(value, null, ' ').replace(/\n/g, indentStr); + recorder.insertLeft(start.offset, content); +} + +function updateAngularJsonToUseBazelBuilder(options: Schema): Rule { + return (host: Tree, context: SchematicContext) => { + const {name} = options; + const workspacePath = getWorkspacePath(host); + if (!workspacePath) { + throw new Error('Could not find angular.json'); + } + const workspaceContent = host.read(workspacePath).toString(); + const workspaceJsonAst = parseJsonAst(workspaceContent) as JsonAstObject; + const projects = findPropertyInAstObject(workspaceJsonAst, 'projects'); + if (!projects) { + throw new SchematicsException('Expect projects in angular.json to be an Object'); + } + const project = findPropertyInAstObject(projects as JsonAstObject, name); + if (!project) { + throw new SchematicsException(`Expected projects to contain ${name}`); + } + const recorder = host.beginUpdate(workspacePath); + const indent = 8; + const architect = + findPropertyInAstObject(project as JsonAstObject, 'architect') as JsonAstObject; + replacePropertyInAstObject( + recorder, architect, 'build', { + builder: '@angular/bazel:build', + options: { + targetLabel: '//src:bundle.js', + bazelCommand: 'build', + }, + configurations: { + production: { + targetLabel: '//src:bundle', + }, + }, + }, + indent); + replacePropertyInAstObject( + recorder, architect, 'serve', { + builder: '@angular/bazel:build', + options: { + targetLabel: '//src:devserver', + bazelCommand: 'run', + }, + configurations: { + production: { + targetLabel: '//src:prodserver', + }, + }, + }, + indent); + replacePropertyInAstObject( + recorder, architect, 'test', { + builder: '@angular/bazel:build', + options: {'bazelCommand': 'test', 'targetLabel': '//src/...'}, + }, + indent); + + const e2e = `${options.name}-e2e`; + const e2eNode = findPropertyInAstObject(projects as JsonAstObject, e2e); + if (e2eNode) { + const architect = + findPropertyInAstObject(e2eNode as JsonAstObject, 'architect') as JsonAstObject; + replacePropertyInAstObject( + recorder, architect, 'e2e', { + builder: '@angular/bazel:build', + options: { + bazelCommand: 'test', + targetLabel: '//e2e:devserver_test', + }, + configurations: { + production: { + targetLabel: '//e2e:prodserver_test', + }, + } + }, + indent); + } + + host.commitUpdate(recorder); + return host; + }; +} + +/** + * Create a backup for the original angular.json file in case user wants to + * eject Bazel and revert to the original workflow. + */ +function backupAngularJson(): Rule { + return (host: Tree, context: SchematicContext) => { + const workspacePath = getWorkspacePath(host); + if (!workspacePath) { + return; + } + host.create( + `${workspacePath}.bak`, '// This is a backup file of the original angular.json. ' + + 'This file is needed in case you want to revert to the workflow without Bazel.\n\n' + + host.read(workspacePath)); + }; +} + +export default function(options: Schema): Rule { + return (host: Tree) => { + validateProjectName(options.name); + + return chain([ + schematic('bazel-workspace', options), + addDevAndProdMainForAot(options), + addDevDependenciesToPackageJson(options), + backupAngularJson(), + updateAngularJsonToUseBazelBuilder(options), + updateGitignore(), + ]); + }; +} diff --git a/packages/bazel/src/schematics/ng-add/index_spec.ts b/packages/bazel/src/schematics/ng-add/index_spec.ts new file mode 100644 index 0000000000..2120153c9a --- /dev/null +++ b/packages/bazel/src/schematics/ng-add/index_spec.ts @@ -0,0 +1,171 @@ +/** + * @license + * Copyright Google Inc. All Rights Reserved. + * + * Use of this source code is governed by an MIT-style license that can be + * found in the LICENSE file at https://angular.io/license + */ + +import {HostTree} from '@angular-devkit/schematics'; +import {SchematicTestRunner, UnitTestTree} from '@angular-devkit/schematics/testing'; + +describe('ng-add schematic', () => { + + const defaultOptions = {name: 'demo'}; + let host: UnitTestTree; + let schematicRunner: SchematicTestRunner; + + beforeEach(() => { + host = new UnitTestTree(new HostTree()); + host.create('package.json', JSON.stringify({ + name: 'demo', + dependencies: { + '@angular/core': '1.2.3', + }, + devDependencies: { + 'typescript': '3.2.2', + }, + })); + host.create('angular.json', JSON.stringify({ + projects: { + 'demo': { + architect: { + build: {}, + serve: {}, + test: {}, + 'extract-i18n': { + builder: '@angular-devkit/build-angular:extract-i18n', + }, + }, + }, + 'demo-e2e': { + architect: { + e2e: {}, + lint: { + builder: '@angular-devkit/build-angular:tslint', + }, + }, + }, + }, + })); + schematicRunner = + new SchematicTestRunner('@angular/bazel', require.resolve('../collection.json')); + }); + + it('throws if package.json is not found', () => { + expect(host.files).toContain('/package.json'); + host.delete('/package.json'); + expect(() => schematicRunner.runSchematic('ng-add', defaultOptions)) + .toThrowError('Could not find package.json'); + }); + + it('throws if angular.json is not found', () => { + expect(host.files).toContain('/angular.json'); + host.delete('/angular.json'); + expect(() => schematicRunner.runSchematic('ng-add', defaultOptions, host)) + .toThrowError('Could not find angular.json'); + }); + + it('should add @angular/bazel to package.json dependencies', () => { + host = schematicRunner.runSchematic('ng-add', defaultOptions, host); + const {files} = host; + expect(files).toContain('/package.json'); + const content = host.readContent('/package.json'); + expect(() => JSON.parse(content)).not.toThrow(); + const json = JSON.parse(content); + const core = '@angular/core'; + const bazel = '@angular/bazel'; + expect(Object.keys(json)).toContain('dependencies'); + expect(Object.keys(json)).toContain('devDependencies'); + expect(Object.keys(json.dependencies)).toContain(core); + expect(Object.keys(json.devDependencies)).toContain(bazel); + expect(json.dependencies[core]).toBe(json.devDependencies[bazel]); + }); + + it('should add @bazel/* dev dependencies', () => { + host = schematicRunner.runSchematic('ng-add', defaultOptions, host); + const content = host.readContent('/package.json'); + const json = JSON.parse(content); + const devDeps = Object.keys(json.devDependencies); + expect(devDeps).toContain('@bazel/bazel'); + expect(devDeps).toContain('@bazel/ibazel'); + expect(devDeps).toContain('@bazel/karma'); + expect(devDeps).toContain('@bazel/typescript'); + }); + + it('should create Bazel workspace file', () => { + host = schematicRunner.runSchematic('ng-add', defaultOptions, host); + const {files} = host; + expect(files).toContain('/WORKSPACE'); + expect(files).toContain('/BUILD.bazel'); + }); + + it('should produce main.dev.ts and main.prod.ts for AOT', () => { + host.create('/src/main.ts', 'generated by CLI'); + host = schematicRunner.runSchematic('ng-add', defaultOptions, host); + const {files} = host; + // main.dev.ts and main.prod.ts are used by Bazel for AOT + expect(files).toContain('/src/main.dev.ts'); + expect(files).toContain('/src/main.prod.ts'); + // main.ts is produced by original ng-add schematics + // This file should be present for backwards compatibility. + expect(files).toContain('/src/main.ts'); + }); + + it('should not overwrite index.html with script tags', () => { + host.create('/src/index.html', 'Hello World'); + host = schematicRunner.runSchematic('ng-add', defaultOptions, host); + const {files} = host; + expect(files).toContain('/src/index.html'); + const content = host.readContent('/src/index.html'); + expect(content).not.toMatch(''); + expect(content).not.toMatch(''); + }); + + it('should generate main.dev.ts and main.prod.ts', () => { + host = schematicRunner.runSchematic('ng-add', defaultOptions, host); + const {files} = host; + expect(files).toContain('/src/main.dev.ts'); + expect(files).toContain('/src/main.prod.ts'); + }); + + it('should overwrite .gitignore for bazel-out directory', () => { + host.create('.gitignore', '\n# compiled output\n'); + host = schematicRunner.runSchematic('ng-add', defaultOptions, host); + const {files} = host; + expect(files).toContain('/.gitignore'); + const content = host.readContent('/.gitignore'); + expect(content).toMatch('\n# compiled output\n/bazel-out\n'); + }); + + it('should create a backup for original angular.json', () => { + expect(host.files).toContain('/angular.json'); + const original = host.readContent('/angular.json'); + host = schematicRunner.runSchematic('ng-add', defaultOptions, host); + expect(host.files).toContain('/angular.json.bak'); + const content = host.readContent('/angular.json.bak'); + expect(content.startsWith('// This is a backup file')).toBe(true); + expect(content).toMatch(original); + }); + + it('should update angular.json to use Bazel builder', () => { + host = schematicRunner.runSchematic('ng-add', defaultOptions, host); + const {files} = host; + expect(files).toContain('/angular.json'); + const content = host.readContent('/angular.json'); + expect(() => JSON.parse(content)).not.toThrow(); + const json = JSON.parse(content); + const demo = json.projects.demo; + const demo_e2e = json.projects['demo-e2e']; + const {build, serve, test} = demo.architect; + expect(build.builder).toBe('@angular/bazel:build'); + expect(serve.builder).toBe('@angular/bazel:build'); + expect(test.builder).toBe('@angular/bazel:build'); + const {e2e, lint} = demo_e2e.architect; + expect(e2e.builder).toBe('@angular/bazel:build'); + // it should leave non-Bazel commands unmodified + expect(demo.architect['extract-i18n'].builder) + .toBe('@angular-devkit/build-angular:extract-i18n'); + expect(lint.builder).toBe('@angular-devkit/build-angular:tslint'); + }); +}); diff --git a/packages/bazel/src/schematics/ng-add/schema.d.ts b/packages/bazel/src/schematics/ng-add/schema.d.ts new file mode 100644 index 0000000000..4e0ad890ab --- /dev/null +++ b/packages/bazel/src/schematics/ng-add/schema.d.ts @@ -0,0 +1,13 @@ + +// THIS FILE IS AUTOMATICALLY GENERATED. TO UPDATE THIS FILE YOU NEED TO CHANGE +// THE CORRESPONDING JSON SCHEMA FILE. See README.md. + +// tslint:disable:no-global-tslint-disable +// tslint:disable + +export interface Schema { + /** + * The name of the project. + */ + name: string; +} diff --git a/packages/bazel/src/schematics/ng-add/schema.json b/packages/bazel/src/schematics/ng-add/schema.json new file mode 100755 index 0000000000..dbfb4ab203 --- /dev/null +++ b/packages/bazel/src/schematics/ng-add/schema.json @@ -0,0 +1,20 @@ +{ + "$schema": "http://json-schema.org/schema", + "id": "SchematicsAngularBazelNgAdd", + "title": "Angular Bazel Ng Add Schema", + "type": "object", + "properties": { + "name": { + "description": "The name of the project.", + "type": "string", + "format": "html-selector", + "$default": { + "$source": "argv", + "index": 0 + } + } + }, + "required": [ + "name" + ] +} diff --git a/packages/bazel/src/schematics/ng-new/BUILD.bazel b/packages/bazel/src/schematics/ng-new/BUILD.bazel index d70cab250f..6227329322 100644 --- a/packages/bazel/src/schematics/ng-new/BUILD.bazel +++ b/packages/bazel/src/schematics/ng-new/BUILD.bazel @@ -12,11 +12,9 @@ ts_library( "schema.json", ], deps = [ - "//packages/bazel/src/schematics/bazel-workspace", - "@ngdeps//@angular-devkit/core", + "//packages/bazel/src/schematics/ng-add", "@ngdeps//@angular-devkit/schematics", "@ngdeps//@schematics/angular", - "@ngdeps//typescript", ], ) diff --git a/packages/bazel/src/schematics/ng-new/index.ts b/packages/bazel/src/schematics/ng-new/index.ts old mode 100755 new mode 100644 index fe3e4adc10..e132ffec6f --- a/packages/bazel/src/schematics/ng-new/index.ts +++ b/packages/bazel/src/schematics/ng-new/index.ts @@ -8,211 +8,19 @@ * @fileoverview Schematics for ng-new project that builds with Bazel. */ -import {SchematicContext, apply, applyTemplates, chain, externalSchematic, MergeStrategy, mergeWith, move, Rule, schematic, Tree, url, SchematicsException, UpdateRecorder,} from '@angular-devkit/schematics'; -import {parseJsonAst, JsonAstObject, strings, JsonValue} from '@angular-devkit/core'; -import {findPropertyInAstObject, insertPropertyInAstObjectInOrder} from '@schematics/angular/utility/json-utils'; +import {Rule, Tree, chain, externalSchematic, schematic} from '@angular-devkit/schematics'; import {validateProjectName} from '@schematics/angular/utility/validation'; -import {getWorkspace} from '@schematics/angular/utility/config'; import {Schema} from './schema'; -/** - * Packages that build under Bazel require additional dev dependencies. This - * function adds those dependencies to "devDependencies" section in - * package.json. - */ -function addDevDependenciesToPackageJson(options: Schema) { - return (host: Tree) => { - const packageJson = `${options.name}/package.json`; - - if (!host.exists(packageJson)) { - throw new Error(`Could not find ${packageJson}`); - } - const packageJsonContent = host.read(packageJson); - if (!packageJsonContent) { - throw new Error('Failed to read package.json content'); - } - const jsonAst = parseJsonAst(packageJsonContent.toString()) as JsonAstObject; - const deps = findPropertyInAstObject(jsonAst, 'dependencies') as JsonAstObject; - const devDeps = findPropertyInAstObject(jsonAst, 'devDependencies') as JsonAstObject; - - const angularCoreNode = findPropertyInAstObject(deps, '@angular/core'); - const angularCoreVersion = angularCoreNode !.value as string; - - const devDependencies: {[k: string]: string} = { - '@angular/bazel': angularCoreVersion, - // TODO(kyliau): Consider moving this to latest-versions.ts - '@bazel/bazel': '^0.22.1', - '@bazel/ibazel': '^0.9.0', - '@bazel/karma': '^0.22.1', - '@bazel/typescript': '^0.22.1', - }; - - const recorder = host.beginUpdate(packageJson); - for (const packageName of Object.keys(devDependencies)) { - const version = devDependencies[packageName]; - const indent = 4; - insertPropertyInAstObjectInOrder(recorder, devDeps, packageName, version, indent); - } - host.commitUpdate(recorder); - return host; - }; -} - -/** - * Append main.dev.ts and main.prod.ts to src directory. These files are needed - * by Bazel for devserver and prodserver, respectively. They are different from - * main.ts generated by CLI because they use platformBrowser (AOT) instead of - * platformBrowserDynamic (JIT). - */ -function addDevAndProdMainForAot(options: Schema) { - return (host: Tree) => { - let newProjectRoot = ''; - try { - const workspace = getWorkspace(host); - newProjectRoot = workspace.newProjectRoot || ''; - } catch { - } - const srcDir = `${newProjectRoot}/${options.name}/src`; - - return mergeWith(apply(url('./files'), [ - applyTemplates({ - utils: strings, - ...options, - 'dot': '.', - }), - move(srcDir), - ])); - }; -} - -function overwriteGitignore(options: Schema) { - return (host: Tree) => { - const gitignore = `${options.name}/.gitignore`; - if (!host.exists(gitignore)) { - return host; - } - const gitIgnoreContent = host.read(gitignore); - if (!gitIgnoreContent) { - throw new Error('Failed to read .gitignore content'); - } - - if (gitIgnoreContent.includes('/bazel-out\n')) { - return host; - } - const lines = gitIgnoreContent.toString().split(/\n/g); - const recorder = host.beginUpdate(gitignore); - const compileOutput = lines.findIndex((line: string) => line === '# compiled output'); - recorder.insertRight(compileOutput, '\n/bazel-out'); - host.commitUpdate(recorder); - - return host; - }; -} - -function replacePropertyInAstObject( - recorder: UpdateRecorder, node: JsonAstObject, propertyName: string, value: JsonValue, - indent: number) { - const property = findPropertyInAstObject(node, propertyName); - if (property === null) { - throw new Error(`Property ${propertyName} does not exist in JSON object`); - } - const {start, text} = property; - recorder.remove(start.offset, text.length); - const indentStr = '\n' + - ' '.repeat(indent); - const content = JSON.stringify(value, null, ' ').replace(/\n/g, indentStr); - recorder.insertLeft(start.offset, content); -} - -function updateWorkspaceFileToUseBazelBuilder(options: Schema): Rule { - return (host: Tree, context: SchematicContext) => { - const {name} = options; - const workspacePath = `${name}/angular.json`; - if (!host.exists(workspacePath)) { - throw new SchematicsException(`Workspace file ${workspacePath} not found.`); - } - const workspaceBuffer = host.read(workspacePath) !; - const workspaceJsonAst = parseJsonAst(workspaceBuffer.toString()) as JsonAstObject; - const projects = findPropertyInAstObject(workspaceJsonAst, 'projects'); - if (!projects) { - throw new SchematicsException('Expect projects in angular.json to be an Object'); - } - const project = findPropertyInAstObject(projects as JsonAstObject, name); - if (!project) { - throw new SchematicsException(`Expected projects to contain ${name}`); - } - const recorder = host.beginUpdate(workspacePath); - const indent = 6; - replacePropertyInAstObject( - recorder, project as JsonAstObject, 'architect', { - 'build': { - 'builder': '@angular/bazel:build', - 'options': {'targetLabel': '//src:bundle.js', 'bazelCommand': 'build'}, - 'configurations': {'production': {'targetLabel': '//src:bundle'}} - }, - 'serve': { - 'builder': '@angular/bazel:build', - 'options': {'targetLabel': '//src:devserver', 'bazelCommand': 'run'}, - 'configurations': {'production': {'targetLabel': '//src:prodserver'}} - }, - 'extract-i18n': { - 'builder': '@angular-devkit/build-angular:extract-i18n', - 'options': {'browserTarget': `${name}:build`} - }, - 'test': { - 'builder': '@angular/bazel:build', - 'options': {'bazelCommand': 'test', 'targetLabel': '//src/...'} - }, - 'lint': { - 'builder': '@angular-devkit/build-angular:tslint', - 'options': { - 'tsConfig': ['src/tsconfig.app.json', 'src/tsconfig.spec.json'], - 'exclude': ['**/node_modules/**'] - } - } - }, - indent); - - const e2e = `${options.name}-e2e`; - const e2eNode = findPropertyInAstObject(projects as JsonAstObject, e2e); - if (e2eNode) { - replacePropertyInAstObject( - recorder, e2eNode as JsonAstObject, 'architect', { - 'e2e': { - 'builder': '@angular/bazel:build', - 'options': {'bazelCommand': 'test', 'targetLabel': '//e2e:devserver_test'}, - 'configurations': {'production': {'targetLabel': '//e2e:prodserver_test'}} - }, - 'lint': { - 'builder': '@angular-devkit/build-angular:tslint', - 'options': {'tsConfig': 'e2e/tsconfig.e2e.json', 'exclude': ['**/node_modules/**']} - } - }, - indent); - } - - host.commitUpdate(recorder); - return host; - }; -} - export default function(options: Schema): Rule { return (host: Tree) => { validateProjectName(options.name); return chain([ - externalSchematic( - '@schematics/angular', 'ng-new', - { - ...options, - }), - addDevDependenciesToPackageJson(options), - addDevAndProdMainForAot(options), - schematic('bazel-workspace', options, { + externalSchematic('@schematics/angular', 'ng-new', options), + schematic('ng-add', options, { scope: options.name, }), - overwriteGitignore(options), - updateWorkspaceFileToUseBazelBuilder(options), ]); }; } diff --git a/packages/bazel/src/schematics/ng-new/index_spec.ts b/packages/bazel/src/schematics/ng-new/index_spec.ts index 946e162eaa..66177ea67c 100644 --- a/packages/bazel/src/schematics/ng-new/index_spec.ts +++ b/packages/bazel/src/schematics/ng-new/index_spec.ts @@ -8,7 +8,7 @@ import {SchematicTestRunner} from '@angular-devkit/schematics/testing'; -describe('Ng-new Schematic', () => { +describe('ng-new schematic', () => { const schematicRunner = new SchematicTestRunner('@angular/bazel', require.resolve('../collection.json'), ); const defaultOptions = { @@ -22,94 +22,15 @@ describe('Ng-new Schematic', () => { const {files} = host; // External schematic should produce workspace file angular.json expect(files).toContain('/demo/angular.json'); - }); - - it('should add @angular/bazel to package.json dependencies', () => { - const options = {...defaultOptions}; - const host = schematicRunner.runSchematic('ng-new', options); - const {files} = host; expect(files).toContain('/demo/package.json'); - const content = host.readContent('/demo/package.json'); - expect(() => JSON.parse(content)).not.toThrow(); - const json = JSON.parse(content); - const core = '@angular/core'; - const bazel = '@angular/bazel'; - expect(Object.keys(json)).toContain('dependencies'); - expect(Object.keys(json)).toContain('devDependencies'); - expect(Object.keys(json.dependencies)).toContain(core); - expect(Object.keys(json.devDependencies)).toContain(bazel); - expect(json.dependencies[core]).toBe(json.devDependencies[bazel]); }); - it('should add @bazel/* dev dependencies', () => { - const options = {...defaultOptions}; - const host = schematicRunner.runSchematic('ng-new', options); - const content = host.readContent('/demo/package.json'); - const json = JSON.parse(content); - const devDeps = Object.keys(json.devDependencies); - expect(devDeps).toContain('@bazel/karma'); - expect(devDeps).toContain('@bazel/typescript'); - }); - - it('should create Bazel workspace file', () => { + it('should call ng-add to generate Bazel files', () => { const options = {...defaultOptions}; const host = schematicRunner.runSchematic('ng-new', options); const {files} = host; expect(files).toContain('/demo/WORKSPACE'); expect(files).toContain('/demo/BUILD.bazel'); - }); - - it('should produce main.prod.ts for AOT', () => { - const options = {...defaultOptions}; - const host = schematicRunner.runSchematic('ng-new', options); - const {files} = host; - // main.prod.ts is used by Bazel for AOT - expect(files).toContain('/demo/src/main.prod.ts'); - // main.ts is produced by original ng-new schematics - // This file should be present for backwards compatibility. - expect(files).toContain('/demo/src/main.ts'); - }); - - it('should not overwrite index.html with script tags', () => { - const options = {...defaultOptions}; - const host = schematicRunner.runSchematic('ng-new', options); - const {files} = host; - expect(files).toContain('/demo/src/index.html'); - const content = host.readContent('/demo/src/index.html'); - expect(content).not.toMatch(''); - expect(content).not.toMatch(''); - }); - - it('should generate main.dev.ts and main.prod.ts', () => { - const options = {...defaultOptions}; - const host = schematicRunner.runSchematic('ng-new', options); - const {files} = host; - expect(files).toContain('/demo/src/main.dev.ts'); - expect(files).toContain('/demo/src/main.prod.ts'); - }); - - it('should overwrite .gitignore for bazel-out directory', () => { - const options = {...defaultOptions}; - const host = schematicRunner.runSchematic('ng-new', options); - const {files} = host; - expect(files).toContain('/demo/.gitignore'); - const content = host.readContent('/demo/.gitignore'); - expect(content).toMatch('/bazel-out'); - }); - - it('should update angular.json to use Bazel builder', () => { - const options = {...defaultOptions}; - const host = schematicRunner.runSchematic('ng-new', options); - const {files} = host; - expect(files).toContain('/demo/angular.json'); - const content = host.readContent('/demo/angular.json'); - expect(() => JSON.parse(content)).not.toThrow(); - const json = JSON.parse(content); - let {architect} = json.projects.demo; - expect(architect.build.builder).toBe('@angular/bazel:build'); - expect(architect.serve.builder).toBe('@angular/bazel:build'); - expect(architect.test.builder).toBe('@angular/bazel:build'); - architect = json.projects['demo-e2e'].architect; - expect(architect.e2e.builder).toBe('@angular/bazel:build'); + expect(files).toContain('/demo/src/BUILD.bazel'); }); }); diff --git a/packages/bazel/src/schematics/ng-new/schema.json b/packages/bazel/src/schematics/ng-new/schema.json old mode 100755 new mode 100644 index e80bc82bc3..ba82f2c628 --- a/packages/bazel/src/schematics/ng-new/schema.json +++ b/packages/bazel/src/schematics/ng-new/schema.json @@ -43,7 +43,9 @@ "commit": { "description": "Initial repository commit information.", "oneOf": [ - { "type": "boolean" }, + { + "type": "boolean" + }, { "type": "object", "properties": { @@ -85,7 +87,12 @@ }, "viewEncapsulation": { "description": "Specifies the view encapsulation strategy.", - "enum": ["Emulated", "Native", "None", "ShadowDom"], + "enum": [ + "Emulated", + "Native", + "None", + "ShadowDom" + ], "type": "string" }, "version": { @@ -118,11 +125,22 @@ "message": "Which stylesheet format would you like to use?", "type": "list", "items": [ - { "value": "css", "label": "CSS" }, - { "value": "scss", "label": "SCSS [ http://sass-lang.com ]" }, - { "value": "sass", "label": "SASS [ http://sass-lang.com ]" }, - { "value": "less", "label": "LESS [ http://lesscss.org ]" }, - { "value": "styl", "label": "Stylus [ http://stylus-lang.com ]" } + { + "value": "css", + "label": "CSS" + }, + { + "value": "sass", + "label": "Sass [ http://sass-lang.com ]" + }, + { + "value": "less", + "label": "Less [ http://lesscss.org ]" + }, + { + "value": "styl", + "label": "Stylus [ http://stylus-lang.com ]" + } ] } },