From 46657eeca968a82799dff1e307055e6834a0b891 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Kamil=20My=C5=9Bliwiec?= Date: Sun, 9 Dec 2018 12:30:51 +0100 Subject: [PATCH] feature() add resolver schematic --- package.json | 2 +- src/collection.json | 5 + src/lib/filter/schema.json | 2 +- src/lib/gateway/schema.json | 2 +- src/lib/guard/schema.json | 2 +- src/lib/interceptor/schema.json | 2 +- src/lib/interface/schema.json | 2 +- src/lib/middleware/schema.json | 2 +- src/lib/pipe/schema.json | 2 +- src/lib/provider/schema.json | 2 +- .../resolver/files/js/__name__.resolver.js | 4 + .../files/js/__name__.resolver.spec.js | 15 +++ .../files/ts/__name__.resolver.spec.ts | 16 +++ .../resolver/files/ts/__name__.resolver.ts | 4 + src/lib/resolver/resolver.factory.test.ts | 117 ++++++++++++++++++ src/lib/resolver/resolver.factory.ts | 92 ++++++++++++++ src/lib/resolver/resolver.schema.d.ts | 36 ++++++ src/lib/resolver/schema.json | 38 ++++++ src/lib/service/schema.json | 2 +- 19 files changed, 337 insertions(+), 10 deletions(-) create mode 100644 src/lib/resolver/files/js/__name__.resolver.js create mode 100644 src/lib/resolver/files/js/__name__.resolver.spec.js create mode 100644 src/lib/resolver/files/ts/__name__.resolver.spec.ts create mode 100644 src/lib/resolver/files/ts/__name__.resolver.ts create mode 100644 src/lib/resolver/resolver.factory.test.ts create mode 100644 src/lib/resolver/resolver.factory.ts create mode 100644 src/lib/resolver/resolver.schema.d.ts create mode 100644 src/lib/resolver/schema.json diff --git a/package.json b/package.json index 864d8886e..c9e89da25 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "@nestjs/schematics", - "version": "5.10.1", + "version": "5.11.0", "description": "Nest - modern, fast, powerful node.js web framework (@schematics)", "main": "index.js", "publishConfig": { diff --git a/src/collection.json b/src/collection.json index bb66f6d6c..3ed3ad9ff 100644 --- a/src/collection.json +++ b/src/collection.json @@ -71,6 +71,11 @@ "description": "Create a Nest service.", "schema": "./lib/service/schema.json" }, + "resolver": { + "factory": "./lib/resolver/resolver.factory#main", + "description": "Create a Nest resolver.", + "schema": "./lib/resolver/schema.json" + }, "configuration": { "factory": "./lib/configuration/configuration.factory#main", "description": "Create a Nest CLI configuration." diff --git a/src/lib/filter/schema.json b/src/lib/filter/schema.json index 02301d23d..c90fd1d3a 100644 --- a/src/lib/filter/schema.json +++ b/src/lib/filter/schema.json @@ -23,7 +23,7 @@ }, "sourceRoot": { "type": "string", - "description": "Nest decorator source root directory." + "description": "Nest filter source root directory." }, "flat": { "default": true, diff --git a/src/lib/gateway/schema.json b/src/lib/gateway/schema.json index 6ede61f9d..de3bdd53d 100644 --- a/src/lib/gateway/schema.json +++ b/src/lib/gateway/schema.json @@ -23,7 +23,7 @@ }, "sourceRoot": { "type": "string", - "description": "Nest decorator source root directory." + "description": "Nest gateway source root directory." }, "flat": { "default": true, diff --git a/src/lib/guard/schema.json b/src/lib/guard/schema.json index 7b2fd0bab..3b3a5350c 100644 --- a/src/lib/guard/schema.json +++ b/src/lib/guard/schema.json @@ -23,7 +23,7 @@ }, "sourceRoot": { "type": "string", - "description": "Nest decorator source root directory." + "description": "Nest guard source root directory." }, "flat": { "default": true, diff --git a/src/lib/interceptor/schema.json b/src/lib/interceptor/schema.json index 23934a4a3..0f43fd5e1 100644 --- a/src/lib/interceptor/schema.json +++ b/src/lib/interceptor/schema.json @@ -23,7 +23,7 @@ }, "sourceRoot": { "type": "string", - "description": "Nest decorator source root directory." + "description": "Nest interceptor source root directory." }, "flat": { "default": true, diff --git a/src/lib/interface/schema.json b/src/lib/interface/schema.json index b64b71433..5f17d96d7 100644 --- a/src/lib/interface/schema.json +++ b/src/lib/interface/schema.json @@ -19,7 +19,7 @@ }, "sourceRoot": { "type": "string", - "description": "Nest decorator source root directory." + "description": "Nest interface source root directory." }, "flat": { "default": true, diff --git a/src/lib/middleware/schema.json b/src/lib/middleware/schema.json index 835553864..04f5002a5 100644 --- a/src/lib/middleware/schema.json +++ b/src/lib/middleware/schema.json @@ -23,7 +23,7 @@ }, "sourceRoot": { "type": "string", - "description": "Nest decorator source root directory." + "description": "Nest middleware source root directory." }, "flat": { "default": true, diff --git a/src/lib/pipe/schema.json b/src/lib/pipe/schema.json index 98d79be62..72e234591 100644 --- a/src/lib/pipe/schema.json +++ b/src/lib/pipe/schema.json @@ -23,7 +23,7 @@ }, "sourceRoot": { "type": "string", - "description": "Nest decorator source root directory." + "description": "Nest pipe source root directory." }, "flat": { "default": true, diff --git a/src/lib/provider/schema.json b/src/lib/provider/schema.json index f98a44e15..0e1d95099 100644 --- a/src/lib/provider/schema.json +++ b/src/lib/provider/schema.json @@ -23,7 +23,7 @@ }, "sourceRoot": { "type": "string", - "description": "Nest decorator source root directory." + "description": "Nest provider source root directory." }, "flat": { "default": true, diff --git a/src/lib/resolver/files/js/__name__.resolver.js b/src/lib/resolver/files/js/__name__.resolver.js new file mode 100644 index 000000000..e04fb4fa4 --- /dev/null +++ b/src/lib/resolver/files/js/__name__.resolver.js @@ -0,0 +1,4 @@ +import { Resolver } from '@nestjs/graphql'; + +@Resolver('<%= classify(name) %>') +export class <%= classify(name) %>Resolver {} diff --git a/src/lib/resolver/files/js/__name__.resolver.spec.js b/src/lib/resolver/files/js/__name__.resolver.spec.js new file mode 100644 index 000000000..16018540d --- /dev/null +++ b/src/lib/resolver/files/js/__name__.resolver.spec.js @@ -0,0 +1,15 @@ +import { Test } from '@nestjs/testing'; +import { <%= classify(name) %>Resolver } from './<%= name %>.resolver'; + +describe('<%= classify(name) %>Resolver', () => { + let resolver; + beforeAll(async () => { + const module = await Test.createTestingModule({ + providers: [<%= classify(name) %>Resolver], + }).compile(); + resolver = module.get(<%= classify(name) %>Resolver); + }); + it('should be defined', () => { + expect(resolver).toBeDefined(); + }); +}); diff --git a/src/lib/resolver/files/ts/__name__.resolver.spec.ts b/src/lib/resolver/files/ts/__name__.resolver.spec.ts new file mode 100644 index 000000000..ddf7b79d6 --- /dev/null +++ b/src/lib/resolver/files/ts/__name__.resolver.spec.ts @@ -0,0 +1,16 @@ +import { Test, TestingModule } from '@nestjs/testing'; +import { <%= classify(name) %>Resolver } from './<%= name %>.resolver'; + +describe('<%= classify(name) %>Resolver', () => { + let resolver: <%= classify(name) %>Resolver; + + beforeAll(async () => { + const module: TestingModule = await Test.createTestingModule({ + providers: [<%= classify(name) %>Resolver], + }).compile(); + resolver = module.get<<%= classify(name) %>Resolver>(<%= classify(name) %>Resolver); + }); + it('should be defined', () => { + expect(resolver).toBeDefined(); + }); +}); diff --git a/src/lib/resolver/files/ts/__name__.resolver.ts b/src/lib/resolver/files/ts/__name__.resolver.ts new file mode 100644 index 000000000..e04fb4fa4 --- /dev/null +++ b/src/lib/resolver/files/ts/__name__.resolver.ts @@ -0,0 +1,4 @@ +import { Resolver } from '@nestjs/graphql'; + +@Resolver('<%= classify(name) %>') +export class <%= classify(name) %>Resolver {} diff --git a/src/lib/resolver/resolver.factory.test.ts b/src/lib/resolver/resolver.factory.test.ts new file mode 100644 index 000000000..6323fb56f --- /dev/null +++ b/src/lib/resolver/resolver.factory.test.ts @@ -0,0 +1,117 @@ +import { + SchematicTestRunner, + UnitTestTree, +} from '@angular-devkit/schematics/testing'; +import * as path from 'path'; +import { ResolverOptions } from './resolver.schema'; + +describe('Resolver Factory', () => { + const runner: SchematicTestRunner = new SchematicTestRunner( + '.', + path.join(process.cwd(), 'src/collection.json'), + ); + it('should manage name only', () => { + const options: ResolverOptions = { + name: 'foo', + flat: false, + }; + const tree: UnitTestTree = runner.runSchematic('resolver', options); + const files: string[] = tree.files; + expect( + files.find(filename => filename === '/foo/foo.resolver.ts'), + ).not.toBeUndefined(); + expect(tree.readContent('/foo/foo.resolver.ts')).toEqual( + "import { Resolver } from '@nestjs/graphql';\n" + + '\n' + + '@Resolver(\'Foo\')\n' + + 'export class FooResolver {}\n', + ); + }); + it('should manage name as a path', () => { + const options: ResolverOptions = { + name: 'bar/foo', + flat: false, + }; + const tree: UnitTestTree = runner.runSchematic('resolver', options); + const files: string[] = tree.files; + expect( + files.find(filename => filename === '/bar/foo/foo.resolver.ts'), + ).not.toBeUndefined(); + expect(tree.readContent('/bar/foo/foo.resolver.ts')).toEqual( + "import { Resolver } from '@nestjs/graphql';\n" + + '\n' + + '@Resolver(\'Foo\')\n' + + 'export class FooResolver {}\n', + ); + }); + it('should manage name and path', () => { + const options: ResolverOptions = { + name: 'foo', + path: 'baz', + flat: false, + }; + const tree: UnitTestTree = runner.runSchematic('resolver', options); + const files: string[] = tree.files; + expect( + files.find(filename => filename === '/baz/foo/foo.resolver.ts'), + ).not.toBeUndefined(); + expect(tree.readContent('/baz/foo/foo.resolver.ts')).toEqual( + "import { Resolver } from '@nestjs/graphql';\n" + + '\n' + + '@Resolver(\'Foo\')\n' + + 'export class FooResolver {}\n', + ); + }); + it('should manage name to dasherize', () => { + const options: ResolverOptions = { + name: 'fooBar', + flat: false, + }; + const tree: UnitTestTree = runner.runSchematic('resolver', options); + const files: string[] = tree.files; + expect( + files.find(filename => filename === '/foo-bar/foo-bar.resolver.ts'), + ).not.toBeUndefined(); + expect(tree.readContent('/foo-bar/foo-bar.resolver.ts')).toEqual( + "import { Resolver } from '@nestjs/graphql';\n" + + '\n' + + '@Resolver(\'FooBar\')\n' + + 'export class FooBarResolver {}\n', + ); + }); + it('should manage path to dasherize', () => { + const options: ResolverOptions = { + name: 'barBaz/foo', + flat: false, + }; + const tree: UnitTestTree = runner.runSchematic('resolver', options); + const files: string[] = tree.files; + expect( + files.find(filename => filename === '/bar-baz/foo/foo.resolver.ts'), + ).not.toBeUndefined(); + expect(tree.readContent('/bar-baz/foo/foo.resolver.ts')).toEqual( + "import { Resolver } from '@nestjs/graphql';\n" + + '\n' + + '@Resolver(\'Foo\')\n' + + 'export class FooResolver {}\n', + ); + }); + it('should manage javascript file', () => { + const options: ResolverOptions = { + name: 'foo', + language: 'js', + flat: false, + }; + const tree: UnitTestTree = runner.runSchematic('resolver', options); + const files: string[] = tree.files; + expect( + files.find(filename => filename === '/foo/foo.resolver.js'), + ).not.toBeUndefined(); + expect(tree.readContent('/foo/foo.resolver.js')).toEqual( + "import { Resolver } from '@nestjs/graphql';\n" + + '\n' + + '@Resolver(\'Foo\')\n' + + 'export class FooResolver {}\n', + ); + }); +}); diff --git a/src/lib/resolver/resolver.factory.ts b/src/lib/resolver/resolver.factory.ts new file mode 100644 index 000000000..58186f057 --- /dev/null +++ b/src/lib/resolver/resolver.factory.ts @@ -0,0 +1,92 @@ +import { join, Path, strings } from '@angular-devkit/core'; +import { + apply, + branchAndMerge, + chain, + filter, + mergeWith, + move, + noop, + Rule, + SchematicContext, + SchematicsException, + Source, + template, + Tree, + url, +} from '@angular-devkit/schematics'; +import { + DeclarationOptions, + ModuleDeclarator, +} from '../../utils/module.declarator'; +import { ModuleFinder } from '../../utils/module.finder'; +import { Location, NameParser } from '../../utils/name.parser'; +import { mergeSourceRoot } from '../../utils/source-root.helpers'; +import { ProviderOptions } from '../provider/provider.schema'; +import { ResolverOptions } from './resolver.schema'; + +export function main(options: ResolverOptions): Rule { + options = transform(options); + return (tree: Tree, context: SchematicContext) => { + return branchAndMerge( + chain([ + mergeSourceRoot(options), + addDeclarationToModule(options), + mergeWith(generate(options)), + ]), + )(tree, context); + }; +} + +function transform(options: ResolverOptions): ResolverOptions { + const target: ResolverOptions = Object.assign({}, options); + if (!target.name) { + throw new SchematicsException('Option (name) is required.'); + } + target.metadata = 'providers'; + target.type = 'resolver'; + + const location: Location = new NameParser().parse(target); + target.name = strings.dasherize(location.name); + target.path = strings.dasherize(location.path); + target.language = target.language !== undefined ? target.language : 'ts'; + + target.path = target.flat + ? target.path + : join(target.path as Path, target.name); + return target; +} + +function generate(options: ResolverOptions): Source { + return (context: SchematicContext) => + apply(url(join('./files' as Path, options.language)), [ + options.spec ? noop() : filter(path => !path.endsWith('.spec.ts')), + template({ + ...strings, + ...options, + }), + move(options.path), + ])(context); +} + +function addDeclarationToModule(options: ProviderOptions): Rule { + return (tree: Tree) => { + if (options.skipImport !== undefined && options.skipImport) { + return tree; + } + options.module = new ModuleFinder(tree).find({ + name: options.name, + path: options.path as Path, + }); + if (!options.module) { + return tree; + } + const content = tree.read(options.module).toString(); + const declarator: ModuleDeclarator = new ModuleDeclarator(); + tree.overwrite( + options.module, + declarator.declare(content, options as DeclarationOptions), + ); + return tree; + }; +} diff --git a/src/lib/resolver/resolver.schema.d.ts b/src/lib/resolver/resolver.schema.d.ts new file mode 100644 index 000000000..a580947f3 --- /dev/null +++ b/src/lib/resolver/resolver.schema.d.ts @@ -0,0 +1,36 @@ +import { Path } from '@angular-devkit/core'; + +export interface ResolverOptions { + /** + * The name of the resolver. + */ + name: string; + /** + * The path to create the resolver. + */ + path?: string | Path; + /** + * Application language. + */ + language?: string; + /** + * The source root path + */ + sourceRoot?: string; + /** + * Specifies if a spec file is generated. + */ + spec?: boolean; + /** + * Flag to indicate if a directory is created. + */ + flat?: boolean; + /** + * Metadata name affected by declaration insertion. + */ + metadata?: string; + /** + * Nest element type name + */ + type?: string; +} diff --git a/src/lib/resolver/schema.json b/src/lib/resolver/schema.json new file mode 100644 index 000000000..abc5d21cc --- /dev/null +++ b/src/lib/resolver/schema.json @@ -0,0 +1,38 @@ +{ + "$schema": "http://json-schema.org/schema", + "id": "SchematicsNestResolver", + "title": "Nest Resolver Options Schema", + "type": "object", + "properties": { + "name": { + "type": "string", + "description": "The name of the resolver.", + "$default": { + "$source": "argv", + "index": 0 + } + }, + "path": { + "type": "string", + "format": "path", + "description": "The path to create the resolver." + }, + "language": { + "type": "string", + "description": "Nest resolver language (ts/js)." + }, + "sourceRoot": { + "type": "string", + "description": "Nest resolver source root directory." + }, + "flat": { + "default": false, + "description": "Flag to indicate if a directory is created." + }, + "spec": { + "default": true, + "description": "Specifies if a spec file is generated." + } + }, + "required": ["name"] +} diff --git a/src/lib/service/schema.json b/src/lib/service/schema.json index d8e2d13bb..b78689135 100644 --- a/src/lib/service/schema.json +++ b/src/lib/service/schema.json @@ -23,7 +23,7 @@ }, "sourceRoot": { "type": "string", - "description": "Nest decorator source root directory." + "description": "Nest service source root directory." }, "flat": { "default": false,