feat(nest): implement nest lib schematic (#2540)

This commit is contained in:
Jay Bell 2020-03-02 08:20:45 -08:00 committed by GitHub
parent ff6b434548
commit a39587a350
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
32 changed files with 1421 additions and 16 deletions

View File

@ -20,6 +20,7 @@ module.exports = {
{ name: 'core', description: 'anything Nx core specific' },
{ name: 'docs', description: 'anything related to docs infrastructure' },
{ name: 'nextjs', description: 'anything Next specific' },
{ name: 'nest', description: 'anything Nest specific' },
{ name: 'node', description: 'anything Node specific' },
{ name: 'nx-plugin', description: 'anything Nx Plugin specific' },
{ name: 'react', description: 'anything React specific' },

View File

@ -181,6 +181,7 @@ The scope must be one of the following:
- core - anything Nx core specific
- docs - anything related to docs infrastructure
- nextjs - anything Next specific
- nest - anything Nest specific
- node - anything Node specific
- linter - anything Linter specific
- react - anything React specific

View File

@ -0,0 +1,145 @@
# library
Create a new nest library
## Usage
```bash
ng generate library ...
```
```bash
ng g lib ... # same
```
By default, Nx will search for `library` in the default collection provisioned in `angular.json`.
You can specify the collection explicitly as follows:
```bash
ng g @nrwl/nest:library ...
```
Show what will be generated without writing to disk:
```bash
ng g library ... --dry-run
```
### Examples
Generate libs/myapp/mylib:
```bash
ng g lib mylib --directory=myapp
```
## Options
### controller
Default: `false`
Type: `boolean`
Include a controller with the library
### directory
Alias(es): d
Type: `string`
A directory where the app is placed
### global
Default: `false`
Type: `boolean`
Add the Global decorator to the generated module.
### linter
Default: `tslint`
Type: `string`
Possible values: `eslint`, `tslint`
The tool to use for running lint checks.
### name
Type: `string`
Library name
### publishable
Type: `boolean`
Create a publishable library. A "build" architect will be added for this project the workspace configuration.
### service
Default: `false`
Type: `boolean`
Include a service with the library.
### skipFormat
Default: `false`
Type: `boolean`
Skip formatting files
### skipTsConfig
Default: `false`
Type: `boolean`
Do not update tsconfig.json for development experience.
### tags
Alias(es): t
Type: `string`
Add tags to the library (used for linting)
### target
Default: `es6`
Type: `string`
Possible values: `es5`, `es6`, `esnext`, `es2015`, `es2016`, `es2017`, `es2018`, `es2019`, `es2020`
The es target, Nest suggest using es6 or higher.
### testEnvironment
Default: `node`
Type: `string`
Possible values: `jsdom`, `node`
The test environment for jest, for node applications this should stay as node unless doing DOM testing.
### unitTestRunner
Default: `jest`
Type: `string`
Possible values: `jest`, `none`
Test runner to use for unit tests

View File

@ -90,6 +90,16 @@ Type: `string`
Add tags to the library (used for linting)
### testEnvironment
Default: `jsdom`
Type: `string`
Possible values: `jsdom`, `node`
The test environment to use if unitTestRunner is set to jest
### unitTestRunner
Default: `jest`

View File

@ -80,6 +80,16 @@ Type: `string`
Add tags to the library (used for linting)
### testEnvironment
Default: `jsdom`
Type: `string`
Possible values: `jsdom`, `node`
The test environment to use if unitTestRunner is set to jest
### unitTestRunner
Default: `jest`

View File

@ -0,0 +1,145 @@
# library
Create a new nest library
## Usage
```bash
nx generate library ...
```
```bash
nx g lib ... # same
```
By default, Nx will search for `library` in the default collection provisioned in `workspace.json`.
You can specify the collection explicitly as follows:
```bash
nx g @nrwl/nest:library ...
```
Show what will be generated without writing to disk:
```bash
nx g library ... --dry-run
```
### Examples
Generate libs/myapp/mylib:
```bash
nx g lib mylib --directory=myapp
```
## Options
### controller
Default: `false`
Type: `boolean`
Include a controller with the library
### directory
Alias(es): d
Type: `string`
A directory where the app is placed
### global
Default: `false`
Type: `boolean`
Add the Global decorator to the generated module.
### linter
Default: `tslint`
Type: `string`
Possible values: `eslint`, `tslint`
The tool to use for running lint checks.
### name
Type: `string`
Library name
### publishable
Type: `boolean`
Create a publishable library. A "build" architect will be added for this project the workspace configuration.
### service
Default: `false`
Type: `boolean`
Include a service with the library.
### skipFormat
Default: `false`
Type: `boolean`
Skip formatting files
### skipTsConfig
Default: `false`
Type: `boolean`
Do not update tsconfig.json for development experience.
### tags
Alias(es): t
Type: `string`
Add tags to the library (used for linting)
### target
Default: `es6`
Type: `string`
Possible values: `es5`, `es6`, `esnext`, `es2015`, `es2016`, `es2017`, `es2018`, `es2019`, `es2020`
The es target, Nest suggest using es6 or higher.
### testEnvironment
Default: `node`
Type: `string`
Possible values: `jsdom`, `node`
The test environment for jest, for node applications this should stay as node unless doing DOM testing.
### unitTestRunner
Default: `jest`
Type: `string`
Possible values: `jest`, `none`
Test runner to use for unit tests

View File

@ -90,6 +90,16 @@ Type: `string`
Add tags to the library (used for linting)
### testEnvironment
Default: `jsdom`
Type: `string`
Possible values: `jsdom`, `node`
The test environment to use if unitTestRunner is set to jest
### unitTestRunner
Default: `jest`

View File

@ -80,6 +80,16 @@ Type: `string`
Add tags to the library (used for linting)
### testEnvironment
Default: `jsdom`
Type: `string`
Possible values: `jsdom`, `node`
The test environment to use if unitTestRunner is set to jest
### unitTestRunner
Default: `jest`

View File

@ -0,0 +1,145 @@
# library
Create a new nest library
## Usage
```bash
nx generate library ...
```
```bash
nx g lib ... # same
```
By default, Nx will search for `library` in the default collection provisioned in `workspace.json`.
You can specify the collection explicitly as follows:
```bash
nx g @nrwl/nest:library ...
```
Show what will be generated without writing to disk:
```bash
nx g library ... --dry-run
```
### Examples
Generate libs/myapp/mylib:
```bash
nx g lib mylib --directory=myapp
```
## Options
### controller
Default: `false`
Type: `boolean`
Include a controller with the library
### directory
Alias(es): d
Type: `string`
A directory where the app is placed
### global
Default: `false`
Type: `boolean`
Add the Global decorator to the generated module.
### linter
Default: `tslint`
Type: `string`
Possible values: `eslint`, `tslint`
The tool to use for running lint checks.
### name
Type: `string`
Library name
### publishable
Type: `boolean`
Create a publishable library. A "build" architect will be added for this project the workspace configuration.
### service
Default: `false`
Type: `boolean`
Include a service with the library.
### skipFormat
Default: `false`
Type: `boolean`
Skip formatting files
### skipTsConfig
Default: `false`
Type: `boolean`
Do not update tsconfig.json for development experience.
### tags
Alias(es): t
Type: `string`
Add tags to the library (used for linting)
### target
Default: `es6`
Type: `string`
Possible values: `es5`, `es6`, `esnext`, `es2015`, `es2016`, `es2017`, `es2018`, `es2019`, `es2020`
The es target, Nest suggest using es6 or higher.
### testEnvironment
Default: `node`
Type: `string`
Possible values: `jsdom`, `node`
The test environment for jest, for node applications this should stay as node unless doing DOM testing.
### unitTestRunner
Default: `jest`
Type: `string`
Possible values: `jest`, `none`
Test runner to use for unit tests

View File

@ -90,6 +90,16 @@ Type: `string`
Add tags to the library (used for linting)
### testEnvironment
Default: `jsdom`
Type: `string`
Possible values: `jsdom`, `node`
The test environment to use if unitTestRunner is set to jest
### unitTestRunner
Default: `jest`

View File

@ -80,6 +80,16 @@ Type: `string`
Add tags to the library (used for linting)
### testEnvironment
Default: `jsdom`
Type: `string`
Possible values: `jsdom`, `node`
The test environment to use if unitTestRunner is set to jest
### unitTestRunner
Default: `jest`

View File

@ -21,6 +21,8 @@ import {
setMaxWorkers,
newProject
} from './utils';
import { stripIndents } from '@angular-devkit/core/src/utils/literals';
import { readFile } from './utils';
function getData(): Promise<any> {
return new Promise(resolve => {
@ -226,6 +228,73 @@ forEachCli(currentCLIName => {
});
}, 120000);
describe('nest libraries', function() {
it('should be able to generate a nest library', async () => {
ensureProject();
const nestlib = uniq('nestlib');
runCLI(`generate @nrwl/nest:lib ${nestlib}`);
const jestConfigContent = readFile(`libs/${nestlib}/jest.config.js`);
expect(stripIndents`${jestConfigContent}`).toEqual(
stripIndents`module.exports = {
name: '${nestlib}',
preset: '../../jest.config.js',
testEnvironment: 'node',
transform: {
'^.+\\.[tj]sx?$': 'ts-jest'
},
moduleFileExtensions: ['ts', 'tsx', 'js', 'jsx', 'html'],
coverageDirectory: '../../coverage/libs/${nestlib}'
};
`
);
const lintResults = runCLI(`lint ${nestlib}`);
expect(lintResults).toContain('All files pass linting.');
}, 60000);
it('should be able to generate a nest library w/ service', async () => {
ensureProject();
const nestlib = uniq('nestlib');
runCLI(`generate @nrwl/nest:lib ${nestlib} --service`);
const lintResults = runCLI(`lint ${nestlib}`);
expect(lintResults).toContain('All files pass linting.');
const jestResult = await runCLIAsync(`test ${nestlib}`);
expect(jestResult.stderr).toContain('Test Suites: 1 passed, 1 total');
}, 60000);
it('should be able to generate a nest library w/ controller', async () => {
ensureProject();
const nestlib = uniq('nestlib');
runCLI(`generate @nrwl/nest:lib ${nestlib} --controller`);
const lintResults = runCLI(`lint ${nestlib}`);
expect(lintResults).toContain('All files pass linting.');
const jestResult = await runCLIAsync(`test ${nestlib}`);
expect(jestResult.stderr).toContain('Test Suites: 1 passed, 1 total');
}, 60000);
it('should be able to generate a nest library w/ controller and service', async () => {
ensureProject();
const nestlib = uniq('nestlib');
runCLI(`generate @nrwl/nest:lib ${nestlib} --controller --service`);
const lintResults = runCLI(`lint ${nestlib}`);
expect(lintResults).toContain('All files pass linting.');
const jestResult = await runCLIAsync(`test ${nestlib}`);
expect(jestResult.stderr).toContain('Test Suites: 2 passed, 2 total');
}, 60000);
});
it('should be able to generate an empty application', async () => {
ensureProject();
const nodeapp = uniq('nodeapp');

View File

@ -1,6 +1,7 @@
module.exports = {
name: '<%= project %>',
preset: '<%= offsetFromRoot %>jest.config.js',<% if (supportTsx) { %>
preset: '<%= offsetFromRoot %>jest.config.js',<% if(testEnvironment) { %>
testEnvironment: '<%= testEnvironment %>',<% } %><% if (supportTsx) { %>
transform: {
'^.+\\.[tj]sx?$': 'ts-jest'
},

View File

@ -1,23 +1,22 @@
import {
Rule,
Tree,
mergeWith,
chain,
url,
apply,
SchematicContext,
chain,
filter,
mergeWith,
move,
template,
noop,
filter
Rule,
SchematicContext,
template,
Tree,
url
} from '@angular-devkit/schematics';
import {
readJsonInTree,
getProjectConfig,
offsetFromRoot,
updateJsonInTree,
updateWorkspaceInTree
} from '@nrwl/workspace';
import { getProjectConfig, addDepsToPackageJson } from '@nrwl/workspace';
import { offsetFromRoot } from '@nrwl/workspace';
import { join, normalize } from '@angular-devkit/core';
import init from '../init/init';
@ -27,6 +26,7 @@ export interface JestProjectSchema {
skipSetupFile: boolean;
setupFile: 'angular' | 'web-components' | 'none';
skipSerializers: boolean;
testEnvironment: 'node' | 'jsdom' | '';
}
function generateFiles(options: JestProjectSchema): Rule {
@ -114,6 +114,10 @@ function check(options: JestProjectSchema): Rule {
}
function normalizeOptions(options: JestProjectSchema): JestProjectSchema {
if (options.testEnvironment === 'jsdom') {
options.testEnvironment = '';
}
if (!options.skipSetupFile) {
return options;
}

View File

@ -32,6 +32,12 @@
"type": "boolean",
"description": "Setup tsx support",
"default": false
},
"testEnvironment": {
"type": "string",
"enum": ["jsdom", "node"],
"description": "The test environment for jest",
"default": "jsdom"
}
},
"required": []

View File

@ -16,6 +16,13 @@
"schema": "./src/schematics/application/schema.json",
"aliases": ["app"],
"description": "Create a nest application"
},
"library": {
"factory": "./src/schematics/library/library",
"schema": "./src/schematics/library/schema.json",
"aliases": ["lib"],
"description": "Create a new nest library"
}
}
}

View File

@ -0,0 +1,26 @@
import { Test } from '@nestjs/testing';
import { <%= className %>Controller } from './<%= fileName %>.controller';
<% if(service) { %>import { <%= className %>Service } from './<%= fileName %>.service';<% } %>
describe('<%= className %>Controller', () => {
let controller: <%= className %>Controller;
beforeEach(async () => {
const module = await Test.createTestingModule({
providers: [
<% if(service) { %><%= className %>Service<% } %>
],
controllers: [
<%= className %>Controller
]
}).compile();
controller = module.get(<%= className %>Controller);
});
it('should be defined', () => {
expect(controller).toBeTruthy();
});
})

View File

@ -0,0 +1,7 @@
import { Controller } from '@nestjs/common';
<% if(service) { %>import { <%= className %>Service } from './<%= fileName %>.service';<% } %>
@Controller('<%= fileName %>')
export class <%= className %>Controller {
constructor(<% if(service) { %>private <%= propertyName %>Service: <%= className %>Service<% } %>) {}
}

View File

@ -0,0 +1,18 @@
import { Module<% if(global) { %>, Global<% } %> } from '@nestjs/common';
<% if(service) { %>import { <%= className %>Service } from './<%= fileName %>.service';<% } %>
<% if(controller) { %>import { <%= className %>Controller } from './<%= fileName %>.controller';<% } %>
<% if(global) { %>@Global()<% } %>
@Module({
controllers: [
<% if(controller) { %><%= className %>Controller<% } %>
],
providers: [
<% if(service) { %><%= className %>Service<% } %>
],
exports: [
<% if(service) { %><%= className %>Service<% } %>
]
})
export class <%= className %>Module {
}

View File

@ -0,0 +1,22 @@
import { Test } from '@nestjs/testing';
import { <%= className %>Service } from './<%= fileName %>.service';
describe('<%= className %>Service', () => {
let service: <%= className %>Service;
beforeEach(async () => {
const module = await Test.createTestingModule({
providers: [
<%= className %>Service
]
}).compile();
service = module.get(<%= className %>Service);
});
it('should be defined', () => {
expect(service).toBeTruthy();
});
})

View File

@ -0,0 +1,6 @@
import { Injectable } from '@nestjs/common';
@Injectable()
export class <%= className %>Service {
constructor() {}
}

View File

@ -0,0 +1,409 @@
import { Tree } from '@angular-devkit/schematics';
import { NxJson, readJsonInTree } from '@nrwl/workspace';
import { createEmptyWorkspace, getFileContent } from '@nrwl/workspace/testing';
import { runSchematic } from '../../utils/testing';
import { stripIndents } from '@angular-devkit/core/src/utils/literals';
describe('lib', () => {
let appTree: Tree;
beforeEach(() => {
appTree = Tree.empty();
appTree = createEmptyWorkspace(appTree);
});
describe('not nested', () => {
it('should update workspace.json', async () => {
const tree = await runSchematic('lib', { name: 'myLib' }, appTree);
const workspaceJson = readJsonInTree(tree, '/workspace.json');
expect(workspaceJson.projects['my-lib'].root).toEqual('libs/my-lib');
expect(workspaceJson.projects['my-lib'].architect.build).toBeUndefined();
expect(workspaceJson.projects['my-lib'].architect.lint).toEqual({
builder: '@angular-devkit/build-angular:tslint',
options: {
exclude: ['**/node_modules/**', '!libs/my-lib/**'],
tsConfig: [
'libs/my-lib/tsconfig.lib.json',
'libs/my-lib/tsconfig.spec.json'
]
}
});
expect(workspaceJson.projects['my-lib'].architect.test).toEqual({
builder: '@nrwl/jest:jest',
options: {
jestConfig: 'libs/my-lib/jest.config.js',
tsConfig: 'libs/my-lib/tsconfig.spec.json',
passWithNoTests: true
}
});
});
it('should include a controller', async () => {
const tree = await runSchematic(
'lib',
{ name: 'myLib', controller: true },
appTree
);
const service = getFileContent(
tree,
'libs/my-lib/src/lib/my-lib.controller.ts'
);
expect(service).toBeTruthy();
});
it('should include a service', async () => {
const tree = await runSchematic(
'lib',
{ name: 'myLib', service: true },
appTree
);
const service = getFileContent(
tree,
'libs/my-lib/src/lib/my-lib.service.ts'
);
expect(service).toBeTruthy();
});
it('should add the @Global decorator', async () => {
const tree = await runSchematic(
'lib',
{ name: 'myLib', global: true },
appTree
);
const module = getFileContent(
tree,
'libs/my-lib/src/lib/my-lib.module.ts'
);
expect(stripIndents`${module}`).toEqual(
stripIndents`import { Module, Global } from '@nestjs/common';
@Global()
@Module({
controllers: [],
providers: [],
exports: []
})
export class MyLibModule {}`
);
});
it('should provide the controller and service', async () => {
const tree = await runSchematic(
'lib',
{ name: 'myLib', service: true, controller: true },
appTree
);
const module = getFileContent(
tree,
'libs/my-lib/src/lib/my-lib.module.ts'
);
expect(stripIndents`${module}`).toEqual(
stripIndents`import { Module } from '@nestjs/common';
import { MyLibService } from './my-lib.service';
import { MyLibController } from './my-lib.controller';
@Module({
controllers: [MyLibController],
providers: [MyLibService],
exports: [MyLibService]
})
export class MyLibModule {}`
);
const controller = getFileContent(
tree,
'libs/my-lib/src/lib/my-lib.controller.ts'
);
expect(stripIndents`${controller}`).toEqual(
stripIndents`import { Controller } from '@nestjs/common';
import { MyLibService } from './my-lib.service';
@Controller('my-lib')
export class MyLibController {
constructor(private myLibService: MyLibService) {}
}`
);
const barrel = getFileContent(tree, 'libs/my-lib/src/index.ts');
expect(stripIndents`${barrel}`).toEqual(
stripIndents`export * from './lib/my-lib.module';
export * from './lib/my-lib.service';
export * from './lib/my-lib.controller';`
);
});
it('should update nx.json', async () => {
const tree = await runSchematic(
'lib',
{ name: 'myLib', tags: 'one,two' },
appTree
);
const nxJson = readJsonInTree<NxJson>(tree, '/nx.json');
expect(nxJson).toEqual({
npmScope: 'proj',
projects: {
'my-lib': {
tags: ['one', 'two']
}
}
});
});
it('should update root tsconfig.json', async () => {
const tree = await runSchematic('lib', { name: 'myLib' }, appTree);
const tsconfigJson = readJsonInTree(tree, '/tsconfig.json');
expect(tsconfigJson.compilerOptions.paths['@proj/my-lib']).toEqual([
'libs/my-lib/src/index.ts'
]);
});
it('should create a local tsconfig.json', async () => {
const tree = await runSchematic('lib', { name: 'myLib' }, appTree);
const tsconfigJson = readJsonInTree(tree, 'libs/my-lib/tsconfig.json');
expect(tsconfigJson).toEqual({
extends: '../../tsconfig.json',
compilerOptions: {
types: ['node', 'jest'],
target: 'es6'
},
include: ['**/*.ts']
});
});
it('should extend the local tsconfig.json with tsconfig.spec.json', async () => {
const tree = await runSchematic('lib', { name: 'myLib' }, appTree);
const tsconfigJson = readJsonInTree(
tree,
'libs/my-lib/tsconfig.spec.json'
);
expect(tsconfigJson.extends).toEqual('./tsconfig.json');
});
it('should extend the local tsconfig.json with tsconfig.lib.json', async () => {
const tree = await runSchematic('lib', { name: 'myLib' }, appTree);
const tsconfigJson = readJsonInTree(
tree,
'libs/my-lib/tsconfig.lib.json'
);
expect(tsconfigJson.extends).toEqual('./tsconfig.json');
});
it('should generate files', async () => {
const tree = await runSchematic('lib', { name: 'myLib' }, appTree);
expect(tree.exists(`libs/my-lib/jest.config.js`)).toBeTruthy();
expect(tree.exists('libs/my-lib/src/index.ts')).toBeTruthy();
expect(tree.exists(`libs/my-lib/src/lib/my-lib.spec.ts`)).toBeFalsy();
});
});
describe('nested', () => {
it('should update nx.json', async () => {
const tree = await runSchematic(
'lib',
{
name: 'myLib',
directory: 'myDir',
tags: 'one'
},
appTree
);
const nxJson = readJsonInTree<NxJson>(tree, '/nx.json');
expect(nxJson).toEqual({
npmScope: 'proj',
projects: {
'my-dir-my-lib': {
tags: ['one']
}
}
});
const tree2 = await runSchematic(
'lib',
{
name: 'myLib2',
directory: 'myDir',
tags: 'one,two'
},
tree
);
const nxJson2 = readJsonInTree<NxJson>(tree2, '/nx.json');
expect(nxJson2).toEqual({
npmScope: 'proj',
projects: {
'my-dir-my-lib': {
tags: ['one']
},
'my-dir-my-lib2': {
tags: ['one', 'two']
}
}
});
});
it('should generate files', async () => {
const tree = await runSchematic(
'lib',
{ name: 'myLib', directory: 'myDir' },
appTree
);
expect(tree.exists(`libs/my-dir/my-lib/jest.config.js`)).toBeTruthy();
expect(tree.exists('libs/my-dir/my-lib/src/index.ts')).toBeTruthy();
expect(
tree.exists(`libs/my-dir/my-lib/src/lib/my-lib.spec.ts`)
).toBeFalsy();
});
it('should update workspace.json', async () => {
const tree = await runSchematic(
'lib',
{ name: 'myLib', directory: 'myDir' },
appTree
);
const workspaceJson = readJsonInTree(tree, '/workspace.json');
expect(workspaceJson.projects['my-dir-my-lib'].root).toEqual(
'libs/my-dir/my-lib'
);
expect(workspaceJson.projects['my-dir-my-lib'].architect.lint).toEqual({
builder: '@angular-devkit/build-angular:tslint',
options: {
exclude: ['**/node_modules/**', '!libs/my-dir/my-lib/**'],
tsConfig: [
'libs/my-dir/my-lib/tsconfig.lib.json',
'libs/my-dir/my-lib/tsconfig.spec.json'
]
}
});
});
it('should update tsconfig.json', async () => {
const tree = await runSchematic(
'lib',
{ name: 'myLib', directory: 'myDir' },
appTree
);
const tsconfigJson = readJsonInTree(tree, '/tsconfig.json');
expect(tsconfigJson.compilerOptions.paths['@proj/my-dir/my-lib']).toEqual(
['libs/my-dir/my-lib/src/index.ts']
);
expect(
tsconfigJson.compilerOptions.paths['my-dir-my-lib/*']
).toBeUndefined();
});
it('should create a local tsconfig.json', async () => {
const tree = await runSchematic(
'lib',
{ name: 'myLib', directory: 'myDir' },
appTree
);
const tsconfigJson = readJsonInTree(
tree,
'libs/my-dir/my-lib/tsconfig.json'
);
expect(tsconfigJson).toEqual({
extends: '../../../tsconfig.json',
compilerOptions: {
types: ['node', 'jest'],
target: 'es6'
},
include: ['**/*.ts']
});
});
});
describe('--unit-test-runner none', () => {
it('should not generate test configuration', async () => {
const resultTree = await runSchematic(
'lib',
{ name: 'myLib', unitTestRunner: 'none' },
appTree
);
expect(resultTree.exists('libs/my-lib/tsconfig.spec.json')).toBeFalsy();
expect(resultTree.exists('libs/my-lib/jest.config.js')).toBeFalsy();
expect(resultTree.exists('libs/my-lib/lib/my-lib.spec.ts')).toBeFalsy();
const workspaceJson = readJsonInTree(resultTree, 'workspace.json');
expect(workspaceJson.projects['my-lib'].architect.test).toBeUndefined();
const tsconfigJson = readJsonInTree(
resultTree,
'libs/my-lib/tsconfig.json'
);
expect(tsconfigJson).toEqual({
extends: '../../tsconfig.json',
compilerOptions: {
types: ['node'],
target: 'es6'
},
include: ['**/*.ts']
});
expect(
workspaceJson.projects['my-lib'].architect.lint.options.tsConfig
).toEqual(['libs/my-lib/tsconfig.lib.json']);
});
});
describe('publishable package', () => {
it('should update package.json', async () => {
const publishableTree = await runSchematic(
'lib',
{ name: 'mylib', publishable: true },
appTree
);
let packageJsonContent = readJsonInTree(
publishableTree,
'libs/mylib/package.json'
);
expect(packageJsonContent.name).toEqual('@proj/mylib');
});
});
describe('compiler options target', () => {
it('should set target to es6 in tsconfig.json by default', async () => {
const tree = await runSchematic(
'lib',
{ name: 'myLib', directory: 'myDir' },
appTree
);
const tsconfigJson = readJsonInTree(
tree,
'libs/my-dir/my-lib/tsconfig.json'
);
expect(tsconfigJson.compilerOptions.target).toEqual('es6');
});
it('should set target to es2020 in tsconfig.json', async () => {
const tree = await runSchematic(
'lib',
{ name: 'myLib', directory: 'myDir', target: 'es2020' },
appTree
);
const tsconfigJson = readJsonInTree(
tree,
'libs/my-dir/my-lib/tsconfig.json'
);
expect(tsconfigJson.compilerOptions.target).toEqual('es2020');
});
it('should set target jest testEnvironment to node', async () => {
const tree = await runSchematic('lib', { name: 'myLib' }, appTree);
const jestConfig = getFileContent(tree, 'libs/my-lib/jest.config.js');
expect(stripIndents`${jestConfig}`)
.toEqual(stripIndents`module.exports = {
name: 'my-lib',
preset: '../../jest.config.js',
testEnvironment: 'node',
transform: {
'^.+\\.[tj]sx?$': 'ts-jest'
},
moduleFileExtensions: ['ts', 'tsx', 'js', 'jsx', 'html'],
coverageDirectory: '../../coverage/libs/my-lib'
};`);
});
});
});

View File

@ -0,0 +1,192 @@
import { normalize, Path } from '@angular-devkit/core';
import {
apply,
chain,
externalSchematic,
filter,
MergeStrategy,
mergeWith,
move,
noop,
Rule,
SchematicContext,
template,
Tree,
url
} from '@angular-devkit/schematics';
import {
addGlobal,
deleteFile,
formatFiles,
getNpmScope,
getProjectConfig,
insert,
names,
offsetFromRoot,
toFileName,
updateJsonInTree,
updateWorkspaceInTree
} from '@nrwl/workspace';
import { Schema } from './schema';
import * as ts from 'typescript';
import { RemoveChange } from '@nrwl/workspace/src/utils/ast-utils';
export interface NormalizedSchema extends Schema {
name: string;
prefix: string;
fileName: string;
projectRoot: Path;
projectDirectory: string;
parsedTags: string[];
}
export default function(schema: NormalizedSchema): Rule {
return (host: Tree, context: SchematicContext) => {
const options = normalizeOptions(host, schema);
return chain([
externalSchematic('@nrwl/node', 'lib', schema),
createFiles(options),
addExportsToBarrelFile(options),
updateTsConfig(options),
addProject(options),
formatFiles(options),
deleteFile(`/${options.projectRoot}/src/lib/${options.fileName}.spec.ts`)
]);
};
}
function normalizeOptions(host: Tree, options: Schema): NormalizedSchema {
const defaultPrefix = getNpmScope(host);
const name = toFileName(options.name);
const projectDirectory = options.directory
? `${toFileName(options.directory)}/${name}`
: name;
const projectName = projectDirectory.replace(new RegExp('/', 'g'), '-');
const fileName = projectName;
const projectRoot = normalize(`libs/${projectDirectory}`);
const parsedTags = options.tags
? options.tags.split(',').map(s => s.trim())
: [];
const normalized: NormalizedSchema = {
...options,
prefix: defaultPrefix, // we could also allow customizing this
fileName,
name: projectName,
projectRoot,
projectDirectory,
parsedTags
};
return normalized;
}
function addExportsToBarrelFile(options: NormalizedSchema): Rule {
return (host: Tree) => {
const indexFilePath = `${options.projectRoot}/src/index.ts`;
const buffer = host.read(indexFilePath);
if (!!buffer) {
const indexSource = buffer!.toString('utf-8');
const indexSourceFile = ts.createSourceFile(
indexFilePath,
indexSource,
ts.ScriptTarget.Latest,
true
);
insert(host, indexFilePath, [
new RemoveChange(
indexFilePath,
0,
`export * from './lib/${options.fileName}';`
),
...addGlobal(
indexSourceFile,
indexFilePath,
`export * from './lib/${options.fileName}.module';`
),
...(options.service
? addGlobal(
indexSourceFile,
indexFilePath,
`export * from './lib/${options.fileName}.service';`
)
: []),
...(options.controller
? addGlobal(
indexSourceFile,
indexFilePath,
`export * from './lib/${options.fileName}.controller';`
)
: [])
]);
}
};
}
function createFiles(options: NormalizedSchema): Rule {
return mergeWith(
apply(url(`./files/lib`), [
template({
...options,
...names(options.name),
tmpl: '',
offsetFromRoot: offsetFromRoot(options.projectRoot)
}),
move(options.projectRoot),
options.unitTestRunner === 'none'
? filter(file => !file.endsWith('spec.ts'))
: noop(),
options.publishable
? noop()
: filter(file => !file.endsWith('package.json')),
options.service ? noop() : filter(file => !file.endsWith('.service.ts')),
options.controller
? noop()
: filter(file => !file.endsWith('.controller.ts')),
!options.controller || options.unitTestRunner === 'none'
? filter(file => !file.endsWith('.controller.spec.ts'))
: noop(),
!options.service || options.unitTestRunner === 'none'
? filter(file => !file.endsWith('.service.spec.ts'))
: noop()
]),
MergeStrategy.Overwrite
);
}
function updateTsConfig(options: NormalizedSchema): Rule {
return (host: Tree, context: SchematicContext) => {
const projectConfig = getProjectConfig(host, options.name);
return updateJsonInTree(`${projectConfig.root}/tsconfig.json`, json => {
json.compilerOptions.target = options.target;
return json;
});
};
}
function addProject(options: NormalizedSchema): Rule {
if (!options.publishable) {
return noop();
}
return updateWorkspaceInTree(json => {
const architect = json.projects[options.name].architect;
if (architect) {
architect.build = {
builder: '@nrwl/node:package',
options: {
outputPath: `dist/libs/${options.projectDirectory}`,
tsConfig: `${options.projectRoot}/tsconfig.lib.json`,
packageJson: `${options.projectRoot}/package.json`,
main: `${options.projectRoot}/src/index.ts`,
assets: [`${options.projectRoot}/*.md`]
}
};
}
return json;
});
}

View File

@ -0,0 +1,26 @@
import { Linter } from '@nrwl/workspace';
export interface Schema {
name: string;
directory?: string;
skipTsConfig: boolean;
skipFormat: boolean;
tags?: string;
unitTestRunner: 'jest' | 'none';
linter: Linter;
publishable?: boolean;
global?: boolean;
service?: boolean;
controller?: boolean;
target?:
| 'es5'
| 'es6'
| 'esnext'
| 'es2015'
| 'es2016'
| 'es2017'
| 'es2018'
| 'es2019'
| 'es2020';
testEnvironment: 'jsdom' | 'node';
}

View File

@ -0,0 +1,97 @@
{
"$schema": "http://json-schema.org/schema",
"id": "NxNestLibrary",
"title": "Create a Nest Library for Nx",
"type": "object",
"examples": [
{
"command": "g lib mylib --directory=myapp",
"description": "Generate libs/myapp/mylib"
}
],
"properties": {
"name": {
"type": "string",
"description": "Library name",
"$default": {
"$source": "argv",
"index": 0
},
"x-prompt": "What name would you like to use for the library?"
},
"directory": {
"type": "string",
"description": "A directory where the app is placed",
"alias": "d"
},
"linter": {
"description": "The tool to use for running lint checks.",
"type": "string",
"enum": ["eslint", "tslint"],
"default": "tslint"
},
"unitTestRunner": {
"type": "string",
"enum": ["jest", "none"],
"description": "Test runner to use for unit tests",
"default": "jest"
},
"tags": {
"type": "string",
"description": "Add tags to the library (used for linting)",
"alias": "t"
},
"skipFormat": {
"description": "Skip formatting files",
"type": "boolean",
"default": false
},
"skipTsConfig": {
"type": "boolean",
"default": false,
"description": "Do not update tsconfig.json for development experience."
},
"publishable": {
"type": "boolean",
"description": "Create a publishable library. A \"build\" architect will be added for this project the workspace configuration."
},
"global": {
"type": "boolean",
"description": "Add the Global decorator to the generated module.",
"default": false
},
"service": {
"type": "boolean",
"description": "Include a service with the library.",
"default": false
},
"controller": {
"type": "boolean",
"description": "Include a controller with the library",
"default": false
},
"testEnvironment": {
"type": "string",
"enum": ["jsdom", "node"],
"description": "The test environment for jest, for node applications this should stay as node unless doing DOM testing.",
"default": "node"
},
"target": {
"type": "string",
"description": "The es target, Nest suggest using es6 or higher.",
"default": "es6",
"enum": [
"es5",
"es6",
"esnext",
"es2015",
"es2016",
"es2017",
"es2018",
"es2019",
"es2020"
]
}
},
"required": ["name"]
}

View File

@ -9,4 +9,5 @@ export interface Schema {
unitTestRunner: 'jest' | 'none';
linter: Linter;
publishable?: boolean;
testEnvironment: 'jsdom' | 'node';
}

View File

@ -54,6 +54,12 @@
"publishable": {
"type": "boolean",
"description": "Create a publishable library. A \"build\" architect will be added for this project the workspace configuration."
},
"testEnvironment": {
"type": "string",
"enum": ["jsdom", "node"],
"description": "The test environment to use if unitTestRunner is set to jest",
"default": "jsdom"
}
},
"required": ["name"]

View File

@ -100,7 +100,8 @@ export default function(schema: Schema): Rule {
project: options.name,
setupFile: 'none',
supportTsx: true,
skipSerializers: true
skipSerializers: true,
testEnvironment: options.testEnvironment
})
: noop(),
formatFiles(options)

View File

@ -9,4 +9,5 @@ export interface Schema {
simpleModuleName: boolean;
unitTestRunner: 'jest' | 'none';
linter: Linter;
testEnvironment: 'jsdom' | 'node';
}

View File

@ -48,6 +48,12 @@
"type": "boolean",
"default": false,
"description": "Do not update tsconfig.json for development experience."
},
"testEnvironment": {
"type": "string",
"enum": ["jsdom", "node"],
"description": "The test environment to use if unitTestRunner is set to jest",
"default": "jsdom"
}
},
"required": ["name"]

View File

@ -253,7 +253,10 @@ function setDefaultLinter(linter: string) {
plugin: { linter }
};
json.schematics['@nrwl/nest'] = { application: { linter } };
json.schematics['@nrwl/express'] = { application: { linter } };
json.schematics['@nrwl/express'] = {
application: { linter },
library: { linter }
};
return json;
});
}

View File

@ -6,7 +6,7 @@ const gitMessage = require('child_process')
.toString()
.trim();
const matchCommit = /(chore|feat|fix|cleanup|docs)\((angular|bazel|core|docs|nextjs|linter|node|nx-plugin|react|storybook|testing|repo|misc)\):\s(([a-z0-9:\-\s])+)/g.test(
const matchCommit = /(chore|feat|fix|cleanup|docs)\((angular|bazel|core|docs|nextjs|nest|linter|node|nx-plugin|react|storybook|testing|repo|misc)\):\s(([a-z0-9:\-\s])+)/g.test(
gitMessage
);
const matchRevert = /Revert/gi.test(gitMessage);
@ -27,7 +27,7 @@ if (exitCode === 0) {
console.log('\n');
console.log('possible types: chore|build|feat|fix|cleanup|docs');
console.log(
'possible scopes: angular|bazel|core|docs|nextjs|linter|node|react|storybook|testing|repo|misc (if unsure use "core")'
'possible scopes: angular|bazel|core|docs|nextjs|nest|linter|linter|node|react|storybook|testing|repo|misc (if unsure use "core")'
);
console.log(
'\nEXAMPLE: \n' +