From 3bca363a3f8a78171070fa0a49a5fbfcf75e085d Mon Sep 17 00:00:00 2001 From: Oleg Pimenov Date: Sat, 6 Oct 2018 21:28:04 +0300 Subject: [PATCH] feat: added cdk schematic --- angular.tsconfig.json | 20 + package-lock.json | 577 ++++++++++++++---- package.json | 7 +- src/cdk/package.json | 4 + src/cdk/schematics/collection.json | 13 + src/cdk/schematics/index.ts | 2 + src/cdk/schematics/migration.json | 5 + src/cdk/schematics/ng-add/index.spec.ts | 26 + src/cdk/schematics/ng-add/index.ts | 28 + src/cdk/schematics/ng-add/package-config.ts | 32 + src/cdk/schematics/ng-add/schema.json | 16 + src/cdk/schematics/ng-add/schema.ts | 5 + src/cdk/schematics/testing/index.ts | 2 + .../testing/post-scheduled-tasks.ts | 36 ++ src/cdk/schematics/testing/test-app.ts | 14 + src/cdk/schematics/tsconfig.json | 26 + src/cdk/schematics/utils/ast.ts | 81 +++ .../schematics/utils/ast/ng-module-imports.ts | 78 +++ src/cdk/schematics/utils/build-component.ts | 239 ++++++++ src/cdk/schematics/utils/get-project.ts | 26 + src/cdk/schematics/utils/index.ts | 18 + src/cdk/schematics/utils/parse5-element.ts | 32 + src/cdk/schematics/utils/project-main-file.ts | 23 + .../schematics/utils/project-style-file.ts | 50 ++ src/cdk/schematics/utils/project-targets.ts | 31 + src/cdk/schematics/utils/schematic-options.ts | 46 ++ .../utils/version-agnostic-typescript.ts | 38 ++ src/cdk/tsconfig.tests.json | 4 + tools/gulp/packages.ts | 2 + tools/gulp/tsconfig.json | 8 +- tools/packages/build-package.ts | 3 + tools/packages/build-release.ts | 4 + tools/packages/gulp/build-tasks-gulp.ts | 35 +- tools/packages/ts-compile.ts | 2 +- 34 files changed, 1407 insertions(+), 126 deletions(-) create mode 100644 angular.tsconfig.json create mode 100644 src/cdk/schematics/collection.json create mode 100644 src/cdk/schematics/index.ts create mode 100644 src/cdk/schematics/migration.json create mode 100644 src/cdk/schematics/ng-add/index.spec.ts create mode 100644 src/cdk/schematics/ng-add/index.ts create mode 100644 src/cdk/schematics/ng-add/package-config.ts create mode 100644 src/cdk/schematics/ng-add/schema.json create mode 100644 src/cdk/schematics/ng-add/schema.ts create mode 100644 src/cdk/schematics/testing/index.ts create mode 100644 src/cdk/schematics/testing/post-scheduled-tasks.ts create mode 100644 src/cdk/schematics/testing/test-app.ts create mode 100644 src/cdk/schematics/tsconfig.json create mode 100644 src/cdk/schematics/utils/ast.ts create mode 100644 src/cdk/schematics/utils/ast/ng-module-imports.ts create mode 100644 src/cdk/schematics/utils/build-component.ts create mode 100644 src/cdk/schematics/utils/get-project.ts create mode 100644 src/cdk/schematics/utils/index.ts create mode 100644 src/cdk/schematics/utils/parse5-element.ts create mode 100644 src/cdk/schematics/utils/project-main-file.ts create mode 100644 src/cdk/schematics/utils/project-style-file.ts create mode 100644 src/cdk/schematics/utils/project-targets.ts create mode 100644 src/cdk/schematics/utils/schematic-options.ts create mode 100644 src/cdk/schematics/utils/version-agnostic-typescript.ts diff --git a/angular.tsconfig.json b/angular.tsconfig.json new file mode 100644 index 000000000..d37a23cc0 --- /dev/null +++ b/angular.tsconfig.json @@ -0,0 +1,20 @@ +// WORKAROUND https://github.com/angular/angular/issues/18810 +// This file is required to run ngc on angular libraries, to write files like +// node_modules/@angular/core/core.ngsummary.json +{ + "compilerOptions": { + "lib": [ + "dom", + "es2015" + ], + "experimentalDecorators": true, + "types": [] + }, + "include": [ + "node_modules/@angular/**/*" + ], + "exclude": [ + "node_modules/@angular/compiler-cli/**", + "node_modules/@angular/**/testing/**" + ] +} diff --git a/package-lock.json b/package-lock.json index a630cc32a..02eae2f96 100644 --- a/package-lock.json +++ b/package-lock.json @@ -4,6 +4,411 @@ "lockfileVersion": 1, "requires": true, "dependencies": { + "@angular-devkit/core": { + "version": "7.0.0-rc.2", + "resolved": "https://registry.npmjs.org/@angular-devkit/core/-/core-7.0.0-rc.2.tgz", + "integrity": "sha512-c2ODPqnc40Wz9Oerjdi3gHa+XBCvGj1rAZd38NdweCSFejUogbppB4MDoKOFuQi7qp+kXfJpXSX4aocDm6T67g==", + "dev": true, + "requires": { + "ajv": "6.5.3", + "chokidar": "2.0.4", + "fast-json-stable-stringify": "2.0.0", + "rxjs": "6.3.3", + "source-map": "0.7.3" + }, + "dependencies": { + "ajv": { + "version": "6.5.3", + "resolved": "https://registry.npmjs.org/ajv/-/ajv-6.5.3.tgz", + "integrity": "sha512-LqZ9wY+fx3UMiiPd741yB2pj3hhil+hQc8taf4o2QGRFpWgZ2V5C8HA165DY9sS3fJwsk7uT7ZlFEyC3Ig3lLg==", + "dev": true, + "requires": { + "fast-deep-equal": "2.0.1", + "fast-json-stable-stringify": "2.0.0", + "json-schema-traverse": "0.4.1", + "uri-js": "4.2.2" + } + }, + "anymatch": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/anymatch/-/anymatch-2.0.0.tgz", + "integrity": "sha512-5teOsQWABXHHBFP9y3skS5P3d/WfWXpv3FUpy+LorMrNYaT9pI4oLMQX7jzQ2KklNpGpWHzdCXTDT2Y3XGlZBw==", + "dev": true, + "requires": { + "micromatch": "3.1.10", + "normalize-path": "2.1.1" + } + }, + "arr-diff": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/arr-diff/-/arr-diff-4.0.0.tgz", + "integrity": "sha1-1kYQdP6/7HHn4VI1dhoyml3HxSA=", + "dev": true + }, + "array-unique": { + "version": "0.3.2", + "resolved": "https://registry.npmjs.org/array-unique/-/array-unique-0.3.2.tgz", + "integrity": "sha1-qJS3XUvE9s1nnvMkSp/Y9Gri1Cg=", + "dev": true + }, + "braces": { + "version": "2.3.2", + "resolved": "https://registry.npmjs.org/braces/-/braces-2.3.2.tgz", + "integrity": "sha512-aNdbnj9P8PjdXU4ybaWLK2IF3jc/EoDYbC7AazW6to3TRsfXxscC9UXOB5iDiEQrkyIbWp2SLQda4+QAa7nc3w==", + "dev": true, + "requires": { + "arr-flatten": "1.1.0", + "array-unique": "0.3.2", + "extend-shallow": "2.0.1", + "fill-range": "4.0.0", + "isobject": "3.0.1", + "repeat-element": "1.1.3", + "snapdragon": "0.8.2", + "snapdragon-node": "2.1.1", + "split-string": "3.1.0", + "to-regex": "3.0.2" + }, + "dependencies": { + "extend-shallow": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/extend-shallow/-/extend-shallow-2.0.1.tgz", + "integrity": "sha1-Ua99YUrZqfYQ6huvu5idaxxWiQ8=", + "dev": true, + "requires": { + "is-extendable": "0.1.1" + } + } + } + }, + "chokidar": { + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/chokidar/-/chokidar-2.0.4.tgz", + "integrity": "sha512-z9n7yt9rOvIJrMhvDtDictKrkFHeihkNl6uWMmZlmL6tJtX9Cs+87oK+teBx+JIgzvbX3yZHT3eF8vpbDxHJXQ==", + "dev": true, + "requires": { + "anymatch": "2.0.0", + "async-each": "1.0.1", + "braces": "2.3.2", + "fsevents": "1.2.4", + "glob-parent": "3.1.0", + "inherits": "2.0.3", + "is-binary-path": "1.0.1", + "is-glob": "4.0.0", + "lodash.debounce": "4.0.8", + "normalize-path": "2.1.1", + "path-is-absolute": "1.0.1", + "readdirp": "2.2.1", + "upath": "1.1.0" + } + }, + "expand-brackets": { + "version": "2.1.4", + "resolved": "https://registry.npmjs.org/expand-brackets/-/expand-brackets-2.1.4.tgz", + "integrity": "sha1-t3c14xXOMPa27/D4OwQVGiJEliI=", + "dev": true, + "requires": { + "debug": "2.6.9", + "define-property": "0.2.5", + "extend-shallow": "2.0.1", + "posix-character-classes": "0.1.1", + "regex-not": "1.0.2", + "snapdragon": "0.8.2", + "to-regex": "3.0.2" + }, + "dependencies": { + "define-property": { + "version": "0.2.5", + "resolved": "https://registry.npmjs.org/define-property/-/define-property-0.2.5.tgz", + "integrity": "sha1-w1se+RjsPJkPmlvFe+BKrOxcgRY=", + "dev": true, + "requires": { + "is-descriptor": "0.1.6" + } + }, + "extend-shallow": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/extend-shallow/-/extend-shallow-2.0.1.tgz", + "integrity": "sha1-Ua99YUrZqfYQ6huvu5idaxxWiQ8=", + "dev": true, + "requires": { + "is-extendable": "0.1.1" + } + }, + "is-accessor-descriptor": { + "version": "0.1.6", + "resolved": "https://registry.npmjs.org/is-accessor-descriptor/-/is-accessor-descriptor-0.1.6.tgz", + "integrity": "sha1-qeEss66Nh2cn7u84Q/igiXtcmNY=", + "dev": true, + "requires": { + "kind-of": "3.2.2" + }, + "dependencies": { + "kind-of": { + "version": "3.2.2", + "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-3.2.2.tgz", + "integrity": "sha1-MeohpzS6ubuw8yRm2JOupR5KPGQ=", + "dev": true, + "requires": { + "is-buffer": "1.1.6" + } + } + } + }, + "is-data-descriptor": { + "version": "0.1.4", + "resolved": "https://registry.npmjs.org/is-data-descriptor/-/is-data-descriptor-0.1.4.tgz", + "integrity": "sha1-C17mSDiOLIYCgueT8YVv7D8wG1Y=", + "dev": true, + "requires": { + "kind-of": "3.2.2" + }, + "dependencies": { + "kind-of": { + "version": "3.2.2", + "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-3.2.2.tgz", + "integrity": "sha1-MeohpzS6ubuw8yRm2JOupR5KPGQ=", + "dev": true, + "requires": { + "is-buffer": "1.1.6" + } + } + } + }, + "is-descriptor": { + "version": "0.1.6", + "resolved": "https://registry.npmjs.org/is-descriptor/-/is-descriptor-0.1.6.tgz", + "integrity": "sha512-avDYr0SB3DwO9zsMov0gKCESFYqCnE4hq/4z3TdUlukEy5t9C0YRq7HLrsN52NAcqXKaepeCD0n+B0arnVG3Hg==", + "dev": true, + "requires": { + "is-accessor-descriptor": "0.1.6", + "is-data-descriptor": "0.1.4", + "kind-of": "5.1.0" + } + }, + "kind-of": { + "version": "5.1.0", + "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-5.1.0.tgz", + "integrity": "sha512-NGEErnH6F2vUuXDh+OlbcKW7/wOcfdRHaZ7VWtqCztfHri/++YKmP51OdWeGPuqCOba6kk2OTe5d02VmTB80Pw==", + "dev": true + } + } + }, + "extglob": { + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/extglob/-/extglob-2.0.4.tgz", + "integrity": "sha512-Nmb6QXkELsuBr24CJSkilo6UHHgbekK5UiZgfE6UHD3Eb27YC6oD+bhcT+tJ6cl8dmsgdQxnWlcry8ksBIBLpw==", + "dev": true, + "requires": { + "array-unique": "0.3.2", + "define-property": "1.0.0", + "expand-brackets": "2.1.4", + "extend-shallow": "2.0.1", + "fragment-cache": "0.2.1", + "regex-not": "1.0.2", + "snapdragon": "0.8.2", + "to-regex": "3.0.2" + }, + "dependencies": { + "define-property": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/define-property/-/define-property-1.0.0.tgz", + "integrity": "sha1-dp66rz9KY6rTr56NMEybvnm/sOY=", + "dev": true, + "requires": { + "is-descriptor": "1.0.2" + } + }, + "extend-shallow": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/extend-shallow/-/extend-shallow-2.0.1.tgz", + "integrity": "sha1-Ua99YUrZqfYQ6huvu5idaxxWiQ8=", + "dev": true, + "requires": { + "is-extendable": "0.1.1" + } + } + } + }, + "fast-deep-equal": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/fast-deep-equal/-/fast-deep-equal-2.0.1.tgz", + "integrity": "sha1-ewUhjd+WZ79/Nwv3/bLLFf3Qqkk=", + "dev": true + }, + "fill-range": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/fill-range/-/fill-range-4.0.0.tgz", + "integrity": "sha1-1USBHUKPmOsGpj3EAtJAPDKMOPc=", + "dev": true, + "requires": { + "extend-shallow": "2.0.1", + "is-number": "3.0.0", + "repeat-string": "1.6.1", + "to-regex-range": "2.1.1" + }, + "dependencies": { + "extend-shallow": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/extend-shallow/-/extend-shallow-2.0.1.tgz", + "integrity": "sha1-Ua99YUrZqfYQ6huvu5idaxxWiQ8=", + "dev": true, + "requires": { + "is-extendable": "0.1.1" + } + } + } + }, + "glob-parent": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-3.1.0.tgz", + "integrity": "sha1-nmr2KZ2NO9K9QEMIMr0RPfkGxa4=", + "dev": true, + "requires": { + "is-glob": "3.1.0", + "path-dirname": "1.0.2" + }, + "dependencies": { + "is-glob": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/is-glob/-/is-glob-3.1.0.tgz", + "integrity": "sha1-e6WuJCF4BKxwcHuWkiVnSGzD6Eo=", + "dev": true, + "requires": { + "is-extglob": "2.1.1" + } + } + } + }, + "is-accessor-descriptor": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/is-accessor-descriptor/-/is-accessor-descriptor-1.0.0.tgz", + "integrity": "sha512-m5hnHTkcVsPfqx3AKlyttIPb7J+XykHvJP2B9bZDjlhLIoEq4XoK64Vg7boZlVWYK6LUY94dYPEE7Lh0ZkZKcQ==", + "dev": true, + "requires": { + "kind-of": "6.0.2" + } + }, + "is-data-descriptor": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/is-data-descriptor/-/is-data-descriptor-1.0.0.tgz", + "integrity": "sha512-jbRXy1FmtAoCjQkVmIVYwuuqDFUbaOeDjmed1tOGPrsMhtJA4rD9tkgA0F1qJ3gRFRXcHYVkdeaP50Q5rE/jLQ==", + "dev": true, + "requires": { + "kind-of": "6.0.2" + } + }, + "is-descriptor": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/is-descriptor/-/is-descriptor-1.0.2.tgz", + "integrity": "sha512-2eis5WqQGV7peooDyLmNEPUrps9+SXX5c9pL3xEB+4e9HnGuDa7mB7kHxHw4CbqS9k1T2hOH3miL8n8WtiYVtg==", + "dev": true, + "requires": { + "is-accessor-descriptor": "1.0.0", + "is-data-descriptor": "1.0.0", + "kind-of": "6.0.2" + } + }, + "is-extglob": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/is-extglob/-/is-extglob-2.1.1.tgz", + "integrity": "sha1-qIwCU1eR8C7TfHahueqXc8gz+MI=", + "dev": true + }, + "is-glob": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/is-glob/-/is-glob-4.0.0.tgz", + "integrity": "sha1-lSHHaEXMJhCoUgPd8ICpWML/q8A=", + "dev": true, + "requires": { + "is-extglob": "2.1.1" + } + }, + "is-number": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/is-number/-/is-number-3.0.0.tgz", + "integrity": "sha1-JP1iAaR4LPUFYcgQJ2r8fRLXEZU=", + "dev": true, + "requires": { + "kind-of": "3.2.2" + }, + "dependencies": { + "kind-of": { + "version": "3.2.2", + "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-3.2.2.tgz", + "integrity": "sha1-MeohpzS6ubuw8yRm2JOupR5KPGQ=", + "dev": true, + "requires": { + "is-buffer": "1.1.6" + } + } + } + }, + "isobject": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/isobject/-/isobject-3.0.1.tgz", + "integrity": "sha1-TkMekrEalzFjaqH5yNHMvP2reN8=", + "dev": true + }, + "json-schema-traverse": { + "version": "0.4.1", + "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-0.4.1.tgz", + "integrity": "sha512-xbbCH5dCYU5T8LcEhhuh7HJ88HXuW3qsI3Y0zOZFKfZEHcpWiHU/Jxzk629Brsab/mMiHQti9wMP+845RPe3Vg==", + "dev": true + }, + "kind-of": { + "version": "6.0.2", + "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-6.0.2.tgz", + "integrity": "sha512-s5kLOcnH0XqDO+FvuaLX8DDjZ18CGFk7VygH40QoKPUQhW4e2rvM0rwUq0t8IQDOwYSeLK01U90OjzBTme2QqA==", + "dev": true + }, + "micromatch": { + "version": "3.1.10", + "resolved": "https://registry.npmjs.org/micromatch/-/micromatch-3.1.10.tgz", + "integrity": "sha512-MWikgl9n9M3w+bpsY3He8L+w9eF9338xRl8IAO5viDizwSzziFEyUzo2xrrloB64ADbTf8uA8vRqqttDTOmccg==", + "dev": true, + "requires": { + "arr-diff": "4.0.0", + "array-unique": "0.3.2", + "braces": "2.3.2", + "define-property": "2.0.2", + "extend-shallow": "3.0.2", + "extglob": "2.0.4", + "fragment-cache": "0.2.1", + "kind-of": "6.0.2", + "nanomatch": "1.2.13", + "object.pick": "1.3.0", + "regex-not": "1.0.2", + "snapdragon": "0.8.2", + "to-regex": "3.0.2" + } + }, + "source-map": { + "version": "0.7.3", + "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.7.3.tgz", + "integrity": "sha512-CkCj6giN3S+n9qrYiBTX5gystlENnRW5jZeNLHpe6aue+SrHcG5VYwujhW9s4dY31mEGsxBDrHR6oI69fTXsaQ==", + "dev": true + }, + "uri-js": { + "version": "4.2.2", + "resolved": "https://registry.npmjs.org/uri-js/-/uri-js-4.2.2.tgz", + "integrity": "sha512-KY9Frmirql91X2Qgjry0Wd4Y+YTdrdZheS8TFwvkbLWf/G5KNJDCh6pKL5OZctEW4+0Baa5idK2ZQuELRwPznQ==", + "dev": true, + "requires": { + "punycode": "2.1.1" + } + } + } + }, + "@angular-devkit/schematics": { + "version": "7.0.0-rc.2", + "resolved": "https://registry.npmjs.org/@angular-devkit/schematics/-/schematics-7.0.0-rc.2.tgz", + "integrity": "sha512-fzXCkCjBw4mJ+8OG07ss5m6kHLNU3zFeuEDl6UrD0bUnbnyfGPUFTKub33ZaBSKhXV3cVg8mGy+V6j/mJ/PgTw==", + "dev": true, + "requires": { + "@angular-devkit/core": "7.0.0-rc.2", + "rxjs": "6.3.3" + } + }, "@angular/animations": { "version": "7.0.0-rc.0", "resolved": "https://registry.npmjs.org/@angular/animations/-/animations-7.0.0-rc.0.tgz", @@ -309,6 +714,15 @@ "tslib": "1.9.3" } }, + "@angular/upgrade": { + "version": "7.0.0-rc.0", + "resolved": "https://registry.npmjs.org/@angular/upgrade/-/upgrade-7.0.0-rc.0.tgz", + "integrity": "sha512-Ymg+ftMjaRmQThX0cumfmWg5ojxIFyHxKOeScg2H8D5Yel24kKuZB+RxmcgJ7gVoraTtROyKpSigeyJOLhI2Vw==", + "dev": true, + "requires": { + "tslib": "1.9.3" + } + }, "@babel/code-frame": { "version": "7.0.0", "resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.0.0.tgz", @@ -748,6 +1162,17 @@ "any-observable": "0.3.0" } }, + "@schematics/angular": { + "version": "7.0.0-rc.2", + "resolved": "https://registry.npmjs.org/@schematics/angular/-/angular-7.0.0-rc.2.tgz", + "integrity": "sha512-awy/wt+d2Dqf6iZmqZ3kWFvJ6oeXRrCQ16bD4hwZd00/6UsJ9Wlv1NodJ8LkzDEWZaCBZ4OHPevYZdfX2M77+Q==", + "dev": true, + "requires": { + "@angular-devkit/core": "7.0.0-rc.2", + "@angular-devkit/schematics": "7.0.0-rc.2", + "typescript": "3.1.1" + } + }, "@types/chalk": { "version": "2.2.0", "resolved": "https://registry.npmjs.org/@types/chalk/-/chalk-2.2.0.tgz", @@ -783,6 +1208,17 @@ "@types/node": "8.9.5" } }, + "@types/gulp": { + "version": "3.8.32", + "resolved": "https://registry.npmjs.org/@types/gulp/-/gulp-3.8.32.tgz", + "integrity": "sha1-g8WcaBzCM9Hsf4LSaVVVZvoTMVY=", + "dev": true, + "requires": { + "@types/node": "8.9.5", + "@types/orchestrator": "0.3.2", + "@types/vinyl": "2.0.2" + } + }, "@types/jasmine": { "version": "2.8.9", "resolved": "https://registry.npmjs.org/@types/jasmine/-/jasmine-2.8.9.tgz", @@ -807,130 +1243,30 @@ "integrity": "sha512-jRHfWsvyMtXdbhnz5CVHxaBgnV6duZnPlQuRSo/dm/GnmikNcmZhxIES4E9OZjUmQ8C+HCl4KJux+cXN/ErGDQ==", "dev": true }, - "@types/rx": { - "version": "4.1.1", - "resolved": "https://registry.npmjs.org/@types/rx/-/rx-4.1.1.tgz", - "integrity": "sha1-WY/JSla67ZdfGUV04PVy/Y5iekg=", + "@types/orchestrator": { + "version": "0.3.2", + "resolved": "https://registry.npmjs.org/@types/orchestrator/-/orchestrator-0.3.2.tgz", + "integrity": "sha512-cKB4yTX0wGaRCSkdHDX2fkGQbMAA8UOshC2U7DQky1CE5o+5q2iQQ8VkbPbE/88uaTtsusvBPMcCX7dgmjxBhQ==", "dev": true, "requires": { - "@types/rx-core": "4.0.3", - "@types/rx-core-binding": "4.0.4", - "@types/rx-lite": "4.0.5", - "@types/rx-lite-aggregates": "4.0.3", - "@types/rx-lite-async": "4.0.2", - "@types/rx-lite-backpressure": "4.0.3", - "@types/rx-lite-coincidence": "4.0.3", - "@types/rx-lite-experimental": "4.0.1", - "@types/rx-lite-joinpatterns": "4.0.1", - "@types/rx-lite-testing": "4.0.1", - "@types/rx-lite-time": "4.0.3", - "@types/rx-lite-virtualtime": "4.0.3" + "@types/node": "8.9.5", + "@types/q": "1.5.1" } }, - "@types/rx-core": { - "version": "4.0.3", - "resolved": "https://registry.npmjs.org/@types/rx-core/-/rx-core-4.0.3.tgz", - "integrity": "sha1-CzNUsSOM7b4rdPYybxOdvHpZHWA=", + "@types/q": { + "version": "1.5.1", + "resolved": "https://registry.npmjs.org/@types/q/-/q-1.5.1.tgz", + "integrity": "sha512-eqz8c/0kwNi/OEHQfvIuJVLTst3in0e7uTKeuY+WL/zfKn0xVujOTp42bS/vUUokhK5P2BppLd9JXMOMHcgbjA==", "dev": true }, - "@types/rx-core-binding": { - "version": "4.0.4", - "resolved": "https://registry.npmjs.org/@types/rx-core-binding/-/rx-core-binding-4.0.4.tgz", - "integrity": "sha512-5pkfxnC4w810LqBPUwP5bg7SFR/USwhMSaAeZQQbEHeBp57pjKXRlXmqpMrLJB4y1oglR/c2502853uN0I+DAQ==", - "dev": true, - "requires": { - "@types/rx-core": "4.0.3" - } - }, - "@types/rx-lite": { - "version": "4.0.5", - "resolved": "https://registry.npmjs.org/@types/rx-lite/-/rx-lite-4.0.5.tgz", - "integrity": "sha512-KZk5XTR1dm/kNgBx8iVpjno6fRYtAUQWBOmj+O8j724+nk097sz4fOoHJNpCkOJUtHUurZlJC7QvSFCZHbkC+w==", - "dev": true, - "requires": { - "@types/rx-core": "4.0.3", - "@types/rx-core-binding": "4.0.4" - } - }, - "@types/rx-lite-aggregates": { - "version": "4.0.3", - "resolved": "https://registry.npmjs.org/@types/rx-lite-aggregates/-/rx-lite-aggregates-4.0.3.tgz", - "integrity": "sha512-MAGDAHy8cRatm94FDduhJF+iNS5//jrZ/PIfm+QYw9OCeDgbymFHChM8YVIvN2zArwsRftKgE33QfRWvQk4DPg==", - "dev": true, - "requires": { - "@types/rx-lite": "4.0.5" - } - }, - "@types/rx-lite-async": { - "version": "4.0.2", - "resolved": "https://registry.npmjs.org/@types/rx-lite-async/-/rx-lite-async-4.0.2.tgz", - "integrity": "sha512-vTEv5o8l6702ZwfAM5aOeVDfUwBSDOs+ARoGmWAKQ6LOInQ8J4/zjM7ov12fuTpktUKdMQjkeCp07Vd73mPkxw==", - "dev": true, - "requires": { - "@types/rx-lite": "4.0.5" - } - }, - "@types/rx-lite-backpressure": { - "version": "4.0.3", - "resolved": "https://registry.npmjs.org/@types/rx-lite-backpressure/-/rx-lite-backpressure-4.0.3.tgz", - "integrity": "sha512-Y6aIeQCtNban5XSAF4B8dffhIKu6aAy/TXFlScHzSxh6ivfQBQw6UjxyEJxIOt3IT49YkS+siuayM2H/Q0cmgA==", - "dev": true, - "requires": { - "@types/rx-lite": "4.0.5" - } - }, - "@types/rx-lite-coincidence": { - "version": "4.0.3", - "resolved": "https://registry.npmjs.org/@types/rx-lite-coincidence/-/rx-lite-coincidence-4.0.3.tgz", - "integrity": "sha512-1VNJqzE9gALUyMGypDXZZXzR0Tt7LC9DdAZQ3Ou/Q0MubNU35agVUNXKGHKpNTba+fr8GdIdkC26bRDqtCQBeQ==", - "dev": true, - "requires": { - "@types/rx-lite": "4.0.5" - } - }, - "@types/rx-lite-experimental": { - "version": "4.0.1", - "resolved": "https://registry.npmjs.org/@types/rx-lite-experimental/-/rx-lite-experimental-4.0.1.tgz", - "integrity": "sha1-xTL1y98/LBXaFt7Ykw0bKYQCPL0=", - "dev": true, - "requires": { - "@types/rx-lite": "4.0.5" - } - }, - "@types/rx-lite-joinpatterns": { - "version": "4.0.1", - "resolved": "https://registry.npmjs.org/@types/rx-lite-joinpatterns/-/rx-lite-joinpatterns-4.0.1.tgz", - "integrity": "sha1-9w/jcFGKhDLykVjMkv+1a05K/D4=", - "dev": true, - "requires": { - "@types/rx-lite": "4.0.5" - } - }, - "@types/rx-lite-testing": { - "version": "4.0.1", - "resolved": "https://registry.npmjs.org/@types/rx-lite-testing/-/rx-lite-testing-4.0.1.tgz", - "integrity": "sha1-IbGdEfTf1v/vWp0WSOnIh5v+Iek=", - "dev": true, - "requires": { - "@types/rx-lite-virtualtime": "4.0.3" - } - }, - "@types/rx-lite-time": { - "version": "4.0.3", - "resolved": "https://registry.npmjs.org/@types/rx-lite-time/-/rx-lite-time-4.0.3.tgz", - "integrity": "sha512-ukO5sPKDRwCGWRZRqPlaAU0SKVxmWwSjiOrLhoQDoWxZWg6vyB9XLEZViKOzIO6LnTIQBlk4UylYV0rnhJLxQw==", - "dev": true, - "requires": { - "@types/rx-lite": "4.0.5" - } - }, - "@types/rx-lite-virtualtime": { - "version": "4.0.3", - "resolved": "https://registry.npmjs.org/@types/rx-lite-virtualtime/-/rx-lite-virtualtime-4.0.3.tgz", - "integrity": "sha512-3uC6sGmjpOKatZSVHI2xB1+dedgml669ZRvqxy+WqmGJDVusOdyxcKfyzjW0P3/GrCiN4nmRkLVMhPwHCc5QLg==", + "@types/run-sequence": { + "version": "0.0.29", + "resolved": "https://registry.npmjs.org/@types/run-sequence/-/run-sequence-0.0.29.tgz", + "integrity": "sha1-atD3ODE24TklMi5p/EHbd7MLIHU=", "dev": true, "requires": { - "@types/rx-lite": "4.0.5" + "@types/gulp": "3.8.32", + "@types/node": "8.9.5" } }, "@types/source-map": { @@ -969,6 +1305,15 @@ "source-map": "0.6.1" } }, + "@types/vinyl": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/@types/vinyl/-/vinyl-2.0.2.tgz", + "integrity": "sha512-2iYpNuOl98SrLPBZfEN9Mh2JCJ2EI9HU35SfgBEb51DcmaHkhp8cKMblYeBqMQiwXMgAD3W60DbQ4i/UdLiXhw==", + "dev": true, + "requires": { + "@types/node": "8.9.5" + } + }, "@types/webpack": { "version": "4.4.14", "resolved": "https://registry.npmjs.org/@types/webpack/-/webpack-4.4.14.tgz", diff --git a/package.json b/package.json index 0dea098b4..a7bb58bc8 100644 --- a/package.json +++ b/package.json @@ -31,24 +31,29 @@ "zone.js": "^0.8.26" }, "devDependencies": { + "@angular-devkit/core": "7.0.0-rc.2", + "@angular-devkit/schematics": "7.0.0-rc.2", "@angular/compiler-cli": "^7.0.0-rc.0", "@angular/http": "^7.0.0-rc.0", "@angular/platform-browser-dynamic": "^7.0.0-rc.0", "@angular/platform-server": "^7.0.0-rc.0", "@angular/router": "^7.0.0-rc.0", + "@angular/upgrade": "^7.0.0-rc.0", "@commitlint/cli": "^7.1.2", "@commitlint/config-conventional": "^7.1.2", "@ptsecurity/commitlint-config": "^0.1.3", "@ptsecurity/prettier-config": "0.1.0", "@ptsecurity/tslint-config": "^0.8.0", + "@schematics/angular": "7.0.0-rc.2", "@types/chalk": "^2.2.0", "@types/fs-extra": "^5.0.4", "@types/glob": "^5.0.36", + "@types/gulp": "3.8.32", "@types/jasmine": "^2.8.9", "@types/node": "~8.9.4", - "@types/rx": "4.1.1", "@types/source-map": "^0.5.7", "@types/uglify-js": "^3.0.3", + "@types/run-sequence": "^0.0.29", "@types/webpack": "^4.4.14", "angular2-template-loader": "^0.6.2", "autoprefixer": "^9.1.5", diff --git a/src/cdk/package.json b/src/cdk/package.json index 2b8c46c49..e46d54ddb 100644 --- a/src/cdk/package.json +++ b/src/cdk/package.json @@ -28,5 +28,9 @@ "dependencies": { "tslib": "^1.7.1" }, + "schematics": "./schematics/collection.json", + "ng-update": { + "migrations": "./schematics/migration.json" + }, "sideEffects": false } diff --git a/src/cdk/schematics/collection.json b/src/cdk/schematics/collection.json new file mode 100644 index 000000000..a74a09644 --- /dev/null +++ b/src/cdk/schematics/collection.json @@ -0,0 +1,13 @@ +{ + "$schema": "./node_modules/@angular-devkit/schematics/collection-schema.json", + "schematics": { + "ng-add": { + "description": "Installs the Mosaic CDK", + "factory": "./ng-add/index", + "schema": "./ng-add/schema.json", + "aliases": [ + "install" + ] + } + } +} diff --git a/src/cdk/schematics/index.ts b/src/cdk/schematics/index.ts new file mode 100644 index 000000000..18f5e1500 --- /dev/null +++ b/src/cdk/schematics/index.ts @@ -0,0 +1,2 @@ +export * from './utils'; +export * from './testing'; diff --git a/src/cdk/schematics/migration.json b/src/cdk/schematics/migration.json new file mode 100644 index 000000000..9916d73b4 --- /dev/null +++ b/src/cdk/schematics/migration.json @@ -0,0 +1,5 @@ +{ + "$schema": "./node_modules/@angular-devkit/schematics/collection-schema.json", + "schematics": { + } +} diff --git a/src/cdk/schematics/ng-add/index.spec.ts b/src/cdk/schematics/ng-add/index.spec.ts new file mode 100644 index 000000000..610501c56 --- /dev/null +++ b/src/cdk/schematics/ng-add/index.spec.ts @@ -0,0 +1,26 @@ +import { Tree } from '@angular-devkit/schematics'; +import { SchematicTestRunner } from '@angular-devkit/schematics/testing'; +import { getFileContent } from '@schematics/angular/utility/test'; + +import { createTestApp } from '../testing'; + + +describe('CDK ng-add', () => { + let runner: SchematicTestRunner; + let appTree: Tree; + + beforeEach(() => { + runner = new SchematicTestRunner('schematics', require.resolve('../collection.json')); + appTree = createTestApp(runner); + }); + + it('should update the package.json', () => { + const tree = runner.runSchematic('ng-add', {}, appTree); + const packageJson = JSON.parse(getFileContent(tree, '/package.json')); + const dependencies = packageJson.dependencies; + + expect(dependencies['@ptsecurity/cdk']).toBeDefined(); + expect(Object.keys(dependencies)).toEqual(Object.keys(dependencies).sort(), + 'Expected the modified "dependencies" to be sorted alphabetically.'); + }); +}); diff --git a/src/cdk/schematics/ng-add/index.ts b/src/cdk/schematics/ng-add/index.ts new file mode 100644 index 000000000..ceea1fee5 --- /dev/null +++ b/src/cdk/schematics/ng-add/index.ts @@ -0,0 +1,28 @@ +import { Rule, Tree } from '@angular-devkit/schematics'; + +import { addPackageToPackageJson } from './package-config'; + + +/** Name of the CDK version that is shipped together with the schematics. */ +export const cdkVersion = loadPackageVersionGracefully('@ptsecurity/cdk'); + +/** + * Schematic factory entry-point for the `ng-add` schematic. The ng-add schematic will be + * automatically executed if developers run `ng add @angular/cdk`. + */ +export default function(): Rule { + return (host: Tree) => { + // By default, the CLI already installs the package that has been installed through `ng add`. + // We just store the version in the `package.json` in case the package manager didn't. + addPackageToPackageJson(host, '@ptsecurity/cdk', `^${cdkVersion}`); + }; +} + +/** Loads the full version from the given Angular package gracefully. */ +function loadPackageVersionGracefully(packageName: string): string | null { + try { + return require(`${packageName}/package.json`).version; + } catch { + return null; + } +} diff --git a/src/cdk/schematics/ng-add/package-config.ts b/src/cdk/schematics/ng-add/package-config.ts new file mode 100644 index 000000000..439db1e01 --- /dev/null +++ b/src/cdk/schematics/ng-add/package-config.ts @@ -0,0 +1,32 @@ +import { Tree } from '@angular-devkit/schematics'; + + +/** + * Sorts the keys of the given object. + * @returns A new object instance with sorted keys + */ +function sortObjectByKeys(obj: object) { + return Object.keys(obj).sort().reduce((result, key) => (result[key] = obj[key]) && result, {}); +} + +/** Adds a package to the package.json in the given host tree. */ +export function addPackageToPackageJson(host: Tree, pkg: string, version: string): Tree { + + if (host.exists('package.json')) { + const sourceText = host.read('package.json')!.toString('utf-8'); + const json = JSON.parse(sourceText); + + if (!json.dependencies) { + json.dependencies = {}; + } + + if (!json.dependencies[pkg]) { + json.dependencies[pkg] = version; + json.dependencies = sortObjectByKeys(json.dependencies); + } + + host.overwrite('package.json', JSON.stringify(json, null, 2)); + } + + return host; +} diff --git a/src/cdk/schematics/ng-add/schema.json b/src/cdk/schematics/ng-add/schema.json new file mode 100644 index 000000000..4f965a05e --- /dev/null +++ b/src/cdk/schematics/ng-add/schema.json @@ -0,0 +1,16 @@ +{ + "$schema": "http://json-schema.org/schema", + "id": "angular-cdk-ng-add", + "title": "Angular CDK ng-add", + "type": "object", + "properties": { + "project": { + "type": "string", + "description": "The name of the project.", + "$default": { + "$source": "projectName" + } + } + }, + "required": [] +} diff --git a/src/cdk/schematics/ng-add/schema.ts b/src/cdk/schematics/ng-add/schema.ts new file mode 100644 index 000000000..3b62b3db4 --- /dev/null +++ b/src/cdk/schematics/ng-add/schema.ts @@ -0,0 +1,5 @@ +export interface Schema { + + /** Name of the project to target. */ + project: string; +} diff --git a/src/cdk/schematics/testing/index.ts b/src/cdk/schematics/testing/index.ts new file mode 100644 index 000000000..1749e2714 --- /dev/null +++ b/src/cdk/schematics/testing/index.ts @@ -0,0 +1,2 @@ +export * from './post-scheduled-tasks'; +export * from './test-app'; diff --git a/src/cdk/schematics/testing/post-scheduled-tasks.ts b/src/cdk/schematics/testing/post-scheduled-tasks.ts new file mode 100644 index 000000000..c28549eb2 --- /dev/null +++ b/src/cdk/schematics/testing/post-scheduled-tasks.ts @@ -0,0 +1,36 @@ +import { EngineHost, TaskExecutor, TaskScheduler } from '@angular-devkit/schematics'; +import { SchematicTestRunner } from '@angular-devkit/schematics/testing'; +import { from as observableFrom, Observable } from 'rxjs'; +import { concatMap, filter, last } from 'rxjs/operators'; + + +/** + * Due to the fact that the Angular devkit does not support running scheduled tasks from a + * schematic that has been launched through the TestRunner, we need to manually find the task + * executor for the given task name and run all scheduled instances. + * + * Note that this means that there can be multiple tasks with the same name. The observable emits + * only when all tasks finished executing. + */ +export function runPostScheduledTasks(runner: SchematicTestRunner, taskName: string) + : Observable { + + // Workaround until there is a public API to run scheduled tasks in the @angular-devkit. + // See: https://github.com/angular/angular-cli/issues/11739 + const host = runner.engine['_host'] as EngineHost<{}, {}>; + const tasks = runner.engine['_taskSchedulers'] as TaskScheduler[]; + const createTaskExecutor = (name: string) => + (host.createTaskExecutor(name) as any) as Observable>; + + return observableFrom(tasks).pipe( + concatMap((scheduler) => scheduler.finalize()), + filter((task) => task.configuration.name === taskName), + concatMap((task) => { + return createTaskExecutor(task.configuration.name) + .pipe(concatMap((executor) => executor(task.configuration.options, task.context))); + }), + // Only emit the last emitted value because there can be multiple tasks with the same name. + // The observable should only emit a value if all tasks completed. + last() + ); +} diff --git a/src/cdk/schematics/testing/test-app.ts b/src/cdk/schematics/testing/test-app.ts new file mode 100644 index 000000000..b159652b7 --- /dev/null +++ b/src/cdk/schematics/testing/test-app.ts @@ -0,0 +1,14 @@ +import { SchematicTestRunner, UnitTestTree } from '@angular-devkit/schematics/testing'; + + +/** Create a base app used for testing. */ +export function createTestApp(runner: SchematicTestRunner, appOptions = {}): UnitTestTree { + const workspaceTree = runner.runExternalSchematic('@schematics/angular', 'workspace', { + name: 'workspace', + version: '7.0.0', + newProjectRoot: 'projects' + }); + + return runner.runExternalSchematic('@schematics/angular', 'application', + {...appOptions, name: 'material'}, workspaceTree); +} diff --git a/src/cdk/schematics/tsconfig.json b/src/cdk/schematics/tsconfig.json new file mode 100644 index 000000000..d963f58e7 --- /dev/null +++ b/src/cdk/schematics/tsconfig.json @@ -0,0 +1,26 @@ +{ + "compilerOptions": { + "composite": true, + "declaration": true, + "lib": [ + "es2017" + ], + "module": "commonjs", + "moduleResolution": "node", + "outDir": "../../../dist/packages/cdk/schematics", + "noEmitOnError": false, + "strictNullChecks": true, + "skipDefaultLibCheck": true, + "skipLibCheck": true, + "sourceMap": true, + "target": "es2015", + "types": [ + "jasmine", + "node" + ] + }, + "exclude": [ + "**/files/**/*", + "**/*.spec.ts" + ] +} diff --git a/src/cdk/schematics/utils/ast.ts b/src/cdk/schematics/utils/ast.ts new file mode 100644 index 000000000..1bf55d36d --- /dev/null +++ b/src/cdk/schematics/utils/ast.ts @@ -0,0 +1,81 @@ +/** + * @license + * Copyright Google LLC 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 {WorkspaceProject} from '@angular-devkit/core/src/workspace'; +import {SchematicsException, Tree} from '@angular-devkit/schematics'; +import {Schema as ComponentOptions} from '@schematics/angular/component/schema'; +import {addImportToModule} from '@schematics/angular/utility/ast-utils'; +import {InsertChange} from '@schematics/angular/utility/change'; +import {getWorkspace} from '@schematics/angular/utility/config'; +import {findModuleFromOptions as internalFindModule} from '@schematics/angular/utility/find-module'; +import {getAppModulePath} from '@schematics/angular/utility/ng-ast-utils'; +import {getProjectMainFile} from './project-main-file'; +import {ts} from './version-agnostic-typescript'; + + +/** Reads file given path and returns TypeScript source file. */ +export function getSourceFile(host: Tree, path: string) { + const buffer = host.read(path); + if (!buffer) { + throw new SchematicsException(`Could not find file for path: ${path}`); + } + const content = buffer.toString(); + return ts.createSourceFile(path, content, ts.ScriptTarget.Latest, true); +} + +/** Import and add module to root app module. */ +export function addModuleImportToRootModule(host: Tree, moduleName: string, src: string, + project: WorkspaceProject) { + const modulePath = getAppModulePath(host, getProjectMainFile(project)); + addModuleImportToModule(host, modulePath, moduleName, src); +} + +/** + * Import and add module to specific module path. + * @param host the tree we are updating + * @param modulePath src location of the module to import + * @param moduleName name of module to import + * @param src src location to import + */ +export function addModuleImportToModule(host: Tree, modulePath: string, moduleName: string, + src: string) { + + const moduleSource = getSourceFile(host, modulePath); + + if (!moduleSource) { + throw new SchematicsException(`Module not found: ${modulePath}`); + } + + const changes = addImportToModule(moduleSource, modulePath, moduleName, src); + const recorder = host.beginUpdate(modulePath); + + changes.forEach((change) => { + if (change instanceof InsertChange) { + recorder.insertLeft(change.pos, change.toAdd); + } + }); + + host.commitUpdate(recorder); +} + +/** Wraps the internal find module from options with undefined path handling */ +export function findModuleFromOptions(host: Tree, options: ComponentOptions): string | undefined { + const workspace = getWorkspace(host); + + if (!options.project) { + options.project = Object.keys(workspace.projects)[0]; + } + + const project = workspace.projects[options.project]; + + if (options.path === undefined) { + options.path = `/${project.root}/src/app`; + } + + return internalFindModule(host, options); +} diff --git a/src/cdk/schematics/utils/ast/ng-module-imports.ts b/src/cdk/schematics/utils/ast/ng-module-imports.ts new file mode 100644 index 000000000..761303436 --- /dev/null +++ b/src/cdk/schematics/utils/ast/ng-module-imports.ts @@ -0,0 +1,78 @@ +/** + * @license + * Copyright Google LLC 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 {Tree} from '@angular-devkit/schematics'; +import * as ts from 'typescript'; + +/** + * Whether the Angular module in the given path imports the specifed module class name. + */ +export function hasNgModuleImport(tree: Tree, modulePath: string, className: string): boolean { + const moduleFileContent = tree.read(modulePath); + + if (!moduleFileContent) { + throw new Error(`Could not read Angular module file: ${modulePath}`); + } + + const parsedFile = ts.createSourceFile(modulePath, moduleFileContent.toString(), + ts.ScriptTarget.Latest, true); + let ngModuleMetadata: ts.ObjectLiteralExpression | null = null; + + const findModuleDecorator = (node: ts.Node) => { + if (ts.isDecorator(node) && ts.isCallExpression(node.expression) && + isNgModuleCallExpression(node.expression)) { + ngModuleMetadata = node.expression.arguments[0] as ts.ObjectLiteralExpression; + return; + } + + ts.forEachChild(node, findModuleDecorator); + }; + + ts.forEachChild(parsedFile, findModuleDecorator); + + if (!ngModuleMetadata) { + throw new Error(`Could not find NgModule declaration inside: "${modulePath}"`); + } + + for (let property of ngModuleMetadata!.properties) { + if (!ts.isPropertyAssignment(property) || property.name.getText() !== 'imports' || + !ts.isArrayLiteralExpression(property.initializer)) { + continue; + } + + if (property.initializer.elements.some(element => element.getText() === className)) { + return true; + } + } + + return false; +} + +/** + * Resolves the last identifier that is part of the given expression. This helps resolving + * identifiers of nested property access expressions (e.g. myNamespace.core.NgModule). + */ +function resolveIdentifierOfExpression(expression: ts.Expression): ts.Identifier | null { + if (ts.isIdentifier(expression)) { + return expression; + } else if (ts.isPropertyAccessExpression(expression)) { + return resolveIdentifierOfExpression(expression.expression); + } + return null; +} + +/** Whether the specified call expression is referring to a NgModule definition. */ +function isNgModuleCallExpression(callExpression: ts.CallExpression): boolean { + if (!callExpression.arguments.length || + !ts.isObjectLiteralExpression(callExpression.arguments[0])) { + return false; + } + + const decoratorIdentifier = resolveIdentifierOfExpression(callExpression.expression); + return decoratorIdentifier ? decoratorIdentifier.text === 'NgModule' : false; +} diff --git a/src/cdk/schematics/utils/build-component.ts b/src/cdk/schematics/utils/build-component.ts new file mode 100644 index 000000000..8b4829202 --- /dev/null +++ b/src/cdk/schematics/utils/build-component.ts @@ -0,0 +1,239 @@ +/* tslint:disable */ + +/** + * @license + * Copyright Google LLC 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 {strings, template as interpolateTemplate} from '@angular-devkit/core'; +import { + apply, + branchAndMerge, + chain, + filter, + mergeWith, + move, + noop, + Rule, + SchematicsException, + template, + Tree, + url, +} from '@angular-devkit/schematics'; +import {FileSystemSchematicContext} from '@angular-devkit/schematics/tools'; +import {Schema as ComponentOptions} from '@schematics/angular/component/schema'; +import { + addDeclarationToModule, + addEntryComponentToModule, + addExportToModule, +} from '@schematics/angular/utility/ast-utils'; +import {InsertChange} from '@schematics/angular/utility/change'; +import {getWorkspace} from '@schematics/angular/utility/config'; +import {buildRelativePath, findModuleFromOptions} from '@schematics/angular/utility/find-module'; +import {parseName} from '@schematics/angular/utility/parse-name'; +import {buildDefaultPath} from '@schematics/angular/utility/project'; +import {validateHtmlSelector, validateName} from '@schematics/angular/utility/validation'; +import {readFileSync, statSync} from 'fs'; +import {dirname, join, resolve} from 'path'; +import {getProjectFromWorkspace} from './get-project'; +import {getDefaultComponentOptions} from './schematic-options'; +import {ts} from './version-agnostic-typescript'; + +function readIntoSourceFile(host: Tree, modulePath: string) { + const text = host.read(modulePath); + if (text === null) { + throw new SchematicsException(`File ${modulePath} does not exist.`); + } + + return ts.createSourceFile(modulePath, text.toString('utf-8'), ts.ScriptTarget.Latest, true); +} + +function addDeclarationToNgModule(options: ComponentOptions): Rule { + return (host: Tree) => { + if (options.skipImport || !options.module) { + return host; + } + + const modulePath = options.module; + const source = readIntoSourceFile(host, modulePath); + + const componentPath = `/${options.path}/` + + (options.flat ? '' : strings.dasherize(options.name) + '/') + + strings.dasherize(options.name) + + '.component'; + const relativePath = buildRelativePath(modulePath, componentPath); + const classifiedName = strings.classify(`${options.name}Component`); + + const declarationChanges = addDeclarationToModule( + source, + modulePath, + classifiedName, + relativePath); + + const declarationRecorder = host.beginUpdate(modulePath); + for (const change of declarationChanges) { + if (change instanceof InsertChange) { + declarationRecorder.insertLeft(change.pos, change.toAdd); + } + } + host.commitUpdate(declarationRecorder); + + if (options.export) { + // Need to refresh the AST because we overwrote the file in the host. + const source = readIntoSourceFile(host, modulePath); + const exportRecorder = host.beginUpdate(modulePath); + + const exportChanges = addExportToModule( + source, + modulePath, + strings.classify(`${options.name}Component`), + relativePath); + + for (const change of exportChanges) { + if (change instanceof InsertChange) { + exportRecorder.insertLeft(change.pos, change.toAdd); + } + } + host.commitUpdate(exportRecorder); + } + + if (options.entryComponent) { + // Need to refresh the AST because we overwrote the file in the host. + const source = readIntoSourceFile(host, modulePath); + const entryComponentRecorder = host.beginUpdate(modulePath); + + const entryComponentChanges = addEntryComponentToModule( + source, + modulePath, + strings.classify(`${options.name}Component`), + relativePath); + + for (const change of entryComponentChanges) { + if (change instanceof InsertChange) { + entryComponentRecorder.insertLeft(change.pos, change.toAdd); + } + } + host.commitUpdate(entryComponentRecorder); + } + + + return host; + }; +} + + +function buildSelector(options: ComponentOptions, projectPrefix: string) { + let selector = strings.dasherize(options.name); + if (options.prefix) { + selector = `${options.prefix}-${selector}`; + } else if (options.prefix === undefined && projectPrefix) { + selector = `${projectPrefix}-${selector}`; + } + + return selector; +} + +/** + * Indents the text content with the amount of specified spaces. The spaces will be added after + * every line-break. This utility function can be used inside of EJS templates to properly + * include the additional files. + */ +function indentTextContent(text: string, numSpaces: number): string { + // In the Material project there should be only LF line-endings, but the schematic files + // are not being linted and therefore there can be also CRLF or just CR line-endings. + return text.replace(/(\r\n|\r|\n)/g, `$1${' '.repeat(numSpaces)}`); +} + +/** + * Rule that copies and interpolates the files that belong to this schematic context. Additionally + * a list of file paths can be passed to this rule in order to expose them inside the EJS + * template context. + * + * This allows inlining the external template or stylesheet files in EJS without having + * to manually duplicate the file content. + */ +export function buildComponent(options: ComponentOptions, + additionalFiles: {[key: string]: string} = {}): Rule { + + return (host: Tree, context: FileSystemSchematicContext) => { + const workspace = getWorkspace(host); + const project = getProjectFromWorkspace(workspace, options.project); + const defaultComponentOptions = getDefaultComponentOptions(project); + + // TODO(devversion): Remove if we drop support for older CLI versions. + // This handles an unreported breaking change from the @angular-devkit/schematics. Previously + // the description path resolved to the factory file, but starting from 6.2.0, it resolves + // to the factory directory. + const schematicPath = statSync(context.schematic.description.path).isDirectory() ? + context.schematic.description.path : + dirname(context.schematic.description.path); + + const schematicFilesUrl = './files'; + const schematicFilesPath = resolve(schematicPath, schematicFilesUrl); + + // Add the default component option values to the options if an option is not explicitly + // specified but a default component option is available. + Object.keys(options) + .filter(optionName => options[optionName] == null && defaultComponentOptions[optionName]) + .forEach(optionName => options[optionName] = defaultComponentOptions[optionName]); + + if (options.path === undefined) { + // TODO(jelbourn): figure out if the need for this `as any` is a bug due to two different + // incompatible `WorkspaceProject` classes in @angular-devkit + options.path = buildDefaultPath(project as any); + } + + options.module = findModuleFromOptions(host, options); + + const parsedPath = parseName(options.path!, options.name); + + options.name = parsedPath.name; + options.path = parsedPath.path; + options.selector = options.selector || buildSelector(options, project.prefix); + + validateName(options.name); + validateHtmlSelector(options.selector!); + + // Object that will be used as context for the EJS templates. + const baseTemplateContext = { + ...strings, + 'if-flat': (s: string) => options.flat ? '' : s, + ...options, + }; + + // Key-value object that includes the specified additional files with their loaded content. + // The resolved contents can be used inside EJS templates. + const resolvedFiles = {}; + + for (let key in additionalFiles) { + if (additionalFiles[key]) { + const fileContent = readFileSync(join(schematicFilesPath, additionalFiles[key]), 'utf-8'); + + // Interpolate the additional files with the base EJS template context. + resolvedFiles[key] = interpolateTemplate(fileContent)(baseTemplateContext); + } + } + + const templateSource = apply(url(schematicFilesUrl), [ + options.spec ? noop() : filter(path => !path.endsWith('.spec.ts')), + options.inlineStyle ? filter(path => !path.endsWith('.__styleext__')) : noop(), + options.inlineTemplate ? filter(path => !path.endsWith('.html')) : noop(), + // Treat the template options as any, because the type definition for the template options + // is made unnecessarily explicit. Every type of object can be used in the EJS template. + template({indentTextContent, resolvedFiles, ...baseTemplateContext} as any), + // TODO(devversion): figure out why we cannot just remove the first parameter + // See for example: angular-cli#schematics/angular/component/index.ts#L160 + move(null as any, parsedPath.path), + ]); + + return chain([ + branchAndMerge(chain([ + addDeclarationToNgModule(options), + mergeWith(templateSource), + ])), + ])(host, context); + }; +} diff --git a/src/cdk/schematics/utils/get-project.ts b/src/cdk/schematics/utils/get-project.ts new file mode 100644 index 000000000..4d4045d2e --- /dev/null +++ b/src/cdk/schematics/utils/get-project.ts @@ -0,0 +1,26 @@ +/** + * @license + * Copyright Google LLC 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 {WorkspaceSchema, WorkspaceProject} from '@angular-devkit/core/src/workspace'; + +/** + * Finds the specified project configuration in the workspace. Throws an error if the project + * couldn't be found. + */ +export function getProjectFromWorkspace( + workspace: WorkspaceSchema, + projectName?: string): WorkspaceProject { + + const project = workspace.projects[projectName || workspace.defaultProject!]; + + if (!project) { + throw new Error(`Could not find project in workspace: ${projectName}`); + } + + return project; +} diff --git a/src/cdk/schematics/utils/index.ts b/src/cdk/schematics/utils/index.ts new file mode 100644 index 000000000..ab3e2b8c8 --- /dev/null +++ b/src/cdk/schematics/utils/index.ts @@ -0,0 +1,18 @@ +/** + * @license + * Copyright Google LLC 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 + */ + +export * from './ast'; +export * from './ast/ng-module-imports'; +export * from './build-component'; +export * from './get-project'; +export * from './parse5-element'; +export * from './project-main-file'; +export * from './project-style-file'; +export * from './project-targets'; +export * from './schematic-options'; +export * from './version-agnostic-typescript'; diff --git a/src/cdk/schematics/utils/parse5-element.ts b/src/cdk/schematics/utils/parse5-element.ts new file mode 100644 index 000000000..e833805d4 --- /dev/null +++ b/src/cdk/schematics/utils/parse5-element.ts @@ -0,0 +1,32 @@ +/** + * @license + * Copyright Google LLC 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 {DefaultTreeElement} from 'parse5'; + +/** Determines the indentation of child elements for the given Parse5 element. */ +export function getChildElementIndentation(element: DefaultTreeElement) { + const childElement = element.childNodes + .find(node => node['tagName']) as DefaultTreeElement | null; + + if ((childElement && !childElement.sourceCodeLocation) || !element.sourceCodeLocation) { + throw new Error('Cannot determine child element indentation because the specified Parse5 ' + + 'element does not have any source code location metadata.'); + } + + const startColumns = childElement ? + // In case there are child elements inside of the element, we assume that their + // indentation is also applicable for other child elements. + childElement.sourceCodeLocation!.startCol : + // In case there is no child element, we just assume that child elements should be indented + // by two spaces. + element.sourceCodeLocation!.startCol + 2; + + // Since Parse5 does not set the `startCol` properties as zero-based, we need to subtract + // one column in order to have a proper zero-based offset for the indentation. + return startColumns - 1; +} diff --git a/src/cdk/schematics/utils/project-main-file.ts b/src/cdk/schematics/utils/project-main-file.ts new file mode 100644 index 000000000..e822f5fd3 --- /dev/null +++ b/src/cdk/schematics/utils/project-main-file.ts @@ -0,0 +1,23 @@ +/** + * @license + * Copyright Google LLC 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 {WorkspaceProject} from '@angular-devkit/core/src/workspace'; +import {SchematicsException} from '@angular-devkit/schematics'; +import {getProjectTargetOptions} from './project-targets'; + +/** Looks for the main TypeScript file in the given project and returns its path. */ +export function getProjectMainFile(project: WorkspaceProject): string { + const buildOptions = getProjectTargetOptions(project, 'build'); + + if (!buildOptions.main) { + throw new SchematicsException(`Could not find the project main file inside of the ` + + `workspace config (${project.sourceRoot})`); + } + + return buildOptions.main; +} diff --git a/src/cdk/schematics/utils/project-style-file.ts b/src/cdk/schematics/utils/project-style-file.ts new file mode 100644 index 000000000..8b9cf0751 --- /dev/null +++ b/src/cdk/schematics/utils/project-style-file.ts @@ -0,0 +1,50 @@ +/** + * @license + * Copyright Google LLC 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 {normalize} from '@angular-devkit/core'; +import {WorkspaceProject} from '@angular-devkit/core/src/workspace'; +import {getProjectTargetOptions} from './project-targets'; + +/** Regular expression that matches all possible Angular CLI default style files. */ +const defaultStyleFileRegex = /styles\.(c|le|sc)ss/; + +/** Regular expression that matches all files that have a proper stylesheet extension. */ +const validStyleFileRegex = /\.(c|le|sc)ss/; + +/** + * Gets a style file with the given extension in a project and returns its path. If no + * extension is specified, any style file with a valid extension will be returned. + */ +export function getProjectStyleFile(project: WorkspaceProject, extension?: string): string | null { + const buildOptions = getProjectTargetOptions(project, 'build'); + + if (buildOptions.styles && buildOptions.styles.length) { + const styles = buildOptions.styles.map(s => typeof s === 'string' ? s : s.input); + + // Look for the default style file that is generated for new projects by the Angular CLI. This + // default style file is usually called `styles.ext` unless it has been changed explicitly. + const defaultMainStylePath = styles + .find(file => extension ? file === `styles.${extension}` : defaultStyleFileRegex.test(file)); + + if (defaultMainStylePath) { + return normalize(defaultMainStylePath); + } + + // If no default style file could be found, use the first style file that matches the given + // extension. If no extension specified explicitly, we look for any file with a valid style + // file extension. + const fallbackStylePath = styles + .find(file => extension ? file.endsWith(`.${extension}`) : validStyleFileRegex.test(file)); + + if (fallbackStylePath) { + return normalize(fallbackStylePath); + } + } + + return null; +} diff --git a/src/cdk/schematics/utils/project-targets.ts b/src/cdk/schematics/utils/project-targets.ts new file mode 100644 index 000000000..39a0e5ad6 --- /dev/null +++ b/src/cdk/schematics/utils/project-targets.ts @@ -0,0 +1,31 @@ +/** + * @license + * Copyright Google LLC 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 {WorkspaceProject} from '@angular-devkit/core/src/workspace'; + +/** Resolves the architect options for the build target of the given project. */ +export function getProjectTargetOptions(project: WorkspaceProject, buildTarget: string) { + if (project.targets && + project.targets[buildTarget] && + project.targets[buildTarget].options) { + + return project.targets[buildTarget].options; + } + + // TODO(devversion): consider removing this architect check if the CLI completely switched + // over to `targets`, and the `architect` support has been removed. + // See: https://github.com/angular/angular-cli/commit/307160806cb48c95ecb8982854f452303801ac9f + if (project.architect && + project.architect[buildTarget] && + project.architect[buildTarget].options) { + + return project.architect[buildTarget].options; + } + + throw new Error(`Cannot determine project target configuration for: ${buildTarget}.`); +} diff --git a/src/cdk/schematics/utils/schematic-options.ts b/src/cdk/schematics/utils/schematic-options.ts new file mode 100644 index 000000000..a6cdc9f0b --- /dev/null +++ b/src/cdk/schematics/utils/schematic-options.ts @@ -0,0 +1,46 @@ +/** + * @license + * Copyright Google LLC 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 {WorkspaceProject} from '@angular-devkit/core/src/workspace'; + + +/** + * Returns the default options for the `@schematics/angular:component` schematic which would + * have been specified at project initialization (ng new or ng init). + * + * This is necessary because the Angular CLI only exposes the default values for the "--style", + * "--inlineStyle", "--skipTests" and "--inlineTemplate" options to the "component" schematic. + */ +export function getDefaultComponentOptions(project: WorkspaceProject) { + // Note: Not all options which are available when running "ng new" will be stored in the + // workspace config. List of options which will be available in the configuration: + // angular/angular-cli/blob/master/packages/schematics/angular/application/index.ts#L109-L131 + return { + styleext: getDefaultComponentOption(project, 'styleext', 'css'), + inlineStyle: getDefaultComponentOption(project, 'inlineStyle', false), + inlineTemplate: getDefaultComponentOption(project, 'inlineTemplate', false), + spec: getDefaultComponentOption(project, 'spec', true), + }; +} + +/** + * Gets the default value for the specified option. The default options will be determined + * by looking at the stored schematic options for `@schematics/angular:component` in the + * CLI workspace configuration. + */ +function getDefaultComponentOption(project: WorkspaceProject, optionName: string, + fallbackValue: T): T | null { + if (project.schematics && + project.schematics['@schematics/angular:component'] && + project.schematics['@schematics/angular:component'][optionName] != null) { + + return project.schematics['@schematics/angular:component'][optionName]; + } + + return fallbackValue; +} diff --git a/src/cdk/schematics/utils/version-agnostic-typescript.ts b/src/cdk/schematics/utils/version-agnostic-typescript.ts new file mode 100644 index 000000000..66cb5d0bd --- /dev/null +++ b/src/cdk/schematics/utils/version-agnostic-typescript.ts @@ -0,0 +1,38 @@ +/** + * @license + * Copyright Google LLC 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 + */ + +/** + * This is just a type import and won't be generated in the release output. + * + * Note that we always need to adjust this type import based on the location of the Typescript + * dependency that will be shipped with `@schematics/angular`. + */ +import typescript = require('typescript'); + +/** + * This is an agnostic re-export of TypeScript. Depending on the context, this module file will + * return the TypeScript version that is being shipped within the `@schematics/angular` package, + * or fall back to the TypeScript version that has been flattened in the node modules. + * + * This is necessary because we parse TypeScript files and pass the resolved AST to the + * `@schematics/angular` package which might have a different TypeScript version installed. + */ +let ts: typeof typescript; + +try { + ts = require('@schematics/angular/node_modules/typescript'); +} catch { + try { + ts = require('typescript'); + } catch { + throw new Error('Error: Could not find a TypeScript version for the schematics. ' + + 'Please report an issue on the Angular Material repository.'); + } +} + +export {ts}; diff --git a/src/cdk/tsconfig.tests.json b/src/cdk/tsconfig.tests.json index 86ffe4f6b..6ff6dcec5 100644 --- a/src/cdk/tsconfig.tests.json +++ b/src/cdk/tsconfig.tests.json @@ -20,11 +20,15 @@ "strictMetadataEmit": true, "skipTemplateCodegen": true, "emitDecoratorMetadata": true, + "experimentalDecorators": true, "fullTemplateTypeCheck": true }, "include": [ // Include the index.ts for each secondary entry-point "./*/index.ts", "**/*.spec.ts" + ], + "exclude": [ + "**/schematics/**/*.ts" ] } diff --git a/tools/gulp/packages.ts b/tools/gulp/packages.ts index ec5f96ed3..a78044a70 100644 --- a/tools/gulp/packages.ts +++ b/tools/gulp/packages.ts @@ -11,3 +11,5 @@ mosaicPackage.exportsSecondaryEntryPointsAtRoot = true; mosaicPackage.sourceDir = join(buildConfig.packagesDir, 'lib'); cdkPackage.copySecondaryEntryPointStylesToRoot = true; + +cdkPackage.hasSchematics = true; diff --git a/tools/gulp/tsconfig.json b/tools/gulp/tsconfig.json index 2d00d36ab..4a9465299 100644 --- a/tools/gulp/tsconfig.json +++ b/tools/gulp/tsconfig.json @@ -4,7 +4,8 @@ "noUnusedParameters": true, "lib": [ "es2015", - "dom" + "dom", + "es2016.array.include" ], "module": "commonjs", "moduleResolution": "node", @@ -12,13 +13,12 @@ "strictNullChecks": true, "strictFunctionTypes": true, "noEmitOnError": true, + "noImplicitAny": true, "target": "es5", "types": [ "node" ], - "baseUrl": ".", - "paths": { - } + "paths": {} }, "files": [ "gulpfile.ts" diff --git a/tools/packages/build-package.ts b/tools/packages/build-package.ts index f9fa829b3..240c9919b 100644 --- a/tools/packages/build-package.ts +++ b/tools/packages/build-package.ts @@ -44,6 +44,9 @@ export class BuildPackage { */ copySecondaryEntryPointStylesToRoot = false; + /** Whether the build package has schematics or not. */ + hasSchematics = false; + /** * Path to the entry file of the package in the output directory. */ diff --git a/tools/packages/build-release.ts b/tools/packages/build-release.ts index 2bf3bbdaa..136c9e4f7 100644 --- a/tools/packages/build-release.ts +++ b/tools/packages/build-release.ts @@ -48,6 +48,10 @@ export function composeRelease(buildPackage: BuildPackage) { createTypingsReexportFile(releasePath, './typings/index', name); createMetadataReexportFile(releasePath, './typings/index', name, importAsName); + if (buildPackage.hasSchematics) { + copyFiles(join(packageOut, 'schematics'), '**/*', join(releasePath, 'schematics')); + } + if (buildPackage.secondaryEntryPoints.length) { createFilesForSecondaryEntryPoint(buildPackage, releasePath); } diff --git a/tools/packages/gulp/build-tasks-gulp.ts b/tools/packages/gulp/build-tasks-gulp.ts index 0e81fd4f9..5fdc87302 100644 --- a/tools/packages/gulp/build-tasks-gulp.ts +++ b/tools/packages/gulp/build-tasks-gulp.ts @@ -2,9 +2,9 @@ import { dest, src, task } from 'gulp'; import { join } from 'path'; import { BuildPackage } from '../build-package'; -import { inlineResourcesForDirectory } from '../inline-resources'; - import { composeRelease } from '../build-release'; +import { inlineResourcesForDirectory } from '../inline-resources'; +import { tsCompile } from '../ts-compile'; import { buildScssPipeline } from './build-scss-pipeline'; import { sequenceTask } from './sequence-task'; @@ -34,6 +34,14 @@ export function createPackageBuildTasks(buildPackage: BuildPackage, preBuildTask // Glob that matches every HTML file in the current package. const htmlGlob = join(buildPackage.sourceDir, '**/*.html'); + const schematicsDir = join(buildPackage.sourceDir, 'schematics'); + + // Pattern matching schematics files to be copied into the output directory. + const schematicsGlobs = [ + join(schematicsDir, '**/+(data|files)/**/*'), + join(schematicsDir, '**/+(schema|collection|migration).json') + ]; + task(`${taskName}:clean-build`, sequenceTask('clean', `${taskName}:build`)); task(`${taskName}:build`, sequenceTask( @@ -66,11 +74,22 @@ export function createPackageBuildTasks(buildPackage: BuildPackage, preBuildTask task(`${taskName}:build:bundles`, () => buildPackage.createBundles()); - task(`${taskName}:assets`, [ + /** + * Asset tasks. Building Sass files and inlining CSS, HTML files into the ESM output. + */ + const assetTasks = [ `${taskName}:assets:scss`, `${taskName}:assets:copy-styles`, `${taskName}:assets:html` - ]); + ]; + + // In case the build package has schematics, we need to build them like assets because + // those are not intended to be entry-points. + if (buildPackage.hasSchematics) { + assetTasks.push(`${taskName}:assets:schematics`); + } + + task(`${taskName}:assets`, assetTasks); task(`${taskName}:assets:scss`, () => { buildScssPipeline(buildPackage.sourceDir, true) @@ -91,4 +110,12 @@ export function createPackageBuildTasks(buildPackage: BuildPackage, preBuildTask }); task(`${taskName}:assets:inline`, () => inlineResourcesForDirectory(buildPackage.outputDir)); + + task(`${taskName}:assets:schematics-ts`, () => { + return tsCompile('tsc', ['-p', join(schematicsDir, 'tsconfig.json')]); + }); + + task(`${taskName}:assets:schematics`, [`${taskName}:assets:schematics-ts`], () => { + return src(schematicsGlobs).pipe(dest(join(buildPackage.outputDir, 'schematics'))); + }); } diff --git a/tools/packages/ts-compile.ts b/tools/packages/ts-compile.ts index 6b7d80478..7a266f058 100644 --- a/tools/packages/ts-compile.ts +++ b/tools/packages/ts-compile.ts @@ -15,7 +15,7 @@ export function tsCompile(binary: 'tsc' | 'ngc', flags: string[]) { // tslint:disable-next-line return new Promise((resolve, reject) => { - const binaryPath = resolvePath('./node_modules/.bin/ngc'); + const binaryPath = resolvePath(`./node_modules/.bin/${binary}`); const childProcess = spawn(binaryPath, flags, {shell: true}); // Pipe stdout and stderr from the child process.