Skip to content

Commit

Permalink
feat(ngrid): add schematics support
Browse files Browse the repository at this point in the history
  • Loading branch information
shlomiassaf committed Dec 30, 2020
1 parent 1e9af34 commit 1d7814c
Show file tree
Hide file tree
Showing 47 changed files with 1,385 additions and 135 deletions.
11 changes: 11 additions & 0 deletions angular.json
Original file line number Diff line number Diff line change
Expand Up @@ -364,6 +364,14 @@
"output": "dist/@pebula/ngrid/themes"
}
]
},
"schematicsCompile": {
"libPath": "./schematics",
"compilerOptions": {
"sourceMap": false,
"inlineSourceMap": true,
"inlineSources": true
}
}
}
}
Expand Down Expand Up @@ -538,6 +546,9 @@
"inlineSourceMap": true,
"inlineSources": true
}
},
"writePackageJson": {

}
}
}
Expand Down
2 changes: 1 addition & 1 deletion libs/ngrid/jest.config.js
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
module.exports = {
preset: '../../jest.preset.js',
preset: './jest.preset.js',
coverageDirectory: '../../coverage/libs/ngrid',
snapshotSerializers: [
'jest-preset-angular/build/AngularNoNgAttributesSnapshotSerializer.js',
Expand Down
5 changes: 5 additions & 0 deletions libs/ngrid/jest.preset.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
const basePreset = require('../../jest.preset.js');
module.exports = {
...basePreset,
testMatch: ['**/+(*.)+(spec|test)\.jest.+(ts|js)?(x)']
};
1 change: 1 addition & 0 deletions libs/ngrid/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@
"grid",
"table"
],
"schematics": "./schematics/collection.json",
"author": "Shlomi Assaf",
"homepage": "https://github.com/shlomiassaf/ngrid",
"bugs": {
Expand Down
23 changes: 23 additions & 0 deletions libs/ngrid/schematics/collection.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
{
"$schema": "../../../node_modules/@angular-devkit/schematics/collection-schema.json",
"schematics": {
"ng-add": {
"description": "Adds ngrid library to the project and installs dependencies",
"factory": "./ng-add/index",
"schema": "./ng-add/schema.json",
"aliases": ["install"]
},
"ng-add-setup-project": {
"private": true,
"description": "Sets up the project after dependencies have been installed",
"factory": "./ng-add/setup-project",
"schema": "./ng-add/schema.json"
},
"grid": {
"description": "Create a component that uses ngrid",
"factory": "./ng-generate/grid/index",
"schema": "./ng-generate/grid/schema.json",
"aliases": ["ngrid"]
}
}
}
58 changes: 58 additions & 0 deletions libs/ngrid/schematics/ng-add/index.spec.jest.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,58 @@
import { Tree } from '@angular-devkit/schematics';
import { SchematicTestRunner } from '@angular-devkit/schematics/testing';

import { createTestApp, getFileContent } from '../utils/testing';
import * as messages from './messages';


describe(`ng add '@pebula/ngrid'`, () => {
let runner: SchematicTestRunner;
let appTree: Tree;

beforeEach(async() => {
runner = new SchematicTestRunner('schematics', require.resolve('../collection.json'));
appTree = await createTestApp(runner);
});

it(`should add missing dependencies to 'package.json'`, async() => {
const tree = await runner.runSchematicAsync('ng-add', {}, appTree).toPromise();
const {dependencies} = JSON.parse(getFileContent(tree, '/package.json'));

expect(dependencies['@pebula/ngrid']).toBeDefined();
expect(dependencies['@angular/cdk']).toBeDefined();
expect(dependencies['@angular/material']).not.toBeDefined();
expect(dependencies['@ng-bootstrap/ng-bootstrap']).not.toBeDefined();
});

it(`should add missing dependencies to 'package.json' with uiPlugin material`, async() => {
const tree = await runner.runSchematicAsync('ng-add', { uiPlugin: 'material' }, appTree).toPromise();
const {dependencies} = JSON.parse(getFileContent(tree, '/package.json'));

expect(dependencies['@pebula/ngrid']).toBeDefined();
expect(dependencies['@angular/cdk']).toBeDefined();
expect(dependencies['@angular/material']).toBeDefined();
expect(dependencies['@ng-bootstrap/ng-bootstrap']).not.toBeDefined();
});


it(`should add missing dependencies to 'package.json' with uiPlugin bootstrap`, async() => {
const tree = await runner.runSchematicAsync('ng-add', { uiPlugin: 'bootstrap' }, appTree).toPromise();
const {dependencies} = JSON.parse(getFileContent(tree, '/package.json'));

expect(dependencies['@pebula/ngrid']).toBeDefined();
expect(dependencies['@angular/cdk']).toBeDefined();
expect(dependencies['@angular/material']).not.toBeDefined();
expect(dependencies['@ng-bootstrap/ng-bootstrap']).toBeDefined();
});

it(`should report when specified 'project' is not found`, async() => {
let message = '';
try {
await runner.runSchematicAsync('ng-add', {project: 'test'}, appTree).toPromise();
} catch (e) {
message = e.message;
} finally {
expect(message).toBe(messages.noProject('test'));
}
});
});
71 changes: 71 additions & 0 deletions libs/ngrid/schematics/ng-add/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,71 @@
import { Rule, SchematicContext, SchematicsException, Tree, externalSchematic } from '@angular-devkit/schematics';
import { NodePackageInstallTask, RunSchematicTask } from '@angular-devkit/schematics/tasks';
import { getWorkspace } from '@schematics/angular/utility/workspace';
import { ProjectType } from '@schematics/angular/utility/workspace-models';

import * as meta from './metadata';
import { Schema, SetupSchema } from './schema';
import * as messages from './messages';
import { addPackageToPackageJson, getPackageVersionFromPackageJson } from '../utils/package-config';

function getNgridPackageName(packageName: string) {
return `@pebula/${packageName}`;
}

export default function ngAdd(options: Schema): Rule {
return async(tree: Tree, context: SchematicContext) => {
const workspace = await getWorkspace(tree);
const project = options.project || workspace.extensions.defaultProject as string;
const uiPlugin = options.uiPlugin || 'none';
const theme = options.theme || 'light';

const projectWorkspace = workspace.projects.get(project);

if (!projectWorkspace) {
throw new SchematicsException(messages.noProject(project));
}

const setupSchema: SetupSchema = {
options: { project, uiPlugin, theme },
withRules: [],
workspace: workspace as any,
project: projectWorkspace as any,
};

// Installing dependencies
const angularCoreVersion = getPackageVersionFromPackageJson(tree, '@angular/core') !;
const angularCdkVersion = getPackageVersionFromPackageJson(tree, '@angular/cdk');

if (angularCdkVersion === null) {
addPackageToPackageJson(tree, '@angular/cdk', angularCoreVersion);
setupSchema.withRules.push(() => externalSchematic('@angular/cdk', 'ng-add', project ? { name: project } : {}));
}

addPackageToPackageJson(tree, getNgridPackageName('ngrid'), `^${meta.NGRID_VERSION}`);

switch (uiPlugin) {
case 'bootstrap':
const ngBootstrapVersion = getPackageVersionFromPackageJson(tree, '@ng-bootstrap/ng-bootstrap');
if (ngBootstrapVersion === null) {
addPackageToPackageJson(tree, '@ng-bootstrap/ng-bootstrap', meta.NG_BOOTSTRAP_VERSION);
setupSchema.withRules.push(() => externalSchematic('@ng-bootstrap/ng-bootstrap', 'ng-add', project ? { project } : {}));
}
addPackageToPackageJson(tree, getNgridPackageName('ngrid-bootstrap'), `^${meta.NGRID_VERSION}`);
break;
case 'material':
const ngMaterialVersion = getPackageVersionFromPackageJson(tree, '@angular/material');
if (ngMaterialVersion === null) {
addPackageToPackageJson(tree, '@angular/material', meta.NG_MATERIAL_VERSION);
setupSchema.withRules.push(() => externalSchematic('@angular/material', 'ng-add', project ? { name: project } : {}));
}
addPackageToPackageJson(tree, getNgridPackageName('ngrid-material'), `^${meta.NGRID_VERSION}`);
break;
}

const installTaskId = context.addTask(new NodePackageInstallTask());

if (projectWorkspace.extensions.projectType === ProjectType.Application) {
context.addTask(new RunSchematicTask('ng-add-setup-project', setupSchema), [ installTaskId ]);
}
};
}
7 changes: 7 additions & 0 deletions libs/ngrid/schematics/ng-add/messages.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
export function noProject(project: string) {
return `Unable to find project '${project}' in the workspace`;
}

export function unsupportedStyles(styleFilePath: string) {
return `Project style file found has unsupported extension: '${styleFilePath}'\nAdding 'bootstrap.min.css' to 'angular.json'`;
}
3 changes: 3 additions & 0 deletions libs/ngrid/schematics/ng-add/metadata.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
export const NGRID_VERSION = '3.0.0-alpha.6';
export const NG_MATERIAL_VERSION = '11.0.0';
export const NG_BOOTSTRAP_VERSION = '8.0.0';
44 changes: 44 additions & 0 deletions libs/ngrid/schematics/ng-add/schema.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@

{
"$schema": "http://json-schema.org/schema",
"id": "ngrid-ng-add",
"title": "ngrid ng-add schematic",
"type": "object",
"properties": {
"project": {
"type": "string",
"description": "Name of the project",
"$default": {
"$source": "projectName"
}
},
"uiPlugin": {
"description": "UI Plugin to add",
"type": "string",
"default": "none",
"x-prompt": {
"message": "Choose",
"type": "list",
"items": [
{ "value": "none", "label": "None" },
{ "value": "material", "label": "Material (@pebula/ngrid-material using @angular/components" },
{ "value": "bootstrap", "label": "Bootstrap (@pebula/ngrid-bootstrap using @ng-bootstrap/ng-bootstrap" }
]
}
},
"theme": {
"description": "The theme to apply",
"type": "string",
"default": "light",
"x-prompt": {
"message": "Choose a prebuilt theme name, or \"custom\" for a custom theme:",
"type": "list",
"items": [
{ "value": "light", "label": "Light Theme" },
{ "value": "dark", "label": "Dark Theme" },
{ "value": "custom", "label": "Custom" }
]
}
},
}
}
21 changes: 21 additions & 0 deletions libs/ngrid/schematics/ng-add/schema.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
import { workspaces } from '@angular-devkit/core';
import { Rule } from '@angular-devkit/schematics';

export interface Schema {
/**
* Name of the project where ng-bootstrap library should be installed
*/
project?: string;

uiPlugin?: 'none' | 'material' | 'bootstrap';

theme?: 'light' | 'dark' | 'custom';

}

export interface SetupSchema {
options: Required<Schema>;
withRules: Array<() => Rule>;
workspace: workspaces.WorkspaceDefinition;
project: workspaces.ProjectDefinition;
}
76 changes: 76 additions & 0 deletions libs/ngrid/schematics/ng-add/setup-project.spec.1jest.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,76 @@
import {SchematicTestRunner, UnitTestTree} from '@angular-devkit/schematics/testing';
import {getWorkspace} from '@schematics/angular/utility/workspace';
import {workspaces} from '@angular-devkit/core';
import {Schema} from './schema';
import * as messages from './messages';
import {createTestApp} from '../utils/testing';

['app', 'second-app'].forEach(projectName => {
describe(`ng-add-project-setup, 'project=${projectName}'`, () => {

let runner: SchematicTestRunner;
let log: string[] = [];

async function createAppWithOptions(appOptions = {}):
Promise<{ tree: UnitTestTree, project: workspaces.ProjectDefinition }> {
// 'app' is the default application, so we're not passing '--project' option
const options: Schema = {project: projectName};
let tree = await createTestApp(runner, appOptions);
tree = await runner.runSchematicAsync('ng-add-setup-project', options, tree).toPromise();
const workspace = await getWorkspace(tree);
const project = workspace.projects.get(projectName);
return {tree, project: project as any};
}

beforeEach(() => {
log = [];
runner = new SchematicTestRunner('schematics', require.resolve('../collection.json'));
runner.logger.subscribe(({message}) => log.push(message));
});

it(`should add '@angular/localize' polyfill`, async() => {
let tree = await createTestApp(runner);
const polyfillFilePath = `projects/${projectName}/src/polyfills.ts`;

expect(tree.read(polyfillFilePath) !.toString()).not.toContain('@angular/localize');

tree = await runner.runSchematicAsync('ng-add-setup-project', projectName ? {project: projectName} : {}, tree)
.toPromise();
expect(tree.read(polyfillFilePath) !.toString()).toContain('@angular/localize');
});

it(`should add 'bootstrap.min.css' to 'angular.json' by default`, async() => {
const {project} = await createAppWithOptions();
const targetOptions = project.targets.get('build') !.options;

expect(targetOptions.styles).toContain('node_modules/bootstrap/dist/css/bootstrap.min.css');
});

it(`should patch 'style.sass' when using SASS styles`, async() => {
const {tree} = await createAppWithOptions({style: 'sass'});

const expectedStylesPath = `projects/${projectName}/src/styles.sass`;
const stylesFile = tree.read(expectedStylesPath) !.toString();

expect(stylesFile).toContain(`@import '~bootstrap/scss/bootstrap'`);
expect(stylesFile).not.toContain(`@import '~bootstrap/scss/bootstrap;'`);
});

it(`should patch 'style.scss' when using SCSS styles`, async() => {
const {tree} = await createAppWithOptions({style: 'scss'});

const expectedStylesPath = `projects/${projectName}/src/styles.scss`;
const stylesFile = tree.read(expectedStylesPath) !.toString();

expect(stylesFile).toContain(`@import '~bootstrap/scss/bootstrap';`);
});

it(`should add 'bootstrap.min.css' to 'angular.json' if style system is unsupported`, async() => {
const {project} = await createAppWithOptions({style: 'less'});
const targetOptions = project.targets.get('build') !.options;

expect(targetOptions.styles).toContain('node_modules/bootstrap/dist/css/bootstrap.min.css');
expect(log).toEqual([messages.unsupportedStyles(`projects/${projectName}/src/styles.less`)]);
});
});
});
12 changes: 12 additions & 0 deletions libs/ngrid/schematics/ng-add/setup-project.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
import {chain, Rule} from '@angular-devkit/schematics';

import { SetupSchema } from './schema';
import { addThemeToAppStyles } from './theming/theming';

export default function ngAddSetupProject(options: SetupSchema): Rule {

return chain([
...(options.withRules.map( factory => factory() )),
addThemeToAppStyles(options),
]);
}
23 changes: 23 additions & 0 deletions libs/ngrid/schematics/ng-add/theming/create-custom-theme.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
/**
* @license
* Copyright Google LLC All Rights Reserved.
*
* Use of this source code is governed by an MIT-style license that can be
* found in the LICENSE file at https://angular.io/license
*/

/** Create custom theme for the given application configuration. */
export function createCustomTheme(name: string = 'app') {
return `
@import '~@pebula/ngrid/theming';
$${name}-palette: pbl-palette($pbl-blue);
$${name}-theme: pbl-light-theme($${name}-palette);
@include pbl-ngrid-typography();
@include pbl-ngrid-theme($${name}-theme);
`;
}
Loading

0 comments on commit 1d7814c

Please sign in to comment.