From dbf50d811b4c2ca165774172491fe946a0b499a1 Mon Sep 17 00:00:00 2001 From: Miguel Ramos Date: Thu, 3 Jan 2019 22:11:08 +0000 Subject: [PATCH 1/3] feat(nestjs): init support for nestjs framework (#65) --- src/app.nest/_files/e2e/app/app.e2e-spec.ts | 33 +++ src/app.nest/_files/e2e/jest-e2e.json | 13 + src/app.nest/_files/jest.json | 13 + src/app.nest/_files/package.json | 15 + .../_files/src/app.controller.spec.ts | 27 ++ src/app.nest/_files/src/app.controller.ts | 12 + src/app.nest/_files/src/app.module.ts | 10 + src/app.nest/_files/src/app.service.ts | 8 + src/app.nest/_files/src/main.ts | 31 +++ src/app.nest/_files/tsconfig.json | 23 ++ src/app.nest/index.ts | 117 ++++++++ src/app.nest/index_spec.ts | 65 +++++ src/app.nest/schema.d.ts | 18 ++ src/app.nest/schema.json | 38 +++ src/collection.json | 5 + src/utils/general.ts | 263 +++++++++++++++--- src/utils/generator.ts | 33 ++- 17 files changed, 676 insertions(+), 48 deletions(-) create mode 100644 src/app.nest/_files/e2e/app/app.e2e-spec.ts create mode 100644 src/app.nest/_files/e2e/jest-e2e.json create mode 100644 src/app.nest/_files/jest.json create mode 100644 src/app.nest/_files/package.json create mode 100644 src/app.nest/_files/src/app.controller.spec.ts create mode 100644 src/app.nest/_files/src/app.controller.ts create mode 100644 src/app.nest/_files/src/app.module.ts create mode 100644 src/app.nest/_files/src/app.service.ts create mode 100644 src/app.nest/_files/src/main.ts create mode 100644 src/app.nest/_files/tsconfig.json create mode 100644 src/app.nest/index.ts create mode 100644 src/app.nest/index_spec.ts create mode 100644 src/app.nest/schema.d.ts create mode 100644 src/app.nest/schema.json diff --git a/src/app.nest/_files/e2e/app/app.e2e-spec.ts b/src/app.nest/_files/e2e/app/app.e2e-spec.ts new file mode 100644 index 00000000..4f3ec20f --- /dev/null +++ b/src/app.nest/_files/e2e/app/app.e2e-spec.ts @@ -0,0 +1,33 @@ +import * as request from 'supertest'; +import { Test } from '@nestjs/testing'; +import { ApplicationModule } from '../../src/app.module'; +import { AppService } from '../../src/app.service'; +import { INestApplication } from '@nestjs/common'; + +describe('Application', () => { + let app: INestApplication; + let appService = { get: () => 'Hello world' }; + + beforeAll(async () => { + const module = await Test.createTestingModule({ + imports: [ApplicationModule] + }) + .overrideProvider(AppService) + .useValue(appService) + .compile(); + + app = module.createNestApplication(); + await app.init(); + }); + + it(`/GET app`, () => { + return request(app.getHttpServer()) + .get('/') + .expect(200) + .expect(appService.get()); + }); + + afterAll(async () => { + await app.close(); + }); +}); diff --git a/src/app.nest/_files/e2e/jest-e2e.json b/src/app.nest/_files/e2e/jest-e2e.json new file mode 100644 index 00000000..83e31c5e --- /dev/null +++ b/src/app.nest/_files/e2e/jest-e2e.json @@ -0,0 +1,13 @@ +{ + "moduleFileExtensions": ["ts", "tsx", "js", "json"], + "transform": { + "^.+\\.tsx?$": "../../../node_modules/ts-jest/preprocessor.js" + }, + "testRegex": "/e2e/.*\\.(e2e-test|e2e-spec).(ts|tsx|js)$", + "collectCoverageFrom": [ + "src/**/*.{js,jsx,tsx,ts}", + "!**/node_modules/**", + "!**/vendor/**" + ], + "coverageReporters": ["json", "lcov"] +} diff --git a/src/app.nest/_files/jest.json b/src/app.nest/_files/jest.json new file mode 100644 index 00000000..62354570 --- /dev/null +++ b/src/app.nest/_files/jest.json @@ -0,0 +1,13 @@ +{ + "moduleFileExtensions": ["ts", "tsx", "js", "json"], + "transform": { + "^.+\\.tsx?$": "../../node_modules/ts-jest/preprocessor.js" + }, + "testRegex": "/src/.*\\.(test|spec).(ts|tsx|js)$", + "collectCoverageFrom": [ + "src/**/*.{js,jsx,tsx,ts}", + "!**/node_modules/**", + "!**/vendor/**" + ], + "coverageReporters": ["json", "lcov"] +} diff --git a/src/app.nest/_files/package.json b/src/app.nest/_files/package.json new file mode 100644 index 00000000..f2012335 --- /dev/null +++ b/src/app.nest/_files/package.json @@ -0,0 +1,15 @@ +{ + "name": "nest-<%= utils.sanitize(name) %>", + "version": "1.0.0", + "description": "<%= utils.classify(name) %> description.", + "license": "MIT", + "main": "src/main.ts", + "author": { + "name": "Your name", + "email": "name@company.com" + }, + "homepage": "https://nstudio.io/xplat", + "repository": { + "url": "https://github.com/nstudio/xplat" + } +} diff --git a/src/app.nest/_files/src/app.controller.spec.ts b/src/app.nest/_files/src/app.controller.spec.ts new file mode 100644 index 00000000..1e81cf81 --- /dev/null +++ b/src/app.nest/_files/src/app.controller.spec.ts @@ -0,0 +1,27 @@ +import { Test } from "@nestjs/testing"; +import { AppController } from "./app.controller"; +import { AppService } from "./app.service"; + +describe("App controller", () => { + let appController: AppController; + let appService: AppService; + + beforeEach(async () => { + const module = await Test.createTestingModule({ + controllers: [AppController], + providers: [AppService] + }).compile(); + + appService = module.get(AppService); + appController = module.get(AppController); + }); + + describe("hello world", () => { + it("should get response", async () => { + const result = "Hello world"; + jest.spyOn(appService, "get").mockImplementation(() => result); + + expect(await appController.root()).toBe(result); + }); + }); +}); diff --git a/src/app.nest/_files/src/app.controller.ts b/src/app.nest/_files/src/app.controller.ts new file mode 100644 index 00000000..7840c574 --- /dev/null +++ b/src/app.nest/_files/src/app.controller.ts @@ -0,0 +1,12 @@ +import { Get, Controller } from "@nestjs/common"; +import { AppService } from "./app.service"; + +@Controller() +export class AppController { + constructor(private readonly appService: AppService) {} + + @Get() + root(): string { + return this.appService.get(); + } +} diff --git a/src/app.nest/_files/src/app.module.ts b/src/app.nest/_files/src/app.module.ts new file mode 100644 index 00000000..facdda47 --- /dev/null +++ b/src/app.nest/_files/src/app.module.ts @@ -0,0 +1,10 @@ +import { Module } from "@nestjs/common"; +import { AppController } from "./app.controller"; +import { AppService } from "./app.service"; + +@Module({ + imports: [], + controllers: [AppController], + providers: [AppService] +}) +export class ApplicationModule {} diff --git a/src/app.nest/_files/src/app.service.ts b/src/app.nest/_files/src/app.service.ts new file mode 100644 index 00000000..c53ff546 --- /dev/null +++ b/src/app.nest/_files/src/app.service.ts @@ -0,0 +1,8 @@ +import { Injectable } from "@nestjs/common"; + +@Injectable() +export class AppService { + get() { + return "Hello world!"; + } +} diff --git a/src/app.nest/_files/src/main.ts b/src/app.nest/_files/src/main.ts new file mode 100644 index 00000000..ded84ec1 --- /dev/null +++ b/src/app.nest/_files/src/main.ts @@ -0,0 +1,31 @@ +import { NestFactory } from "@nestjs/core"; +import { ApplicationModule } from "./app.module"; + +import * as helmet from "helmet"; +// import * as csurf from "csurf"; +import * as rateLimit from "express-rate-limit"; +import * as compression from "compression"; + +declare const module: any; + +async function bootstrap() { + const app = await NestFactory.create(ApplicationModule, { cors: true }); + + app.use(helmet()); + //app.use(csurf()); + app.use( + rateLimit({ + windowMs: 15 * 60 * 1000, // 15 minutes + max: 100 // limit each IP to 100 requests per windowMs + }) + ); + app.use(compression()); + + await app.listen(9000); + + if (module.hot) { + module.hot.accept(); + module.hot.dispose(() => app.close()); + } +} +bootstrap(); diff --git a/src/app.nest/_files/tsconfig.json b/src/app.nest/_files/tsconfig.json new file mode 100644 index 00000000..8df14c5c --- /dev/null +++ b/src/app.nest/_files/tsconfig.json @@ -0,0 +1,23 @@ +{ + "compilerOptions": { + "module": "commonjs", + "declaration": false, + "noImplicitAny": false, + "removeComments": true, + "noLib": false, + "emitDecoratorMetadata": true, + "experimentalDecorators": true, + "target": "es6", + "sourceMap": true, + "allowJs": true, + "outDir": "../../dist/apps/nest-<%= utils.sanitize(name) %>", + "typeRoots": ["../../node_modules/@types"], + "types": ["node", "jest"] + }, + "include": ["src/**/*"], + "exclude": [ + "../../node_modules", + "**/*.spec.ts", + "node_modules/@types/jasmine/**/*" + ] +} diff --git a/src/app.nest/index.ts b/src/app.nest/index.ts new file mode 100644 index 00000000..0a806b2e --- /dev/null +++ b/src/app.nest/index.ts @@ -0,0 +1,117 @@ +import { Schema as ApplicationOptions } from "./schema"; +import { + SchematicsException, + chain, + Tree, + SchematicContext, + branchAndMerge, + mergeWith, + apply, + url, + template, + move, + Rule, + noop +} from "@angular-devkit/schematics"; +import { NodePackageInstallTask } from "@angular-devkit/schematics/tasks"; +import { + stringUtils, + getNpmScope, + getPrefix, + addRootDeps, + getJsonFromFile, + updatePackageScripts, + addPostinstallers, + formatFiles, + updateNxProjects +} from "../utils"; + +export default function(options: ApplicationOptions) { + if (!options.name) { + throw new SchematicsException( + `Missing name argument. Provide a name for your Nest app. Example: ng g app.nest sample` + ); + } + + const appPath = `nest-${options.name}`; + + return chain([ + // create app files + (tree: Tree, context: SchematicContext) => + addAppFiles(options, appPath)(tree, context), + // add root package dependencies + (tree: Tree) => addRootDeps(tree, { nest: true }), + // add npm scripts + (tree: Tree) => { + const packageConfig = getJsonFromFile(tree, "package.json"); + const scripts = packageConfig.scripts || {}; + + scripts[`serve.nest.${options.name}`] = `ts-node -P apps/nest-${ + options.name + }/tsconfig.json apps/nest-${options.name}/src/main.ts`; + scripts[`start.nest.${options.name}`] = `npm-run-all -p serve.nest.${ + options.name + }`; + scripts[`build.nest.${options.name}`] = `tsc -p apps/nest-${ + options.name + }`; + scripts[`test.nest.${options.name}`] = `jest --config=apps/nest-${ + options.name + }/jest.json`; + scripts[ + `test.nest.${options.name}.coverage` + ] = `jest --config=apps/nest-${ + options.name + }/jest.json --coverage --coverageDirectory=coverage`; + scripts[`test.nest.${options.name}.watch`] = `jest --config=apps/nest-${ + options.name + }/jest.json --watch`; + scripts[`test.nest.${options.name}.e2e`] = `jest --config=apps/nest-${ + options.name + }/e2e/jest-e2e.json --forceExit`; + scripts[ + `test.nest.${options.name}.e2e.watch` + ] = `jest --config=apps/nest-${options.name}/e2e/jest-e2e.json --watch`; + + return updatePackageScripts(tree, scripts); + }, + // nx.json + (tree: Tree) => { + const projects = {}; + projects[`nest-${options.name}`] = { + tags: [] + }; + return updateNxProjects(tree, projects); + }, + addInstall, + addPostinstallers(), + options.skipFormat ? noop() : formatFiles(options) + ]); +} + +function addInstall(host: Tree, context: SchematicContext) { + context.addTask(new NodePackageInstallTask()); + return host; +} + +function addAppFiles( + options: ApplicationOptions, + appPath: string, + sample: string = "" +): Rule { + sample = ""; + return branchAndMerge( + mergeWith( + apply(url(`./_${sample}files`), [ + template({ + ...(options as any), + utils: stringUtils, + npmScope: getNpmScope(), + prefix: getPrefix(), + dot: "." + }), + move(`apps/${appPath}`) + ]) + ) + ); +} diff --git a/src/app.nest/index_spec.ts b/src/app.nest/index_spec.ts new file mode 100644 index 00000000..34f08655 --- /dev/null +++ b/src/app.nest/index_spec.ts @@ -0,0 +1,65 @@ +import { Tree, VirtualTree } from "@angular-devkit/schematics"; +import { Schema as ApplicationOptions } from "./schema"; +import { SchematicTestRunner } from "@angular-devkit/schematics/testing"; + +import * as path from "path"; +import { createEmptyWorkspace, getFileContent } from "../utils"; + +describe("app.nest schematic", () => { + const schematicRunner = new SchematicTestRunner( + "@nstudio/schematics", + path.join(__dirname, "../collection.json") + ); + const defaultOptions: ApplicationOptions = { + name: "foo", + npmScope: "testing" + }; + + let appTree: Tree; + + beforeEach(() => { + appTree = new VirtualTree(); + appTree = createEmptyWorkspace(appTree); + }); + + it("should create all files for node app", () => { + const options: ApplicationOptions = { ...defaultOptions }; + const tree = schematicRunner.runSchematic("app.nest", options, appTree); + const files = tree.files; + + expect( + files.indexOf("/apps/nest-foo/src/main.ts") + ).toBeGreaterThanOrEqual(0); + expect( + files.indexOf("/apps/nest-foo/src/app.service.ts") + ).toBeGreaterThanOrEqual(0); + expect( + files.indexOf("/apps/nest-foo/src/app.module.ts") + ).toBeGreaterThanOrEqual(0); + expect( + files.indexOf("/apps/nest-foo/src/app.controller.ts") + ).toBeGreaterThanOrEqual(0); + + let checkPath = "/apps/nest-foo/package.json"; + expect(files.indexOf(checkPath)).toBeGreaterThanOrEqual(0); + + let checkFile = getFileContent(tree, checkPath); + expect(checkFile.indexOf(`"name": "nest-foo"`)).toBeGreaterThanOrEqual(0); + + expect( + files.indexOf("/tools/electron/postinstall.js") + ).toBeGreaterThanOrEqual(0); + expect(files.indexOf("/tools/web/postinstall.js")).toBeGreaterThanOrEqual( + 0 + ); + + checkPath = "/package.json"; + expect(files.indexOf(checkPath)).toBeGreaterThanOrEqual(0); + + checkFile = getFileContent(tree, checkPath); + + const packageData: any = JSON.parse(checkFile); + expect(packageData.scripts["serve.nest.foo"]).toBeDefined(); + expect(packageData.scripts["start.nest.foo"]).toBeDefined(); + }); +}); diff --git a/src/app.nest/schema.d.ts b/src/app.nest/schema.d.ts new file mode 100644 index 00000000..e06b04a3 --- /dev/null +++ b/src/app.nest/schema.d.ts @@ -0,0 +1,18 @@ +export interface Schema { + /** + * The name of the app. + */ + name: string; + /** + * npm scope - auto detected from nx.json but can specify your own name + */ + npmScope?: string; + /** + * Skip installing dependencies + */ + skipInstall?: boolean; + /** + * Skip formatting files + */ + skipFormat?: boolean; +} diff --git a/src/app.nest/schema.json b/src/app.nest/schema.json new file mode 100644 index 00000000..6efba43c --- /dev/null +++ b/src/app.nest/schema.json @@ -0,0 +1,38 @@ +{ + "$schema": "http://json-schema.org/schema", + "id": "app.nest", + "title": "Nest App Options Schema", + "type": "object", + "properties": { + "name": { + "type": "string", + "description": "The name of the app.", + "alias": "n", + "$default": { + "$source": "argv", + "index": 0 + } + }, + "prefix": { + "type": "string", + "description": "The prefix to apply to generated selectors.", + "alias": "p" + }, + "npmScope": { + "type": "string", + "description": "The npm scope to use.", + "alias": "wn" + }, + "skipInstall": { + "type": "boolean", + "description": "Skip installing dependencies.", + "default": false + }, + "skipFormat": { + "description": "Skip formatting files", + "type": "boolean", + "default": false + } + }, + "required": [] +} diff --git a/src/collection.json b/src/collection.json index e28b5b89..b5a012ce 100644 --- a/src/collection.json +++ b/src/collection.json @@ -24,6 +24,11 @@ "schema": "./app.electron/schema.json", "description": "Create an Electron app." }, + "app.nest": { + "factory": "./app.nest", + "schema": "./app.nest/schema.json", + "description": "Create an Nest node app." + }, "mode": { "factory": "./mode", "schema": "./mode/schema.json", diff --git a/src/utils/general.ts b/src/utils/general.ts index 6cd3c523..393f12a3 100644 --- a/src/utils/general.ts +++ b/src/utils/general.ts @@ -21,16 +21,29 @@ import * as ts from "typescript"; const util = require('util'); const xml2js = require('xml2js'); -export const supportedPlatforms = ["web", "nativescript", "ionic", "electron"]; +export const supportedPlatforms = [ + "web", + "nativescript", + "ionic", + "electron", + "nest" +]; export interface ITargetPlatforms { web?: boolean; nativescript?: boolean; ionic?: boolean; electron?: boolean; ssr?: boolean; + nest?: boolean; } -export type IDevMode = "web" | "nativescript" | "ionic" | "electron" | "fullstack"; +export type IDevMode = + | "web" + | "nativescript" + | "ionic" + | "electron" + | "nest" + | "fullstack"; export interface NodeDependency { name: string; @@ -168,7 +181,10 @@ export function prerun(prefixArg?: string, init?: boolean) { export function sanitizeCommaDelimitedArg(input: string): Array { if (input) { - return input.split(",").filter(i => !!i).map(i => i.trim().toLowerCase()); + return input + .split(",") + .filter(i => !!i) + .map(i => i.trim().toLowerCase()); } return []; } @@ -199,12 +215,15 @@ export function addRootDeps( }; deps.push(dep); - dep = { - name: `@${getNpmScope()}/scss`, - version: "file:libs/scss", - type: "dependency" - }; - deps.push(dep); + if (!targetPlatforms.nest) { + // if just setting up workspace with nest, we don't need frontend scss + dep = { + name: `@${getNpmScope()}/scss`, + version: "file:libs/scss", + type: "dependency" + }; + deps.push(dep); + } dep = { name: "reflect-metadata", @@ -268,35 +287,35 @@ export function addRootDeps( type: "dependency" }; deps.push(dep); - + dep = { name: "@ionic-native/splash-screen", version: "^5.0.0-beta.14", type: "dependency" }; deps.push(dep); - + dep = { name: "@ionic-native/status-bar", version: "^5.0.0-beta.14", type: "dependency" }; deps.push(dep); - + dep = { name: "@ionic/angular", version: "^4.0.0-beta.3", type: "dependency" }; deps.push(dep); - + dep = { name: "@ionic/ng-toolkit", version: "~1.0.0", type: "dependency" }; deps.push(dep); - + dep = { name: "@ionic/schematics-angular", version: "~1.0.0", @@ -308,72 +327,222 @@ export function addRootDeps( /** ELECTRON */ if (targetPlatforms.electron) { dep = { - name: 'electron', - version: '2.0.8', - type: 'devDependency' + name: "electron", + version: "2.0.8", + type: "devDependency" }; deps.push(dep); dep = { - name: 'electron-builder', - version: '20.28.4', - type: 'devDependency' + name: "electron-builder", + version: "20.28.4", + type: "devDependency" }; deps.push(dep); dep = { - name: 'electron-installer-dmg', - version: '1.0.0', - type: 'devDependency' + name: "electron-installer-dmg", + version: "1.0.0", + type: "devDependency" }; deps.push(dep); dep = { - name: 'electron-packager', - version: '12.1.0', - type: 'devDependency' + name: "electron-packager", + version: "12.1.0", + type: "devDependency" }; deps.push(dep); dep = { - name: 'electron-reload', - version: '1.2.5', - type: 'devDependency' + name: "electron-reload", + version: "1.2.5", + type: "devDependency" }; deps.push(dep); dep = { - name: 'electron-store', - version: '2.0.0', - type: 'devDependency' + name: "electron-store", + version: "2.0.0", + type: "devDependency" }; deps.push(dep); dep = { - name: 'electron-updater', - version: '3.1.2', - type: 'devDependency' + name: "electron-updater", + version: "3.1.2", + type: "devDependency" }; deps.push(dep); dep = { - name: 'npm-run-all', - version: '4.1.3', - type: 'devDependency' + name: "npm-run-all", + version: "^4.1.5", + type: "devDependency" }; deps.push(dep); dep = { - name: 'npx', - version: '10.2.0', - type: 'devDependency' + name: "npx", + version: "10.2.0", + type: "devDependency" }; deps.push(dep); dep = { - name: 'wait-on', - version: '2.1.0', - type: 'devDependency' + name: "wait-on", + version: "2.1.0", + type: "devDependency" + }; + deps.push(dep); + } + + /** NESTJS */ + if (targetPlatforms.nest) { + dep = { + name: "@nestjs/common", + version: "^5.3.0", + type: "dependency" + }; + deps.push(dep); + + dep = { + name: "@nestjs/core", + version: "^5.3.0", + type: "dependency" + }; + deps.push(dep); + + dep = { + name: "@nestjs/testing", + version: "^5.3.0", + type: "dependency" + }; + deps.push(dep); + + dep = { + name: "@nestjs/typeorm", + version: "^5.2.2", + type: "dependency" + }; + deps.push(dep); + + dep = { + name: "@nestjs/websockets", + version: "^5.5.0", + type: "dependency" + }; + deps.push(dep); + + dep = { + name: "@nestjs/microservices", + version: "^5.5.0", + type: "dependency" + }; + deps.push(dep); + + dep = { + name: "typeorm", + version: "^0.2.9", + type: "dependency" + }; + deps.push(dep); + + dep = { + name: "cache-manager", + version: "^2.9.0", + type: "dependency" + }; + deps.push(dep); + + dep = { + name: "helmet", + version: "^3.15.0", + type: "dependency" + }; + deps.push(dep); + + dep = { + name: "csurf", + version: "^1.9.0", + type: "dependency" + }; + deps.push(dep); + + dep = { + name: "express-rate-limit", + version: "^3.3.2", + type: "dependency" + }; + deps.push(dep); + + dep = { + name: "dotenv", + version: "^6.2.0", + type: "dependency" + }; + deps.push(dep); + + dep = { + name: "compression", + version: "^1.7.3", + type: "dependency" + }; + deps.push(dep); + + dep = { + name: "@types/node", + version: "^9.3.0", + type: "devDependency" + }; + deps.push(dep); + + dep = { + name: "ts-loader", + version: "^4.0.0", + type: "devDependency" + }; + deps.push(dep); + + dep = { + name: "ts-node", + version: "^7.0.0", + type: "devDependency" + }; + deps.push(dep); + + dep = { + name: "npm-run-all", + version: "^4.1.5", + type: "devDependency" + }; + deps.push(dep); + + dep = { + name: "@types/jest", + version: "^20.0.8", + type: "devDependency" + }; + deps.push(dep); + + dep = { + name: "jest", + version: "^20.0.4", + type: "devDependency" + }; + deps.push(dep); + + dep = { + name: "ts-jest", + version: "^20.0.14", + type: "devDependency" + }; + deps.push(dep); + + dep = { + name: "supertest", + version: "^3.0.0", + type: "devDependency" }; deps.push(dep); } @@ -604,7 +773,8 @@ fs.readFile(f_angular, 'utf8', function (err, data) { fs.writeFile(f_angular, result, 'utf8', function (err) { if (err) return console.log(err); }); -});`); +});` + ); } const postinstallElectron = "/tools/electron/postinstall.js"; if (!tree.exists(postinstallElectron)) { @@ -626,7 +796,8 @@ fs.readFile(f_angular, 'utf8', function (err, data) { fs.writeFile(f_angular, result, 'utf8', function (err) { if (err) return console.log(err); }); -});`); +});` + ); } return tree; }; diff --git a/src/utils/generator.ts b/src/utils/generator.ts index e877b6f6..a029d762 100644 --- a/src/utils/generator.ts +++ b/src/utils/generator.ts @@ -69,14 +69,17 @@ export function generate(type: IGenerateType, options) { for (const name of projects) { const nameParts = name.split("-"); const platPrefix = nameParts[0]; - const platSuffix = nameParts[nameParts.length-1]; + const platSuffix = nameParts[nameParts.length - 1]; if ( supportedPlatforms.includes(platPrefix) && !platforms.includes(platPrefix) ) { // if project name is prefixed with supported platform and not already added platforms.push(platPrefix); - } else if (supportedPlatforms.includes(platSuffix) && !platforms.includes(platSuffix)) { + } else if ( + supportedPlatforms.includes(platSuffix) && + !platforms.includes(platSuffix) + ) { // if project name is suffixed with supported platform and not already added platforms.push(platSuffix); } @@ -305,6 +308,32 @@ export function generate(type: IGenerateType, options) { !options.projects && targetPlatforms.electron ? adjustModule(type, options, "xplat/electron")(tree, context) : noop()(tree, context), + /** + * NEST + **/ + // add for nest + (tree: Tree, context: SchematicContext) => + !options.projects && targetPlatforms.nest + ? addToFeature(type, options, "xplat/nest", tree)(tree, context) + : noop()(tree, context), + // adjust nest barrel + (tree: Tree, context: SchematicContext) => + !options.projects && targetPlatforms.nest + ? adjustBarrel(type, options, "xplat/nest")(tree, context) + : noop()(tree, context), + // add index barrel if needed + (tree: Tree, context: SchematicContext) => + options.needsIndex + ? addToFeature(type, options, "xplat/nest", tree, "_index")( + tree, + context + ) + : noop()(tree, context), + // adjust feature module metadata if needed + (tree: Tree, context: SchematicContext) => + !options.projects && targetPlatforms.nest + ? adjustModule(type, options, "xplat/nest")(tree, context) + : noop()(tree, context), // project handling ...projectChains, From 3fe134660a30819ef9f2b29723fd37921cc6c530 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Morten=20Anton=20Bach=20Sj=C3=B8gren?= Date: Fri, 4 Jan 2019 11:07:37 +0100 Subject: [PATCH 2/3] fix: Inconsistent named feature arrays. --- src/directive/_index_files/index.ts | 6 +++++- src/pipe/_index_files/index.ts | 6 +++++- src/service/_index_files/index.ts | 4 +++- src/utils/generator.ts | 2 +- 4 files changed, 14 insertions(+), 4 deletions(-) diff --git a/src/directive/_index_files/index.ts b/src/directive/_index_files/index.ts index f4192428..62ecc1f1 100644 --- a/src/directive/_index_files/index.ts +++ b/src/directive/_index_files/index.ts @@ -1,3 +1,7 @@ import { <%= utils.classify(name) %>Directive } from './<%= name %>.directive'; -export const <%= utils.sanitize(name).toUpperCase() %>_DIRECTIVES = [<%= utils.classify(name) %>Directive]; +export const <%= utils.sanitize(feature).toUpperCase() %>_DIRECTIVES = [ + <%= utils.classify(name) %>Directive +]; + +export * from './<%= name %>.directive'; diff --git a/src/pipe/_index_files/index.ts b/src/pipe/_index_files/index.ts index 307236e6..563fad92 100644 --- a/src/pipe/_index_files/index.ts +++ b/src/pipe/_index_files/index.ts @@ -1,3 +1,7 @@ import { <%= utils.classify(name) %>Pipe } from './<%= name %>.pipe'; -export const <%= utils.sanitize(name).toUpperCase() %>_PIPES = [<%= utils.classify(name) %>Pipe]; +export const <%= utils.sanitize(feature).toUpperCase() %>_PIPES = [ + <%= utils.classify(name) %>Pipe +]; + +export * from './<%= name %>.pipe'; diff --git a/src/service/_index_files/index.ts b/src/service/_index_files/index.ts index b8bfbd45..f3590f35 100644 --- a/src/service/_index_files/index.ts +++ b/src/service/_index_files/index.ts @@ -1,5 +1,7 @@ import { <%= utils.classify(name) %>Service } from './<%= name %>.service'; -export const <%= utils.sanitize(name).toUpperCase() %>_PROVIDERS = [<%= utils.classify(name) %>Service]; +export const <%= utils.sanitize(feature).toUpperCase() %>_PROVIDERS = [ + <%= utils.classify(name) %>Service +]; export * from './<%= name %>.service'; diff --git a/src/utils/generator.ts b/src/utils/generator.ts index a029d762..02205267 100644 --- a/src/utils/generator.ts +++ b/src/utils/generator.ts @@ -546,7 +546,7 @@ export function adjustBarrelIndex( } } - if (type === "component" || type === "service") { + if (type === "component" || type === "service" || type === "directive" || type === 'pipe') { // export symbol from barrel if ((isBase || importIfSubFolder) && options.subFolder) { changes.push( From 53482114f6f67763b33918a16efc06a301227884 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Morten=20Anton=20Bach=20Sj=C3=B8gren?= Date: Mon, 7 Jan 2019 14:19:09 +0100 Subject: [PATCH 3/3] test: pipes and directive should work with and without --feature name --- src/directive/_index_files/index.ts | 10 ++++---- src/directive/index_spec.ts | 33 +++++++++++++++++++++---- src/pipe/_index_files/index.ts | 6 ++++- src/pipe/index_spec.ts | 37 +++++++++++++++++++++++++++-- src/utils/generator.ts | 2 +- 5 files changed, 76 insertions(+), 12 deletions(-) diff --git a/src/directive/_index_files/index.ts b/src/directive/_index_files/index.ts index 62ecc1f1..75d7b1f7 100644 --- a/src/directive/_index_files/index.ts +++ b/src/directive/_index_files/index.ts @@ -1,7 +1,9 @@ import { <%= utils.classify(name) %>Directive } from './<%= name %>.directive'; -export const <%= utils.sanitize(feature).toUpperCase() %>_DIRECTIVES = [ - <%= utils.classify(name) %>Directive +<% if (feature) { %> + export const <%= utils.sanitize(feature).toUpperCase() %>_DIRECTIVES = [ +<% } else { %> +export const DIRECTIVES = [ +<% } %> + <%= utils.classify(name) %>Directive ]; - -export * from './<%= name %>.directive'; diff --git a/src/directive/index_spec.ts b/src/directive/index_spec.ts index 88c9e9cd..76de8389 100644 --- a/src/directive/index_spec.ts +++ b/src/directive/index_spec.ts @@ -36,9 +36,11 @@ describe('directive schematic', () => { name: 'foo', platforms: 'nativescript,web' }, tree); - const options: GenerateOptions = { ...defaultOptions }; + let options: GenerateOptions = { ...defaultOptions }; + + // Directives without the feature option are added to the ui-feature tree = schematicRunner.runSchematic('directive', options, tree); - const files = tree.files; + let files = tree.files; // console.log(files.slice(91,files.length)); // component @@ -52,9 +54,32 @@ describe('directive schematic', () => { let modulePath = '/libs/features/ui/ui.module.ts'; let moduleContent = getFileContent(tree, modulePath); + // console.log(modulePath + ':'); // console.log(moduleContent); expect(moduleContent.indexOf(`...UI_DIRECTIVES`)).toBeGreaterThanOrEqual(0); + + // Directives added to the foo-feature + options = { ...defaultOptions, feature: 'foo' }; + tree = schematicRunner.runSchematic('directive', options, tree); + files = tree.files; + // console.log(files.slice(91,files.length)); + + // component + expect(files.indexOf('/libs/features/foo/directives/active-link.directive.ts')).toBeGreaterThanOrEqual(0); + + // file content + content = getFileContent(tree, '/libs/features/foo/directives/active-link.directive.ts'); + // console.log(content); + expect(content.indexOf(`@Directive({`)).toBeGreaterThanOrEqual(0); + expect(content.indexOf(`selector: '[active-link]'`)).toBeGreaterThanOrEqual(0); + + modulePath = '/libs/features/foo/foo.module.ts'; + moduleContent = getFileContent(tree, modulePath); + + // console.log(modulePath + ':'); + // console.log(moduleContent); + expect(moduleContent.indexOf(`...FOO_DIRECTIVES`)).toBeGreaterThanOrEqual(0); }); it('should create directive for specified projects only', () => { @@ -72,7 +97,7 @@ describe('directive schematic', () => { projects: 'nativescript-viewer,web-viewer,ionic-viewer', onlyProject: true }, tree); - const options: GenerateOptions = { + const options: GenerateOptions = { name: 'active-link', feature: 'foo', projects: 'nativescript-viewer,web-viewer,ionic-viewer' @@ -111,4 +136,4 @@ describe('directive schematic', () => { // console.log(barrelIndex); expect(index.indexOf(`ActiveLinkDirective`)).toBeGreaterThanOrEqual(0); }); -}); +}); diff --git a/src/pipe/_index_files/index.ts b/src/pipe/_index_files/index.ts index 563fad92..953dea44 100644 --- a/src/pipe/_index_files/index.ts +++ b/src/pipe/_index_files/index.ts @@ -1,7 +1,11 @@ import { <%= utils.classify(name) %>Pipe } from './<%= name %>.pipe'; +<% if (feature) { %> export const <%= utils.sanitize(feature).toUpperCase() %>_PIPES = [ - <%= utils.classify(name) %>Pipe +<% } else { %> +export const PIPES = [ +<% } %> + <%= utils.classify(name) %>Pipe ]; export * from './<%= name %>.pipe'; diff --git a/src/pipe/index_spec.ts b/src/pipe/index_spec.ts index 232a4b97..98d1ce89 100644 --- a/src/pipe/index_spec.ts +++ b/src/pipe/index_spec.ts @@ -50,7 +50,7 @@ describe("pipe schematic", () => { ); let options: GenerateOptions = { ...defaultOptions }; tree = schematicRunner.runSchematic("pipe", options, tree); - const files = tree.files; + let files = tree.files; // console.log(files.slice(91,files.length)); // component @@ -66,6 +66,39 @@ describe("pipe schematic", () => { // console.log(content); expect(content.indexOf(`@Pipe({`)).toBeGreaterThanOrEqual(0); expect(content.indexOf(`name: 'truncate'`)).toBeGreaterThanOrEqual(0); + + let modulePath = '/libs/features/ui/ui.module.ts'; + let moduleContent = getFileContent(tree, modulePath); + + // console.log(modulePath + ':'); + // console.log(moduleContent); + expect(moduleContent.indexOf(`...PIPES`)).toBeGreaterThanOrEqual(0); + + options = { ...defaultOptions, feature: 'foo' }; + tree = schematicRunner.runSchematic("pipe", options, tree); + files = tree.files; + // console.log(files.slice(91,files.length)); + + // component + expect( + files.indexOf("/libs/features/foo/pipes/truncate.pipe.ts") + ).toBeGreaterThanOrEqual(0); + + // file content + content = getFileContent( + tree, + "/libs/features/foo/pipes/truncate.pipe.ts" + ); + // console.log(content); + expect(content.indexOf(`@Pipe({`)).toBeGreaterThanOrEqual(0); + expect(content.indexOf(`name: 'truncate'`)).toBeGreaterThanOrEqual(0); + + modulePath = '/libs/features/foo/foo.module.ts'; + moduleContent = getFileContent(tree, modulePath); + + // console.log(modulePath + ':'); + // console.log(moduleContent); + expect(moduleContent.indexOf(`...FOO_PIPES`)).toBeGreaterThanOrEqual(0); }); it("should create pipe in libs and handle camel case properly", () => { @@ -94,7 +127,7 @@ describe("pipe schematic", () => { }, tree ); - let options: GenerateOptions = { + let options: GenerateOptions = { ...defaultOptions, name: 'test-with-dashes' }; diff --git a/src/utils/generator.ts b/src/utils/generator.ts index 02205267..931cf2c8 100644 --- a/src/utils/generator.ts +++ b/src/utils/generator.ts @@ -546,7 +546,7 @@ export function adjustBarrelIndex( } } - if (type === "component" || type === "service" || type === "directive" || type === 'pipe') { + if (type === "component" || type === "service" || type === 'pipe') { // export symbol from barrel if ((isBase || importIfSubFolder) && options.subFolder) { changes.push(