From 5d5a8742ad8072fa1aa1fe2c25d75adfeeb915e3 Mon Sep 17 00:00:00 2001 From: Nikita Poltoratsky Date: Thu, 15 Nov 2018 11:36:28 +0200 Subject: [PATCH] feat(theme): ng-add schematic (#942) --- angular.json | 7 +- docs/articles/install-into-existing.md | 47 +++- docs/tsconfig.app.json | 3 +- package.json | 9 +- scripts/ci/travis-script.sh | 1 + .../inline-resources/inline-resources.ts | 9 +- src/framework/auth/package.json | 2 +- src/framework/theme/package.json | 5 +- .../theme/schematics/collection.json | 16 ++ .../theme/schematics/ng-add/index.spec.ts | 216 ++++++++++++++++++ .../theme/schematics/ng-add/index.ts | 63 +++++ .../app-routing-module-content.ts | 18 ++ .../ng-add/register-modules/index.ts | 112 +++++++++ .../schematics/ng-add/register-theme/index.ts | 107 +++++++++ .../ng-add/register-theme/theme-content.ts | 26 +++ .../theme/schematics/ng-add/schema.json | 38 +++ .../theme/schematics/ng-add/schema.ts | 13 ++ .../theme/schematics/ng-add/setup.ts | 25 ++ .../schematics/ng-add/wrap-in-layout/index.ts | 53 +++++ .../ng-add/wrap-in-layout/layout-content.ts | 47 ++++ src/framework/theme/schematics/util/ast.ts | 29 +++ .../theme/schematics/util/component.ts | 92 ++++++++ src/framework/theme/schematics/util/file.ts | 46 ++++ src/framework/theme/schematics/util/index.ts | 11 + .../theme/schematics/util/package.ts | 137 +++++++++++ .../theme/schematics/util/project.ts | 18 ++ tsconfig.publish.json | 3 +- tsconfig.schematics.json | 34 +++ tsconfig.schematics.publish.json | 34 +++ 29 files changed, 1207 insertions(+), 14 deletions(-) create mode 100644 src/framework/theme/schematics/collection.json create mode 100644 src/framework/theme/schematics/ng-add/index.spec.ts create mode 100644 src/framework/theme/schematics/ng-add/index.ts create mode 100644 src/framework/theme/schematics/ng-add/register-modules/app-routing-module-content.ts create mode 100644 src/framework/theme/schematics/ng-add/register-modules/index.ts create mode 100644 src/framework/theme/schematics/ng-add/register-theme/index.ts create mode 100644 src/framework/theme/schematics/ng-add/register-theme/theme-content.ts create mode 100644 src/framework/theme/schematics/ng-add/schema.json create mode 100644 src/framework/theme/schematics/ng-add/schema.ts create mode 100644 src/framework/theme/schematics/ng-add/setup.ts create mode 100644 src/framework/theme/schematics/ng-add/wrap-in-layout/index.ts create mode 100644 src/framework/theme/schematics/ng-add/wrap-in-layout/layout-content.ts create mode 100644 src/framework/theme/schematics/util/ast.ts create mode 100644 src/framework/theme/schematics/util/component.ts create mode 100644 src/framework/theme/schematics/util/file.ts create mode 100644 src/framework/theme/schematics/util/index.ts create mode 100644 src/framework/theme/schematics/util/package.ts create mode 100644 src/framework/theme/schematics/util/project.ts create mode 100644 tsconfig.schematics.json create mode 100644 tsconfig.schematics.publish.json diff --git a/angular.json b/angular.json index 61ac2dec46..63a2c0864f 100644 --- a/angular.json +++ b/angular.json @@ -83,6 +83,9 @@ "assets": [ "src/assets", "src/favicon.ico" + ], + "exclude": [ + "./src/framework/**/schematics/**/*" ] } }, @@ -94,7 +97,9 @@ "src/tsconfig.spec.json", "scripts/gulp/tsconfig.json" ], - "exclude": [] + "exclude": [ + "node_modules/**" + ] } } } diff --git a/docs/articles/install-into-existing.md b/docs/articles/install-into-existing.md index 76539da59b..75bd01afae 100644 --- a/docs/articles/install-into-existing.md +++ b/docs/articles/install-into-existing.md @@ -13,11 +13,48 @@ In case you want to start based on our ngx-admin starter kit, please check out [
-## Installing dependencies +## Using Angular CLI + +
+ +### Installation + +We strongly recommend to develop Angular with @angular/cli, you can install it with the following command. + +```bash +npm install -g @angular/cli +``` +
+ +### Create a New Project + +A new project can be created using Angular CLI tools. + +```bash +ng new PROJECT-NAME +``` +
+ +### Install Nebular + +Nebular support init configuration with schematics. So, you may just add it to your project. + +```bash +ng add @nebular/theme +``` + +That's it. Nebular has to be ready to go now. +
+ +## Manually + +
+ +### Installing dependencies At this step, we assume you already have Angular modules installed. -## Install Nebular modules +### Install Nebular modules ```bash npm install --save @nebular/theme @angular/cdk @angular/animations @@ -25,7 +62,7 @@ npm install --save @nebular/theme @angular/cdk @angular/animations Additionally you can install Auth and Security `npm install --save @nebular/auth @nebular/security`
-## Configure Nebular +### Configure Nebular At this stage you have everything in place, let's configure Nebular in the app module. @@ -48,7 +85,7 @@ export class AppModule { Same way you can enable Auth Module (more details under [Auth Module Concepts & Install](docs/auth/conceptsinstall) article).
-## Install Styles +### Install Styles Now, let's import Nebular styles Include default Nebular theme CSS files into your `angular.json` file: @@ -68,7 +105,7 @@ Include default Nebular theme CSS files into your `angular.json` file:
-## Create a page +### Create a page Now, let's create a simple Nebular page (header + sidebar) in your project. We suppose that you have a separate module per page, let's open your `some-page.module.ts` and import necessary layout components: diff --git a/docs/tsconfig.app.json b/docs/tsconfig.app.json index f741d5d86b..52d70edd56 100644 --- a/docs/tsconfig.app.json +++ b/docs/tsconfig.app.json @@ -27,7 +27,8 @@ "**/*.spec.ts", "../src/framework/**/*.spec.ts", "assets/**/*.ts", - "dist/**/*" + "dist/**/*", + "../src/framework/**/schematics/**/*" ], "include": [ "../docs/**/*", diff --git a/package.json b/package.json index e0b1cdc14a..62df6b3bb7 100644 --- a/package.json +++ b/package.json @@ -32,8 +32,11 @@ "docs:gh-pages": "npm run docs:build && npm run ngh -- --dir ./docs/dist", "build": "ng build", "build:wp": "npm run build -- playground-wp --prod --aot", - "build:package": "npm-run-all -s clean:tmp:lib gulp build:ts build:inline-resources build:bundle clean:tmp", + "build:package": "npm-run-all -s clean:tmp:lib gulp build:ts build:schematics:package build:inline-resources build:bundle clean:tmp", "build:ts": "tsc -p tsconfig.publish.json && ngc -p tsconfig.publish.json", + "build:schematics:package": "tsc -p tsconfig.schematics.publish.json", + "build:schematics:dev": "rimraf ./src/.lib && tsc -p tsconfig.schematics.json && npm run build:inline-schematics-resources", + "build:inline-schematics-resources": "gulp inline-schematics-resources", "build:inline-resources": "gulp inline-resources", "build:bundle": "gulp bundle", "build:rename:dev": "gulp bundle:rename-dev", @@ -42,6 +45,7 @@ "test:wp": "npm run test -- playground-wp", "test:watch": "ng test playground-wp --watch", "test:coverage": "rimraf coverage && ng test playground --code-coverage --watch=false", + "test:schematics": "npm run build:schematics:dev && jasmine ./src/.lib/**/*.spec.js", "lint": "ng lint playground --type-check", "lint:styles": "stylelint ./src/framework/**/*.scss && stylelint ./docs/**/*.scss", "lint:fix": "npm run lint -- --fix", @@ -113,6 +117,7 @@ "@angular/cli": "7.0.2", "@angular/compiler-cli": "7.0.0", "@angular/language-service": "7.0.0", + "@schematics/angular": "^7.0.5", "@types/gulp": "3.8.36", "@types/highlight.js": "9.12.2", "@types/jasmine": "2.8.3", @@ -163,4 +168,4 @@ "uglifyjs-webpack-plugin": "1.1.5" }, "schematics": "./schematics/dist/collection.json" -} \ No newline at end of file +} diff --git a/scripts/ci/travis-script.sh b/scripts/ci/travis-script.sh index c41dc3cf8f..b9987d45fe 100755 --- a/scripts/ci/travis-script.sh +++ b/scripts/ci/travis-script.sh @@ -51,6 +51,7 @@ elif [[ "${MODE}" =~ ^.*_(e2e)$ ]]; then elif [[ "${MODE}" =~ ^.*_(unit_test)$ ]]; then npm run ci:test npm install codecov -g && codecov + npm run test:schematics elif [[ "${MODE}" = docs ]]; then npm run ci:docs elif [[ "${MODE}" = deploy_dev ]]; then diff --git a/scripts/gulp/tasks/inline-resources/inline-resources.ts b/scripts/gulp/tasks/inline-resources/inline-resources.ts index 3dc6a73433..fd2af41dea 100644 --- a/scripts/gulp/tasks/inline-resources/inline-resources.ts +++ b/scripts/gulp/tasks/inline-resources/inline-resources.ts @@ -2,7 +2,7 @@ import { dest, src, task } from 'gulp'; import { copyResources } from './copy-resources'; import { BUILD_DIR, LIB_DIR } from '../config'; -task('inline-resources', () => { +task('inline-resources', ['inline-schematics-resources'], () => { src([ `${BUILD_DIR}/**/*.html`, `${BUILD_DIR}/**/*.css`, @@ -14,3 +14,10 @@ task('inline-resources', () => { .pipe(dest(LIB_DIR)) .on('end', () => copyResources(LIB_DIR)); }); + +task('inline-schematics-resources', () => { + src([ + `./src/framework/**/schematics/**/*.json`, + `./src/framework/**/package.json`, + ]).pipe(dest(LIB_DIR)); +}); diff --git a/src/framework/auth/package.json b/src/framework/auth/package.json index d93946bc28..0eb501a45e 100644 --- a/src/framework/auth/package.json +++ b/src/framework/auth/package.json @@ -35,4 +35,4 @@ "@nebular/theme": "2.0.2", "rxjs": "^6.3.0" } -} \ No newline at end of file +} diff --git a/src/framework/theme/package.json b/src/framework/theme/package.json index c1036daada..35eff74917 100644 --- a/src/framework/theme/package.json +++ b/src/framework/theme/package.json @@ -34,5 +34,6 @@ }, "dependencies": { "intersection-observer": "0.5.0" - } -} \ No newline at end of file + }, + "schematics": "./schematics/collection.json" +} diff --git a/src/framework/theme/schematics/collection.json b/src/framework/theme/schematics/collection.json new file mode 100644 index 0000000000..1d2eac9f9d --- /dev/null +++ b/src/framework/theme/schematics/collection.json @@ -0,0 +1,16 @@ +{ + "$schema": "../node_modules/@angular-devkit/schematics/collection-schema.json", + "schematics": { + "ng-add": { + "description": "Adds Nebular Theme to the application.", + "factory": "./ng-add/index", + "schema": "./ng-add/schema.json" + }, + "setup": { + "description": "Sets up the specified project after the ng-add dependencies have been installed.", + "private": true, + "factory": "./ng-add/setup", + "schema": "./ng-add/schema.json" + } + } +} diff --git a/src/framework/theme/schematics/ng-add/index.spec.ts b/src/framework/theme/schematics/ng-add/index.spec.ts new file mode 100644 index 0000000000..5449fc2485 --- /dev/null +++ b/src/framework/theme/schematics/ng-add/index.spec.ts @@ -0,0 +1,216 @@ +/* + * @license + * Copyright Akveo. All Rights Reserved. + * Licensed under the MIT License. See License.txt in the project root for license information. + */ + +import { addModuleImportToRootModule, getProjectFromWorkspace, getProjectTargetOptions } from '@angular/cdk/schematics'; +import { Tree } from '@angular-devkit/schematics'; +import { SchematicTestRunner } from '@angular-devkit/schematics/testing'; + +import { getFileContent } from '@schematics/angular/utility/test'; +import { getWorkspace } from '@schematics/angular/utility/config'; +import { Schema as WorkspaceOptions } from '@schematics/angular/workspace/schema'; +import { Schema as ApplicationOptions } from '@schematics/angular/application/schema'; + +import { Schema as NgAddOptions } from './schema'; + +const workspaceOptions: WorkspaceOptions = { + name: 'workspace', + newProjectRoot: 'projects', + version: '7.0.0', +}; + +const defaultAppOptions: ApplicationOptions = { + name: 'nebular', + inlineStyle: false, + inlineTemplate: false, + routing: false, + style: 'scss', + skipTests: false, + skipPackageJson: false, +}; + +function createTestWorkspace(runner: SchematicTestRunner, appOptions: Partial = {}) { + const workspace = runner.runExternalSchematic('@schematics/angular', 'workspace', workspaceOptions); + return runner.runExternalSchematic('@schematics/angular', 'application', + { ...defaultAppOptions, ...appOptions }, workspace); +} + +function getPackageDependencies(tree: Tree): any { + const packageJson = JSON.parse(getFileContent(tree, '/package.json')); + return packageJson.dependencies; +} + +describe('ng-add', () => { + let runner: SchematicTestRunner; + let appTree: Tree; + + function runNgAddSchematic(options: Partial = {}): Tree { + return runner.runSchematic('ng-add', options, appTree); + } + + function runSetupSchematic(options: Partial = {}) { + return runner.runSchematic('setup', options, appTree); + } + + beforeEach(() => { + const collectionPath = require.resolve('../collection.json'); + runner = new SchematicTestRunner('schematics', collectionPath); + appTree = createTestWorkspace(runner); + }); + + it('should add @angular/cdk in package.json', () => { + const tree = runNgAddSchematic(); + const dependencies = getPackageDependencies(tree); + const angularCoreVersion = dependencies['@angular/core']; + + expect(dependencies['@angular/cdk']).toBeDefined(); + expect(dependencies['@angular/cdk']).toBe(angularCoreVersion); + }); + + it('should add @angular/animations in package.json', function () { + const tree = runNgAddSchematic(); + const dependencies = getPackageDependencies(tree); + const angularCoreVersion = dependencies['@angular/core']; + + expect(dependencies['@angular/animations']).toBeDefined(); + expect(dependencies['@angular/animations']).toBe(angularCoreVersion); + }); + + it('should add @nebular/theme in package.json', function () { + const tree = runNgAddSchematic(); + const dependencies = getPackageDependencies(tree); + const nebularThemeVersion = require('../../package.json').version; + + expect(dependencies['@nebular/theme']).toBeDefined(); + expect(dependencies['@nebular/theme']).toBe(nebularThemeVersion); + }); + + it('should add nebular-icons in package.json', function () { + const tree = runNgAddSchematic(); + const dependencies = getPackageDependencies(tree); + const nebularIconsVersion = require('../../package.json').peerDependencies['nebular-icons']; + + expect(dependencies['nebular-icons']).toBeDefined(); + expect(dependencies['nebular-icons']).toBe(nebularIconsVersion); + }); + + it('should register NbThemeModule.forRoot()', () => { + const tree = runSetupSchematic(); + const appModuleContent = tree.readContent('/projects/nebular/src/app/app.module.ts'); + + expect(appModuleContent).toContain(`NbThemeModule.forRoot({ name: 'default' })`); + }); + + it('should register NbThemeModule with specified theme', () => { + const tree = runSetupSchematic({ theme: 'cosmic' }); + const appModuleContent = tree.readContent('/projects/nebular/src/app/app.module.ts'); + + expect(appModuleContent).toContain(`NbThemeModule.forRoot({ name: 'cosmic' })`); + }); + + it('should register NbLayoutModule', () => { + const tree = runSetupSchematic(); + const appModuleContent = tree.readContent('/projects/nebular/src/app/app.module.ts'); + + expect(appModuleContent).toContain(`NbLayoutModule`); + }); + + it('should create AppRoutingModule if no Router already registered', () => { + const tree = runSetupSchematic(); + const appModuleContent = tree.readContent('/projects/nebular/src/app/app.module.ts'); + + expect(appModuleContent).toContain(`AppRoutingModule`); + expect(tree.files).toContain('/projects/nebular/src/app/app-routing.module.ts'); + }); + + it('should register inline theme if no theme already registered', () => { + const tree = runSetupSchematic({ customization: false }); + const workspace = getWorkspace(tree); + const project = getProjectFromWorkspace(workspace); + const styles = getProjectTargetOptions(project, 'build').styles; + + expect(styles).toContain('./node_modules/@nebular/theme/styles/prebuilt/default.css') + }); + + it('should create theme.scss and plug it into the project', () => { + appTree = createTestWorkspace(runner, { style: 'scss' }); + const tree = runSetupSchematic({ customization: true }); + const styles = tree.readContent('/projects/nebular/src/styles.scss'); + const themes = tree.readContent('/projects/nebular/src/themes.scss'); + + expect(styles).toContain(`@import 'themes'; + +@import '~@nebular/theme/styles/globals'; + +@include nb-install() { + @include nb-theme-global(); +}; +`); + + expect(themes).toContain(`@import '~@nebular/theme/styles/theming'; +@import '~@nebular/theme/styles/themes/default'; + +$nb-themes: nb-register-theme(( + // add your variables here like: + // color-bg: #4ca6ff, +), default, default); +`); + + }); + + it('should throw error if adding scss themes in css project', () => { + appTree = createTestWorkspace(runner, { style: 'css' }); + + expect(() => runSetupSchematic({ customization: true })).toThrow(); + }); + + it('should add the BrowserAnimationsModule to the project module', () => { + const tree = runSetupSchematic(); + const fileContent = getFileContent(tree, '/projects/nebular/src/app/app.module.ts'); + + expect(fileContent).toContain('BrowserAnimationsModule', + 'Expected the project app module to import the "BrowserAnimationsModule".'); + }); + + it('should not add BrowserAnimationsModule if NoopAnimationsModule is set up', () => { + const workspace = getWorkspace(appTree); + const project = getProjectFromWorkspace(workspace); + + // Simulate the case where a developer uses `ng-add` on an Angular CLI project which already + // explicitly uses the `NoopAnimationsModule`. It would be wrong to forcibly enable browser + // animations without knowing what other components would be affected. In this case, we + // just print a warning message. + addModuleImportToRootModule(appTree, 'NoopAnimationsModule', + '@angular/platform-browser/animations', project); + + spyOn(console, 'warn'); + runSetupSchematic(); + + expect(console.warn).toHaveBeenCalled(); + }); + + it('should add the NoopAnimationsModule to the project module', () => { + const tree = runSetupSchematic({ animations: false }); + const fileContent = getFileContent(tree, '/projects/nebular/src/app/app.module.ts'); + + expect(fileContent).toContain('NoopAnimationsModule', + 'Expected the project app module to import the "NoopAnimationsModule".'); + }); + + it('should not add NoopAnimationsModule if BrowserAnimationsModule is set up', () => { + const workspace = getWorkspace(appTree); + const project = getProjectFromWorkspace(workspace); + + // Simulate the case where a developer uses `ng-add` on an Angular CLI project which already + // explicitly uses the `BrowserAnimationsModule`. It would be wrong to forcibly change + // to noop animations. + const fileContent = addModuleImportToRootModule(appTree, 'BrowserAnimationsModule', + '@angular/platform-browser/animations', project); + runSetupSchematic({ animations: false }); + + expect(fileContent).not.toContain('NoopAnimationsModule', + 'Expected the project app module to not import the "NoopAnimationsModule".'); + }); +}); diff --git a/src/framework/theme/schematics/ng-add/index.ts b/src/framework/theme/schematics/ng-add/index.ts new file mode 100644 index 0000000000..fac7f319ae --- /dev/null +++ b/src/framework/theme/schematics/ng-add/index.ts @@ -0,0 +1,63 @@ +/* + * @license + * Copyright Akveo. All Rights Reserved. + * Licensed under the MIT License. See License.txt in the project root for license information. + */ + +import { Rule, SchematicContext, Tree } from '@angular-devkit/schematics'; +import { NodePackageInstallTask, RunSchematicTask } from '@angular-devkit/schematics/tasks'; + +import { Schema } from './schema'; +/** + * This utils has to imported directly from the `/util/package`, not from the `/util/`. + * Other utilities use `@angular/sdk/schematics` and `@schematics/angular` packages. + * But these packages are not installed in this step. + * */ +import { + addDependencyToPackageJson, + addDevDependencyToPackageJson, + getDependencyVersionFromPackageJson, + getNebularPeerDependencyVersionFromPackageJson, + getNebularVersion, +} from '../util/package'; + + +/** + * ng-add schematics, installs peer dependencies and runs project setup schematics. + * */ +export default function (options: Schema): Rule { + return (host: Tree, context: SchematicContext) => { + registerPeerDependencies(host); + runSetupSchematics(context, options); + } +} + +/** + * Add required peer dependencies in package.json if needed. + * */ +function registerPeerDependencies(host: Tree) { + const angularCoreVersion = getDependencyVersionFromPackageJson(host, '@angular/core'); + const nebularThemeVersion = getNebularVersion(); + const nebularIconsVersion = getNebularPeerDependencyVersionFromPackageJson('nebular-icons'); + + addDependencyToPackageJson(host, '@angular/cdk', angularCoreVersion); + addDependencyToPackageJson(host, '@angular/animations', angularCoreVersion); + addDependencyToPackageJson(host, '@nebular/theme', nebularThemeVersion); + addDependencyToPackageJson(host, 'nebular-icons', nebularIconsVersion); + + addDevDependencyToPackageJson(host, '@schematics/angular', angularCoreVersion); +} + +/** + * Runs `npm install` and after complete runs `setup` schematics. + * The rest part of the ng-add schematics uses `@angular/cdk/schematics` and `@schematics/angular` + * utilities. That's why we have to install `@angular/cdk` and `@schematics/angular` package + * before running Nebular setup in the project. + * + * The only possibility to run `setup` schematics after required packages installed + * is to use context tasks and add `npm install` task as the dependency to `setup` schematics task. + * */ +function runSetupSchematics(context: SchematicContext, options: Schema) { + const installTaskId = context.addTask(new NodePackageInstallTask()); + context.addTask(new RunSchematicTask('setup', options), [installTaskId]); +} diff --git a/src/framework/theme/schematics/ng-add/register-modules/app-routing-module-content.ts b/src/framework/theme/schematics/ng-add/register-modules/app-routing-module-content.ts new file mode 100644 index 0000000000..b820bc878b --- /dev/null +++ b/src/framework/theme/schematics/ng-add/register-modules/app-routing-module-content.ts @@ -0,0 +1,18 @@ +/* + * @license + * Copyright Akveo. All Rights Reserved. + * Licensed under the MIT License. See License.txt in the project root for license information. + */ + +export const appRoutingModuleContent = `import { NgModule } from '@angular/core'; + +import { Routes, RouterModule } from '@angular/router'; + +const routes: Routes = []; + +@NgModule({ + imports: [RouterModule.forRoot(routes)], + exports: [RouterModule], +}) +export class AppRoutingModule { } +`; diff --git a/src/framework/theme/schematics/ng-add/register-modules/index.ts b/src/framework/theme/schematics/ng-add/register-modules/index.ts new file mode 100644 index 0000000000..e64e2487f5 --- /dev/null +++ b/src/framework/theme/schematics/ng-add/register-modules/index.ts @@ -0,0 +1,112 @@ +/* + * @license + * Copyright Akveo. All Rights Reserved. + * Licensed under the MIT License. See License.txt in the project root for license information. + */ + +import { chain, Rule, SchematicContext, Tree } from '@angular-devkit/schematics'; +import { addModuleImportToRootModule, getProjectMainFile, hasNgModuleImport } from '@angular/cdk/schematics'; +import { WorkspaceProject } from '@angular-devkit/core/src/workspace'; +import { normalize } from '@angular-devkit/core'; +import { bold, red } from '@angular-devkit/core/src/terminal'; + +import { Schema } from '../schema'; +import { getAppModulePath, getProject, isImportedInMainModule } from '../../util'; +import { appRoutingModuleContent } from './app-routing-module-content'; + + +export function registerModules(options: Schema): Rule { + return chain([ + registerAnimationsModule(options), + registerNebularModules(options), + registerRouterIfNeeded(options), + ]); +} + +function registerAnimationsModule(options: Schema) { + return (tree: Tree, context: SchematicContext) => { + const project = getProject(tree, options.project); + const appModulePath = getAppModulePath(tree, getProjectMainFile(project)); + const browserAnimationsModuleName = 'BrowserAnimationsModule'; + const noopAnimationsModuleName = 'NoopAnimationsModule'; + const animationsPackage = '@angular/platform-browser/animations'; + + if (options.animations) { + // In case the project explicitly uses the NoopAnimationsModule, we should print a warning + // message that makes the user aware of the fact that we won't automatically set up + // animations. If we would add the BrowserAnimationsModule while the NoopAnimationsModule + // is already configured, we would cause unexpected behavior and runtime exceptions. + if (hasNgModuleImport(tree, appModulePath, noopAnimationsModuleName)) { + return context.logger.warn(red(`Could not set up "${bold(browserAnimationsModuleName)}" ` + + `because "${bold(noopAnimationsModuleName)}" is already imported. Please manually ` + + `set up browser animations.`)); + } + + addModuleImportToRootModule(tree, browserAnimationsModuleName, animationsPackage, project); + } else if (!hasNgModuleImport(tree, appModulePath, browserAnimationsModuleName)) { + // Do not add the NoopAnimationsModule module if the project already explicitly uses + // the BrowserAnimationsModule. + addModuleImportToRootModule(tree, noopAnimationsModuleName, animationsPackage, project); + } + } +} + +function registerNebularModules(options: Schema): Rule { + return (tree: Tree) => { + const project = getProject(tree, options.project); + const nebularThemeModule = `NbThemeModule.forRoot({ name: '${options.theme}' })`; + + addModuleImportToRootModule(tree, nebularThemeModule, '@nebular/theme', project); + addModuleImportToRootModule(tree, 'NbLayoutModule', '@nebular/theme', project); + } +} + +/** + * Creates `AppRoutingModule` if no either `RouterModule` or `AppRoutingModule` already imported + * in the `AppModule`. + * */ +function registerRouterIfNeeded(options: Schema): Rule { + return (tree: Tree) => { + const project = getProject(tree, options.project); + + if (shouldRegisterRouter(tree, project)) { + registerRoutingModule(tree, options.project); + } + + return tree; + } +} + +/** + * Checks if `RouterModule` or `AppRoutingModule` already imported in the `AppModule`. + * */ +function shouldRegisterRouter(tree: Tree, project: WorkspaceProject): boolean { + const appRoutingModuleAlreadyImported = isImportedInMainModule(tree, project, + 'AppRoutingModule', './app-routing.module'); + const routerModuleAlreadyImported = isImportedInMainModule(tree, project, + 'RouterModule', '@angular/router'); + + return !(appRoutingModuleAlreadyImported || routerModuleAlreadyImported); +} + +function registerRoutingModule(tree: Tree, projectName: string) { + registerAppRoutingModule(tree, projectName); + createAppRoutingModule(tree, projectName); +} + +/** + * We're just adding app-routing.module without any interpolations + * and customization. So, I don't think we have to use schematics + * template files. + * */ +function createAppRoutingModule(tree: Tree, projectName: string) { + const project = getProject(tree, projectName); + const appRoutingModulePath = normalize(`${project.sourceRoot}/app/app-routing.module.ts`); + + tree.create(appRoutingModulePath, appRoutingModuleContent); +} + +function registerAppRoutingModule(tree: Tree, projectName: string) { + const project = getProject(tree, projectName); + addModuleImportToRootModule(tree, 'AppRoutingModule', './app-routing.module', project); +} diff --git a/src/framework/theme/schematics/ng-add/register-theme/index.ts b/src/framework/theme/schematics/ng-add/register-theme/index.ts new file mode 100644 index 0000000000..eea654b550 --- /dev/null +++ b/src/framework/theme/schematics/ng-add/register-theme/index.ts @@ -0,0 +1,107 @@ +/* + * @license + * Copyright Akveo. All Rights Reserved. + * Licensed under the MIT License. See License.txt in the project root for license information. + */ + +import { getProjectFromWorkspace, getProjectStyleFile, getProjectTargetOptions } from '@angular/cdk/schematics'; +import { SchematicsException, Tree } from '@angular-devkit/schematics'; +import { getWorkspace } from '@schematics/angular/utility/config'; +import { WorkspaceProject, WorkspaceSchema } from '@angular-devkit/core/src/workspace'; +import { join, normalize, Path } from '@angular-devkit/core'; + +import { createThemeContent, stylesContent } from './theme-content'; +import { Schema } from '../schema'; +import { getProject, writeJSON } from '../../util'; + + +/** + * Registers customizable scss theme in the specified project. + * It creates `theme.scss` file which manages theme content and it's customization. + * Also as importing `theme.scss` in the styles.scss file and installing the theme globally. + * If the project uses *.css files it'll throw the error. Because we can't use scss themes + * in the css Angular project. + * */ +export function registerCustomizableTheme(options: Schema) { + return (tree: Tree) => { + const project = getProject(tree, options.project); + const stylesPath: string = getProjectStyleFile(project, 'scss') as string; + + if (!tree.exists(stylesPath)) { + throwSCSSRequiredForCustomizableThemes(); + } + + createThemeSCSS(tree, options.theme, project.sourceRoot as string); + insertThemeImportInStyles(tree, stylesPath); + + + return tree; + } +} + +/** + * Registers prebuilt css themes by inserting them in the angular.json styles. + * */ +export function registerPrebuiltTheme(options: Schema) { + return (tree: Tree) => { + const workspace = getWorkspace(tree); + const project = getProjectFromWorkspace(workspace, options.project); + + const themePath = `./node_modules/@nebular/theme/styles/prebuilt/${options.theme}.css`; + + addStyleToTarget(project, 'build', tree, themePath, workspace); + + return tree; + } +} + +/** + * Creates theme.scss with Nebular theme setup. + * */ +function createThemeSCSS(tree: Tree, theme: string, sourceRoot: string) { + const themeContent: string = createThemeContent(theme); + + const customThemePath: Path = normalize(join((sourceRoot as Path), 'themes.scss')); + tree.create(customThemePath, themeContent); +} + +/** + * Updates styles.scss and insert theme.scss import. + * */ +function insertThemeImportInStyles(tree: Tree, stylesPath: string) { + const recorder = tree.beginUpdate(stylesPath) + .insertLeft(0, stylesContent); + + tree.commitUpdate(recorder); +} + +/** + * Adds a style entry to the given project target. + * */ +function addStyleToTarget(project: WorkspaceProject, targetName: string, tree: Tree, + stylesPath: string, workspace: WorkspaceSchema) { + const targetOptions = getProjectTargetOptions(project, targetName); + + if (!targetOptions.styles) { + targetOptions.styles = [stylesPath]; + } else if (noNebularThemeIncluded(targetOptions, stylesPath)) { + targetOptions.styles.unshift(stylesPath); + } + + writeJSON(tree, 'angular.json', workspace); +} + +/** + * Validates no Nebular styles already included into the specified project. + * */ +function noNebularThemeIncluded(targetOptions: any, stylesPath: string): boolean { + const existingStyles = targetOptions.styles.map((s: any) => typeof s === 'string' ? s : s.input); + const hasGivenTheme = existingStyles.find((s: any) => s.includes(stylesPath)); + const hasOtherTheme = existingStyles.find((s: any) => s.includes('@nebular/theme/styles/prebuilt')); + + return !hasGivenTheme && !hasOtherTheme; +} + +function throwSCSSRequiredForCustomizableThemes() { + throw new SchematicsException('No scss root found. Customizable theme requires scss to be enabled in the project.'); +} diff --git a/src/framework/theme/schematics/ng-add/register-theme/theme-content.ts b/src/framework/theme/schematics/ng-add/register-theme/theme-content.ts new file mode 100644 index 0000000000..f32ae8fa81 --- /dev/null +++ b/src/framework/theme/schematics/ng-add/register-theme/theme-content.ts @@ -0,0 +1,26 @@ +/* + * @license + * Copyright Akveo. All Rights Reserved. + * Licensed under the MIT License. See License.txt in the project root for license information. + */ + +export function createThemeContent(themeName: string): string { + return `@import '~@nebular/theme/styles/theming'; +@import '~@nebular/theme/styles/themes/${themeName}'; + +$nb-themes: nb-register-theme(( + // add your variables here like: + // color-bg: #4ca6ff, +), ${themeName}, ${themeName}); +`; +} + +export const stylesContent = `@import 'themes'; + +@import '~@nebular/theme/styles/globals'; + +@include nb-install() { + @include nb-theme-global(); +}; +`; + diff --git a/src/framework/theme/schematics/ng-add/schema.json b/src/framework/theme/schematics/ng-add/schema.json new file mode 100644 index 0000000000..3e9ac5ecc4 --- /dev/null +++ b/src/framework/theme/schematics/ng-add/schema.json @@ -0,0 +1,38 @@ +{ + "$schema": "http://json-schema.org/schema", + "id": "ng-add", + "title": "Nebular Theme ng-add schematics", + "type": "object", + "properties": { + "project": { + "type": "string", + "description": "The name of the project.", + "$default": { + "$source": "projectName" + } + }, + "theme": { + "enum": ["default", "cosmic", "corporate"], + "description": "The theme which will be installed.", + "default": "default", + "x-prompt": "Which Nebular theme do you want to use:" + }, + "customization": { + "type": "boolean", + "description": "Use prebuilt css styles or import customizable scss.", + "default": true, + "x-prompt": "Use customizable scss themes?" + }, + "layout": { + "type": "boolean", + "description": "Whether root component has to be wrapped in nb-layout or not.", + "default": true + }, + "animations": { + "type": "boolean", + "default": true, + "description": "Whether Angular browser animations should be set up or not.", + "x-prompt": "Set up browser animations for Nebular?" + } + } +} diff --git a/src/framework/theme/schematics/ng-add/schema.ts b/src/framework/theme/schematics/ng-add/schema.ts new file mode 100644 index 0000000000..93ec51f066 --- /dev/null +++ b/src/framework/theme/schematics/ng-add/schema.ts @@ -0,0 +1,13 @@ +/* + * @license + * Copyright Akveo. All Rights Reserved. + * Licensed under the MIT License. See License.txt in the project root for license information. + */ + +export interface Schema { + project: string; + theme: 'cosmic' | 'default' | 'corporate'; + customization: boolean; + layout: boolean; + animations: boolean; +} diff --git a/src/framework/theme/schematics/ng-add/setup.ts b/src/framework/theme/schematics/ng-add/setup.ts new file mode 100644 index 0000000000..831bef3cf0 --- /dev/null +++ b/src/framework/theme/schematics/ng-add/setup.ts @@ -0,0 +1,25 @@ +/* + * @license + * Copyright Akveo. All Rights Reserved. + * Licensed under the MIT License. See License.txt in the project root for license information. + */ + +import { chain, noop } from '@angular-devkit/schematics'; + +import { Schema } from './schema'; +import { registerModules } from './register-modules'; +import { registerCustomizableTheme, registerPrebuiltTheme } from './register-theme'; +import { wrapRootComponentInLayout } from './wrap-in-layout'; + + +/** + * Setting up Nebular for the specified project by registering required modules, + * adding Nebular themes and wrapping root component in the Nebular Layout. + * */ +export default function (options: Schema) { + return chain([ + registerModules(options), + options.customization ? registerCustomizableTheme(options) : registerPrebuiltTheme(options), + options.layout ? wrapRootComponentInLayout(options) : noop(), + ]); +} diff --git a/src/framework/theme/schematics/ng-add/wrap-in-layout/index.ts b/src/framework/theme/schematics/ng-add/wrap-in-layout/index.ts new file mode 100644 index 0000000000..0c18127741 --- /dev/null +++ b/src/framework/theme/schematics/ng-add/wrap-in-layout/index.ts @@ -0,0 +1,53 @@ +/* + * @license + * Copyright Akveo. All Rights Reserved. + * Licensed under the MIT License. See License.txt in the project root for license information. + */ + +import { Rule, Tree } from '@angular-devkit/schematics'; +import { dirname, join, normalize } from '@angular-devkit/core'; +import * as ts from 'typescript'; + +import { getAppComponentPath, getComponentTemplateDescriptor, TemplateDescriptor, writeText } from '../../util'; +import { wrapHtmlFileTemplateInLayout, wrapInlineTemplateInLayout } from './layout-content'; +import { Schema } from '../schema'; + +/** + * Wraps `AppComponent` in `NbLayoutComponent`. It's required for correct + * work of Nebular components. + * */ +export function wrapRootComponentInLayout(options: Schema): Rule { + return (tree: Tree) => { + const componentPath: string = getAppComponentPath(tree, options.project); + const templateDescriptor: TemplateDescriptor = getComponentTemplateDescriptor(tree, componentPath); + + if (templateDescriptor.isInline()) { + wrapInlineTemplate(tree, templateDescriptor); + } else { + wrapHtmlFileTemplate(tree, templateDescriptor); + } + + return tree; + } +} + +function wrapInlineTemplate(tree: Tree, templateDescriptor: TemplateDescriptor) { + const { templateProp, componentPath, template } = templateDescriptor; + + const wrappedTemplate = wrapInlineTemplateInLayout(template); + const recorder = tree.beginUpdate(componentPath) + .remove(templateProp.initializer.pos, template.length) + .insertLeft(templateProp.initializer.pos, wrappedTemplate); + + tree.commitUpdate(recorder); +} + +function wrapHtmlFileTemplate(tree: Tree, templateDescriptor: TemplateDescriptor) { + const { templateUrlProp, componentPath, template } = templateDescriptor; + + const templateUrl = (templateUrlProp.initializer as ts.StringLiteral).text; + const dir = dirname(normalize(componentPath)); + const templatePath = join(dir, templateUrl); + + writeText(tree, templatePath, wrapHtmlFileTemplateInLayout(template)); +} diff --git a/src/framework/theme/schematics/ng-add/wrap-in-layout/layout-content.ts b/src/framework/theme/schematics/ng-add/wrap-in-layout/layout-content.ts new file mode 100644 index 0000000000..a9d867f33c --- /dev/null +++ b/src/framework/theme/schematics/ng-add/wrap-in-layout/layout-content.ts @@ -0,0 +1,47 @@ +/* + * @license + * Copyright Akveo. All Rights Reserved. + * Licensed under the MIT License. See License.txt in the project root for license information. + */ + +const layoutStart = ` + + + + + + +`; + +const layoutEnd = ` + + + + + +`; + +export function wrapInlineTemplateInLayout(template: string): string { + return ` \` + ${layoutStart} +${padd(template, 8)} + ${layoutEnd} +`; +} + +export function wrapHtmlFileTemplateInLayout(template: string): string { + return `${layoutStart} +${padd(template, 4)} +${layoutEnd} +`; +} + +/** + * Adds padding to the each line of the multyline string. + * */ +function padd(text: string, paddLen: number): string { + return text + .split('\n') + .map(line => `${' '.repeat(paddLen)}${line}`) + .join('\n'); +} diff --git a/src/framework/theme/schematics/util/ast.ts b/src/framework/theme/schematics/util/ast.ts new file mode 100644 index 0000000000..da454679b2 --- /dev/null +++ b/src/framework/theme/schematics/util/ast.ts @@ -0,0 +1,29 @@ +/* + * @license + * Copyright Akveo. All Rights Reserved. + * Licensed under the MIT License. See License.txt in the project root for license information. + */ + +import * as ts from 'typescript'; +import { Tree } from '@angular-devkit/schematics'; +import { dirname, normalize, Path } from '@angular-devkit/core'; +import { getProjectMainFile, getSourceFile } from '@angular/cdk/schematics'; +import { WorkspaceProject } from '@angular-devkit/core/src/workspace'; +import { isImported } from '@schematics/angular/utility/ast-utils'; +import { findBootstrapModulePath } from '@schematics/angular/utility/ng-ast-utils'; + +export function isImportedInMainModule(tree: Tree, project: WorkspaceProject, moduleName: string, + importPath: string): boolean { + const appModulePath = getAppModulePath(tree, getProjectMainFile(project)); + const appModuleSource = getSourceFile(tree, appModulePath) as ts.SourceFile; + + return isImported(appModuleSource, moduleName, importPath); +} + + +export function getAppModulePath(host: Tree, mainPath: string): string { + const moduleRelativePath = findBootstrapModulePath(host, mainPath); + const mainDir = dirname(mainPath as Path); + + return normalize(`/${mainDir}/${moduleRelativePath}.ts`); +} diff --git a/src/framework/theme/schematics/util/component.ts b/src/framework/theme/schematics/util/component.ts new file mode 100644 index 0000000000..1c228cc13e --- /dev/null +++ b/src/framework/theme/schematics/util/component.ts @@ -0,0 +1,92 @@ +/* + * @license + * Copyright Akveo. All Rights Reserved. + * Licensed under the MIT License. See License.txt in the project root for license information. + */ + +import { getDecoratorMetadata } from '@schematics/angular/utility/ast-utils'; +import { getSourceFile } from '@angular/cdk/schematics'; +import { Tree } from '@angular-devkit/schematics'; +import { dirname, join, normalize } from '@angular-devkit/core'; +import * as ts from 'typescript'; + +import { getProject } from './project'; + + +export class TemplateDescriptor { + constructor(public templateProp: ts.PropertyAssignment, + public templateUrlProp: ts.PropertyAssignment, + public componentPath: string, + public template: string) { + } + + isInline(): boolean { + return !!this.templateProp; + } +} + +export function getComponentTemplateDescriptor(host: Tree, componentPath: string): TemplateDescriptor { + const compSource: ts.SourceFile = getSourceFile(host, componentPath); + const compMetadata: ts.Node = getDecoratorMetadata(compSource, 'Component', '@angular/core')[0]; + const templateProp = getMetadataProperty(compMetadata, 'template'); + const templateUrlProp = getMetadataProperty(compMetadata, 'templateUrl'); + + const template: string = getComponentTemplate(host, componentPath, { + templateProp, + templateUrlProp, + }); + + return new TemplateDescriptor( + templateProp, + templateUrlProp, + componentPath, + template, + ); +} + +export function getAppComponentPath(tree: Tree, projectName: string): string { + const project = getProject(tree, projectName); + return normalize(`${project.sourceRoot}/app/app.component.ts`); +} + +interface TemplateInfo { + templateProp?: ts.PropertyAssignment, + templateUrlProp?: ts.PropertyAssignment, +} + +function getComponentTemplate(host: Tree, compPath: string, tmplInfo: TemplateInfo): string { + let template = ''; + + if (tmplInfo.templateProp) { + template = (tmplInfo.templateProp.initializer as ts.StringLiteral).text; + } else if (tmplInfo.templateUrlProp) { + const templateUrl = (tmplInfo.templateUrlProp.initializer as ts.StringLiteral).text; + const dir = dirname(normalize(compPath)); + const templatePath = join(dir, templateUrl); + const buffer = host.read(templatePath); + if (buffer) { + template = buffer.toString(); + } + } + + return template; +} + +function getMetadataProperty(metadata: ts.Node, propertyName: string): ts.PropertyAssignment { + const properties = (metadata as ts.ObjectLiteralExpression).properties; + const property = properties + .filter(prop => prop.kind === ts.SyntaxKind.PropertyAssignment) + .filter((prop: ts.PropertyAssignment) => { + const name = prop.name; + switch (name.kind) { + case ts.SyntaxKind.Identifier: + return (name as ts.Identifier).getText() === propertyName; + case ts.SyntaxKind.StringLiteral: + return (name as ts.StringLiteral).text === propertyName; + } + + return false; + })[0]; + + return property as ts.PropertyAssignment; +} diff --git a/src/framework/theme/schematics/util/file.ts b/src/framework/theme/schematics/util/file.ts new file mode 100644 index 0000000000..9eaee429f1 --- /dev/null +++ b/src/framework/theme/schematics/util/file.ts @@ -0,0 +1,46 @@ +/* + * @license + * Copyright Akveo. All Rights Reserved. + * Licensed under the MIT License. See License.txt in the project root for license information. + */ + +import { Tree } from '@angular-devkit/schematics'; + +function throwFileNotFoundError(fileName: string) { + throw new Error(`${fileName} file not found in the tree.`); +} + +/** + * Reads specified file from the given tree + * Throws the exception if file not found + * */ +export function readText(tree: Tree, fileName: string, encoding: string = 'utf8'): string { + const file: Buffer | null = tree.read(fileName); + + if (!file) { + throwFileNotFoundError(fileName); + } + + return (file as Buffer).toString(encoding); +} + +/** + * Reads specified file as JSON from the given tree + * */ +export function readJSON(tree: Tree, fileName: string, encoding: string = 'utf8'): any { + return JSON.parse(readText(tree, fileName, encoding)); +} + +/** + * Writes specified files to the given tree + * */ +export function writeText(tree: Tree, fileName: string, content: string) { + tree.overwrite(fileName, content) +} + +/** + * Writes specified JSON to the given tree + * */ +export function writeJSON(tree: Tree, fileName: string, content: any) { + writeText(tree, fileName, JSON.stringify(content, null, 2)); +} diff --git a/src/framework/theme/schematics/util/index.ts b/src/framework/theme/schematics/util/index.ts new file mode 100644 index 0000000000..b5dbcbc90e --- /dev/null +++ b/src/framework/theme/schematics/util/index.ts @@ -0,0 +1,11 @@ +/* + * @license + * Copyright Akveo. All Rights Reserved. + * Licensed under the MIT License. See License.txt in the project root for license information. + */ + +export * from './package'; +export * from './file'; +export * from './project'; +export * from './component'; +export * from './ast'; diff --git a/src/framework/theme/schematics/util/package.ts b/src/framework/theme/schematics/util/package.ts new file mode 100644 index 0000000000..52467e26e8 --- /dev/null +++ b/src/framework/theme/schematics/util/package.ts @@ -0,0 +1,137 @@ +/* + * @license + * Copyright Akveo. All Rights Reserved. + * Licensed under the MIT License. See License.txt in the project root for license information. + */ + +import { Tree } from '@angular-devkit/schematics'; +import { readJSON, writeJSON } from './file'; + +const packageJsonName = 'package.json'; + +interface PackageJson { + version: string; + dependencies: { [key: string]: string }, + devDependencies: { [key: string]: string }, + peerDependencies: { [key: string]: string }, +} + +export function getNebularVersion(): string { + return getNebularPackageJson().version; +} + +/** + * Gets the version of the specified Nebular peerDependency + * */ +export function getNebularPeerDependencyVersionFromPackageJson(packageName: string): string { + const packageJson: PackageJson = getNebularPackageJson(); + + if (noInfoAboutPeerDependency(packageJson, packageName)) { + throwNoPackageInfoInPackageJson(packageName); + } + + return packageJson.peerDependencies[packageName]; +} + +/** + * Gets the version of the specified dependency by looking at the package.json in the specified tree + * */ +export function getDependencyVersionFromPackageJson(tree: Tree, packageName: string): string { + if (!tree.exists(packageJsonName)) { + throwNoPackageJsonError(); + } + + const packageJson: PackageJson = readJSON(tree, packageJsonName); + + if (noInfoAboutDependency(packageJson, packageName)) { + throwNoPackageInfoInPackageJson(packageName); + } + + return packageJson.dependencies[packageName]; +} + +export function addDependencyToPackageJson(tree: Tree, packageName: string, packageVersion: string) { + if (!tree.exists(packageJsonName)) { + throwNoPackageJsonError(); + } + + const packageJson: PackageJson = readJSON(tree, packageJsonName); + + if (!packageJson.dependencies) { + packageJson.dependencies = {}; + } + + if (!packageJson.dependencies[packageName]) { + packageJson.dependencies[packageName] = packageVersion; + packageJson.dependencies = sortObjectByKeys(packageJson.dependencies); + } + + writeJSON(tree, packageJsonName, packageJson); +} + +export function addDevDependencyToPackageJson(tree: Tree, packageName: string, packageVersion: string) { + if (!tree.exists(packageJsonName)) { + throwNoPackageJsonError(); + } + + const packageJson: PackageJson = readJSON(tree, packageJsonName); + + if (!packageJson.devDependencies) { + packageJson.devDependencies = {}; + } + + if (!packageJson.devDependencies[packageName]) { + packageJson.devDependencies[packageName] = packageVersion; + packageJson.devDependencies = sortObjectByKeys(packageJson.devDependencies); + } + + writeJSON(tree, packageJsonName, packageJson); +} + +function throwNoPackageJsonError() { + throw new Error('No package.json found in the tree.'); +} + +function throwNoPackageInfoInPackageJson(packageName: string) { + throw new Error(`No info found in package.json for ${packageName}`); +} + +/** + * Validates packageJson has dependencies, also as specified dependency not exists. + * */ +function noInfoAboutDependency(packageJson: PackageJson, packageName: string): boolean { + return !dependencyAlreadyExists(packageJson, packageName); +} + +/** + * Validates packageJson has peerDependencies, also as specified peerDependency not exists. + * */ +function noInfoAboutPeerDependency(packageJson: PackageJson, packageName: string): boolean { + return !peerDependencyAlreadyExists(packageJson, packageName); +} + +/** + * Validates packageJson has dependencies, also as specified dependency exists. + * */ +function dependencyAlreadyExists(packageJson: PackageJson, packageName: string): boolean { + return !!(packageJson.dependencies && packageJson.dependencies[packageName]); +} + +/** + * Validates packageJson has peerDependencies, also as specified peerDependency exists. + * */ +function peerDependencyAlreadyExists(packageJson: PackageJson, packageName: string): boolean { + return !!(packageJson.peerDependencies && packageJson.peerDependencies[packageName]); +} + +/** + * 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, {}); +} + +function getNebularPackageJson(): PackageJson { + return require('../../package.json'); +} diff --git a/src/framework/theme/schematics/util/project.ts b/src/framework/theme/schematics/util/project.ts new file mode 100644 index 0000000000..539cc92f44 --- /dev/null +++ b/src/framework/theme/schematics/util/project.ts @@ -0,0 +1,18 @@ +/* + * @license + * Copyright Akveo. All Rights Reserved. + * Licensed under the MIT License. See License.txt in the project root for license information. + */ + +import { Tree } from '@angular-devkit/schematics'; +import { WorkspaceProject } from '@angular-devkit/core/src/workspace'; +import { getWorkspace } from '@schematics/angular/utility/config'; +import { getProjectFromWorkspace } from '@angular/cdk/schematics'; + +/** + * Gets project workspace from the specified tree by given project name + * */ +export function getProject(tree: Tree, projectName: string): WorkspaceProject { + const workspace = getWorkspace(tree); + return getProjectFromWorkspace(workspace, projectName); +} diff --git a/tsconfig.publish.json b/tsconfig.publish.json index ecb73c33e6..d717d17ee7 100644 --- a/tsconfig.publish.json +++ b/tsconfig.publish.json @@ -30,7 +30,8 @@ ], "exclude": [ "./.ng_build/**/*.e2e.ts", - "./.ng_build/**/*.spec.ts" + "./.ng_build/**/*.spec.ts", + "./.ng_build/**/schematics/*" ], "angularCompilerOptions": { "skipTemplateCodegen": true, diff --git a/tsconfig.schematics.json b/tsconfig.schematics.json new file mode 100644 index 0000000000..c35958ab7f --- /dev/null +++ b/tsconfig.schematics.json @@ -0,0 +1,34 @@ +{ + "compilerOptions": { + "baseUrl": ".", + "lib": [ + "es2017", + "dom" + ], + "declaration": true, + "module": "commonjs", + "moduleResolution": "node", + "outDir": "./src/.lib", + "rootDir": "./src/framework", + "noEmitOnError": true, + "noFallthroughCasesInSwitch": true, + "noImplicitAny": false, + "noImplicitThis": false, + "noUnusedParameters": false, + "noUnusedLocals": false, + "skipDefaultLibCheck": true, + "skipLibCheck": true, + "sourceMap": false, + "strictNullChecks": false, + "target": "es5", + "typeRoots": [ + "node_modules/@types" + ] + }, + "include": [ + "./src/framework/**/schematics/**/*" + ], + "exclude": [ + "./src/framework/**/schematics/**/files/**/*" + ] +} diff --git a/tsconfig.schematics.publish.json b/tsconfig.schematics.publish.json new file mode 100644 index 0000000000..8abc9a6745 --- /dev/null +++ b/tsconfig.schematics.publish.json @@ -0,0 +1,34 @@ +{ + "compilerOptions": { + "baseUrl": ".", + "lib": [ + "es2017", + "dom" + ], + "declaration": true, + "module": "commonjs", + "moduleResolution": "node", + "outDir": "./src/.lib", + "rootDir": "./src/framework", + "noEmitOnError": true, + "noFallthroughCasesInSwitch": true, + "noImplicitAny": false, + "noImplicitThis": false, + "noUnusedParameters": true, + "noUnusedLocals": true, + "skipDefaultLibCheck": true, + "skipLibCheck": true, + "sourceMap": true, + "strictNullChecks": true, + "target": "es5", + "typeRoots": [ + "node_modules/@types" + ] + }, + "include": [ + "./src/framework/**/schematics/**/*" + ], + "exclude": [ + "./src/framework/**/schematics/**/files/**/*" + ] +}