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/**/*"
+ ]
+}