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
This commit is contained in:
Keen Yee Liau 2019-01-28 11:03:04 -08:00 committed by Matias Niemelä
parent 744b0205e2
commit 4a92fa9471
20 changed files with 617 additions and 542 deletions

View File

@ -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"
}

View File

@ -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

View File

@ -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==

View File

@ -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",
],
)

View File

@ -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",
],

View File

@ -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',

View File

@ -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', () => {

View File

@ -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
}
}
}

View File

@ -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",
],
)

View File

@ -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(),
]);
};
}

View File

@ -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', '<html>Hello World</html>');
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('<script src="/zone.min.js"></script>');
expect(content).not.toMatch('<script src="/bundle.min.js"></script>');
});
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');
});
});

View File

@ -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;
}

View File

@ -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"
]
}

View File

@ -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",
],
)

198
packages/bazel/src/schematics/ng-new/index.ts Executable file → Normal file
View File

@ -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),
]);
};
}

View File

@ -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('<script src="/zone.min.js"></script>');
expect(content).not.toMatch('<script src="/bundle.min.js"></script>');
});
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');
});
});

32
packages/bazel/src/schematics/ng-new/schema.json Executable file → Normal file
View File

@ -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 ]"
}
]
}
},