From 86139f6728789d1fc0e97b7f88ea392156ba49f8 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Miroslav=20Bajto=C5=A1?= Date: Tue, 6 Feb 2018 13:42:37 +0100 Subject: [PATCH 01/20] fix(example-getting-started): remove juggler warning Explicitly cast path parameter "id" from string to number because the REST transport does not implement parameter coercion yet. See https://github.com/strongloop/loopback-next/issues/750 This change removes the following warnings from `npm test` output: WARNING: id property cannot be changed from 3 to 3 for model:Todo in 'before save' operation hook WARNING: id property cannot be changed from 3 to 3 for model:Todo in 'loaded' operation hook --- .../src/controllers/todo.controller.ts | 12 ++++++++++++ 1 file changed, 12 insertions(+) diff --git a/packages/example-getting-started/src/controllers/todo.controller.ts b/packages/example-getting-started/src/controllers/todo.controller.ts index aad4865d16cf..2e58030c68fa 100644 --- a/packages/example-getting-started/src/controllers/todo.controller.ts +++ b/packages/example-getting-started/src/controllers/todo.controller.ts @@ -43,6 +43,12 @@ export class TodoController { @param.body('todo', TodoSchema) todo: Todo, ): Promise { + // REST adapter does not coerce parameter values coming from string sources + // like path & query. As a workaround, we have to cast the value to a number + // ourselves. + // See https://github.com/strongloop/loopback-next/issues/750 + id = +id; + return await this.todoRepo.replaceById(id, todo); } @@ -52,6 +58,12 @@ export class TodoController { @param.body('todo', TodoSchema) todo: Todo, ): Promise { + // REST adapter does not coerce parameter values coming from string sources + // like path & query. As a workaround, we have to cast the value to a number + // ourselves. + // See https://github.com/strongloop/loopback-next/issues/750 + id = +id; + return await this.todoRepo.updateById(id, todo); } From c2e7cf60b5781fcdf7964500e1ea61875744369b Mon Sep 17 00:00:00 2001 From: Taranveer Virk Date: Thu, 8 Feb 2018 10:07:41 -0500 Subject: [PATCH 02/20] chore: cleanup of top level files (#976) --- .nycrc | 1 - .prettierignore | 1 - .vscode/launch.json | 2 +- .vscode/settings.json | 1 - CHANGELOG.md | 6 ++++-- appveyor.yml | 1 - 6 files changed, 5 insertions(+), 7 deletions(-) diff --git a/.nycrc b/.nycrc index 4a69e8c7b863..4b0f66920d54 100644 --- a/.nycrc +++ b/.nycrc @@ -1,6 +1,5 @@ { "include": [ - "packages/example-codehub/src/**", "packages/*/dist*/*" ], "extension": [ diff --git a/.prettierignore b/.prettierignore index 6863f7cc22dc..2d3791d21ef7 100644 --- a/.prettierignore +++ b/.prettierignore @@ -1,5 +1,4 @@ packages/*/dist -packages/*/dist6 packages/*/api-docs packages/cli/generators/*/templates package.json diff --git a/.vscode/launch.json b/.vscode/launch.json index 830fff0b3778..8f47306af72a 100644 --- a/.vscode/launch.json +++ b/.vscode/launch.json @@ -8,7 +8,7 @@ "type": "node", "request": "launch", "name": "Run Mocha tests", - "program": "${workspaceRoot}/node_modules/.bin/_mocha", + "program": "${workspaceRoot}/packages/build/node_modules/.bin/_mocha", "cwd": "${workspaceRoot}", "args": [ "--opts", diff --git a/.vscode/settings.json b/.vscode/settings.json index ab57e265400a..8c6ff114992a 100644 --- a/.vscode/settings.json +++ b/.vscode/settings.json @@ -14,7 +14,6 @@ ".nyc_output": true, "coverage": true, "packages/*/dist": true, - "packages/*/dist6": true, "packages/*/api-docs": true }, "files.insertFinalNewline": true, diff --git a/CHANGELOG.md b/CHANGELOG.md index f6bd0b05468b..7b27e634adc4 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,5 @@ -# v4.0.0-alpha.1 +# CHANGELOG -- Initial published version \ No newline at end of file +CHANGELOG's are generated independently for each package and can be found at: + +`packages/*/CHANGELOG.md` diff --git a/appveyor.yml b/appveyor.yml index 6a07fc0d5a8b..2e737c78bd18 100644 --- a/appveyor.yml +++ b/appveyor.yml @@ -4,7 +4,6 @@ environment: install: - ps: Install-Product node $env:nodejs_version - - npm install - npm run bootstrap test_script: From 7ffc1e5b1602fc59cd4daef988eb0047918588cb Mon Sep 17 00:00:00 2001 From: Raymond Feng Date: Tue, 6 Feb 2018 15:53:39 -0800 Subject: [PATCH 03/20] feat(context): formalize injection metadata as an interface --- packages/context/src/index.ts | 2 +- packages/context/src/inject.ts | 29 +++++++++++++++++---- packages/context/src/resolution-session.ts | 3 ++- packages/context/test/unit/resolver.test.ts | 2 +- 4 files changed, 28 insertions(+), 8 deletions(-) diff --git a/packages/context/src/index.ts b/packages/context/src/index.ts index 9ac21008ea7d..24f11013053f 100644 --- a/packages/context/src/index.ts +++ b/packages/context/src/index.ts @@ -21,7 +21,7 @@ export {Binding, BindingScope, BindingType} from './binding'; export {Context} from './context'; export {ResolutionSession} from './resolution-session'; -export {inject, Setter, Getter, Injection} from './inject'; +export {inject, Setter, Getter, Injection, InjectionMetadata} from './inject'; export {Provider} from './provider'; export {instantiateClass, invokeMethod} from './resolver'; diff --git a/packages/context/src/inject.ts b/packages/context/src/inject.ts index bc5410fe477a..ceba364c293d 100644 --- a/packages/context/src/inject.ts +++ b/packages/context/src/inject.ts @@ -28,6 +28,25 @@ export interface ResolverFunction { ): ValueOrPromise; } +/** + * An object to provide metadata for `@inject` + */ +export interface InjectionMetadata { + /** + * Name of the decorator function, such as `@inject` or `@inject.setter`. + * It's usually set by the decorator implementation. + */ + decorator?: string; + /** + * Control if the dependency is optional, default to false + */ + optional?: boolean; + /** + * Other attributes + */ + [attribute: string]: BoundValue; +} + /** * Descriptor for an injection point */ @@ -39,7 +58,7 @@ export interface Injection { | number; bindingKey: string; // Binding key - metadata?: {[attribute: string]: BoundValue}; // Related metadata + metadata?: InjectionMetadata; // Related metadata resolve?: ResolverFunction; // A custom resolve function } @@ -71,7 +90,7 @@ export interface Injection { */ export function inject( bindingKey: string, - metadata?: Object, + metadata?: InjectionMetadata, resolve?: ResolverFunction, ) { metadata = Object.assign({decorator: '@inject'}, metadata); @@ -162,7 +181,7 @@ export namespace inject { */ export const getter = function injectGetter( bindingKey: string, - metadata?: Object, + metadata?: InjectionMetadata, ) { metadata = Object.assign({decorator: '@inject.getter'}, metadata); return inject(bindingKey, metadata, resolveAsGetter); @@ -183,7 +202,7 @@ export namespace inject { */ export const setter = function injectSetter( bindingKey: string, - metadata?: Object, + metadata?: InjectionMetadata, ) { metadata = Object.assign({decorator: '@inject.setter'}, metadata); return inject(bindingKey, metadata, resolveAsSetter); @@ -205,7 +224,7 @@ export namespace inject { */ export const tag = function injectTag( bindingTag: string | RegExp, - metadata?: Object, + metadata?: InjectionMetadata, ) { metadata = Object.assign( {decorator: '@inject.tag', tag: bindingTag}, diff --git a/packages/context/src/resolution-session.ts b/packages/context/src/resolution-session.ts index 1c30c5101671..2fe5bcc4d6ef 100644 --- a/packages/context/src/resolution-session.ts +++ b/packages/context/src/resolution-session.ts @@ -166,7 +166,8 @@ export class ResolutionSession { return { targetName: name, bindingKey: injection.bindingKey, - metadata: injection.metadata, + // Cast to Object so that we don't have to expose InjectionMetadata + metadata: injection.metadata as Object, }; } diff --git a/packages/context/test/unit/resolver.test.ts b/packages/context/test/unit/resolver.test.ts index a96ad7b03cab..c14e80cc1617 100644 --- a/packages/context/test/unit/resolver.test.ts +++ b/packages/context/test/unit/resolver.test.ts @@ -247,7 +247,7 @@ describe('constructor injection', () => { const context = new Context(); let bindingPath = ''; let resolutionPath = ''; - let decorators: string[] = []; + let decorators: (string | undefined)[] = []; class ZClass { @inject( From c86b0cf717966ccc4f58e305bc0179198576362c Mon Sep 17 00:00:00 2001 From: Raymond Feng Date: Tue, 6 Feb 2018 09:03:47 -0800 Subject: [PATCH 04/20] chore: separate tslint config files for build and vscode This is a follow-up to https://github.com/strongloop/loopback-next/pull/964/. Two config files are in place now: - tslint.build.json for CLI build scripts - tslint.json for vscode --- .../project/templates/tslint.build.json | 15 +------------ .../generators/project/templates/tslint.json | 2 +- tslint.build.json | 22 +++++-------------- tslint.json | 2 +- 4 files changed, 9 insertions(+), 32 deletions(-) diff --git a/packages/cli/generators/project/templates/tslint.build.json b/packages/cli/generators/project/templates/tslint.build.json index 11aa5c89be1e..0ace3417fa90 100644 --- a/packages/cli/generators/project/templates/tslint.build.json +++ b/packages/cli/generators/project/templates/tslint.build.json @@ -1,17 +1,4 @@ { "$schema": "http://json.schemastore.org/tslint", - "extends": ["./tslint.json"], - // This configuration files enabled rules which require type checking - // and therefore cannot be run by Visual Studio Code TSLint extension - // See https://github.com/Microsoft/vscode-tslint/issues/70 - "rules": { - // These rules find errors related to TypeScript features. - - // These rules catch common errors in JS programming or otherwise - // confusing constructs that are prone to producing bugs. - - "await-promise": true, - "no-floating-promises": true, - "no-void-expression": [true, "ignore-arrow-function-shorthand"] - } + "extends": ["./node_modules/@loopback/build/config/tslint.build.json"] } diff --git a/packages/cli/generators/project/templates/tslint.json b/packages/cli/generators/project/templates/tslint.json index f151a872949c..6a8445c727c3 100644 --- a/packages/cli/generators/project/templates/tslint.json +++ b/packages/cli/generators/project/templates/tslint.json @@ -1,7 +1,7 @@ { "$schema": "http://json.schemastore.org/tslint", <% if (project.loopbackBuild) { -%> - "extends": ["./node_modules/@loopback/build/config/tslint.build.json"] + "extends": ["./node_modules/@loopback/build/config/tslint.common.json"] <% } else { -%> // See https://palantir.github.io/tslint/rules/ "rules": { diff --git a/tslint.build.json b/tslint.build.json index 67f07ae8d46f..0de4205d0047 100644 --- a/tslint.build.json +++ b/tslint.build.json @@ -1,20 +1,10 @@ { "$schema": "http://json.schemastore.org/tslint", - "extends": [ - "./tslint.json" - ], - // This configuration files enabled rules which require type checking - // and therefore cannot be run by Visual Studio Code TSLint extension - // See https://github.com/Microsoft/vscode-tslint/issues/70 - "rules": { - // These rules find errors related to TypeScript features. - - - // These rules catch common errors in JS programming or otherwise - // confusing constructs that are prone to producing bugs. - - "await-promise": true, - "no-floating-promises": true, - "no-void-expression": [true, "ignore-arrow-function-shorthand"] + "extends": ["./packages/build/config/tslint.build.json"], + "linterOptions": { + "exclude": [ + "./packages/cli/generators/*/templates/**/*", + "./packages/cli/test/sandbox/**/*" + ] } } diff --git a/tslint.json b/tslint.json index 0de4205d0047..57fe820a65da 100644 --- a/tslint.json +++ b/tslint.json @@ -1,6 +1,6 @@ { "$schema": "http://json.schemastore.org/tslint", - "extends": ["./packages/build/config/tslint.build.json"], + "extends": ["./packages/build/config/tslint.common.json"], "linterOptions": { "exclude": [ "./packages/cli/generators/*/templates/**/*", From e5a9ce8ca3ed322f86dd7738fb9992ecf0d4651a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Miroslav=20Bajto=C5=A1?= Date: Fri, 9 Feb 2018 18:48:54 +0100 Subject: [PATCH 05/20] refactor: simplify app setup via RestApplication (part II) (#968) - Move any REST-related setup from app.start() back to app constructor. - Update the project template to use RestApplication too - Update authentication README to use RestApplication --- packages/authentication/README.md | 25 ++++++++----------- .../app/templates/src/application.ts | 20 +++++---------- packages/cli/test/app.js | 8 +++--- .../example-hello-world/src/application.ts | 3 --- packages/example-log-extension/README.md | 4 +-- packages/rest/README.md | 16 +++++------- packages/rest/package.json | 1 + 7 files changed, 31 insertions(+), 46 deletions(-) diff --git a/packages/authentication/README.md b/packages/authentication/README.md index 240ab27845a4..c7ff6f374dde 100644 --- a/packages/authentication/README.md +++ b/packages/authentication/README.md @@ -145,36 +145,33 @@ export class MySequence implements SequenceHandler { Finally, put it all together in your application class: ```ts -import {Application} from '@loopback/core'; import { AuthenticationComponent, AuthenticationBindings, } from '@loopback/authentication'; -import {RestComponent, RestServer} from '@loopback/rest'; +import {RestApplication, RestServer} from '@loopback/rest'; import {MyAuthStrategyProvider} from './providers/auth-strategy'; import {MyController} from './controllers/my-controller'; import {MySequence} from './sequence'; -class MyApp extends Application { +class MyApp extends RestApplication { constructor() { - super({ - components: [AuthenticationComponent, RestComponent], - rest: { - sequence: MySequence - }, - controllers: [MyController], - }); + super(); + + this.component(AuthenticationComponent); + this + .bind(AuthenticationBindings.STRATEGY) + .toProvider(MyAuthStrategyProvider); + + this.sequence(MySequence); this.controller(MyController); } async start() { - const server = await this.getServer(RestServer); - - server.bind(AuthenticationBindings.STRATEGY) - .toProvider(MyAuthStrategyProvider); await super.start(); + const server = await this.getServer(RestServer); console.log(`REST server running on port: ${server.getSync('rest.port')}`); } } diff --git a/packages/cli/generators/app/templates/src/application.ts b/packages/cli/generators/app/templates/src/application.ts index d82aada7cd76..a89100142af0 100644 --- a/packages/cli/generators/app/templates/src/application.ts +++ b/packages/cli/generators/app/templates/src/application.ts @@ -3,33 +3,25 @@ // This file is licensed under the MIT License. // License text available at https://opensource.org/licenses/MIT -import {Application, ApplicationConfig} from '@loopback/core'; -import {RestComponent, RestServer} from '@loopback/rest'; +import {ApplicationConfig} from '@loopback/core'; +import {RestApplication, RestServer} from '@loopback/rest'; import {PingController} from './controllers/ping.controller'; import {MySequence} from './sequence'; -export class <%= project.applicationName %> extends Application { +export class <%= project.applicationName %> extends RestApplication { constructor(options?: ApplicationConfig) { - // Allow options to replace the defined components array, if desired. - options = Object.assign( - {}, - { - components: [RestComponent], - }, - options, - ); super(options); - this.server(RestServer); + this.sequence(MySequence); this.setupControllers(); } async start() { + await super.start(); + const server = await this.getServer(RestServer); - server.sequence(MySequence); const port = await server.get('rest.port'); console.log(`Server is running at http://127.0.0.1:${port}`); console.log(`Try http://127.0.0.1:${port}/ping`); - return await super.start(); } setupControllers() { diff --git a/packages/cli/test/app.js b/packages/cli/test/app.js index c33a8fd46a21..920ed0421326 100644 --- a/packages/cli/test/app.js +++ b/packages/cli/test/app.js @@ -27,11 +27,10 @@ describe('app-generator specfic files', () => { assert.file('src/application.ts'); assert.fileContent( 'src/application.ts', - /class MyAppApplication extends Application/ + /class MyAppApplication extends RestApplication/ ); assert.fileContent('src/application.ts', /constructor\(/); assert.fileContent('src/application.ts', /async start\(/); - assert.fileContent('src/application.ts', /RestComponent/); assert.file('src/index.ts'); assert.fileContent('src/index.ts', /new MyAppApplication/); @@ -48,7 +47,10 @@ describe('app-generator specfic files', () => { /@get\('\/ping'\)/ ); assert.fileContent('src/controllers/ping.controller.ts', /ping\(\)/); - assert.fileContent('src/controllers/ping.controller.ts', /\'\@loopback\/openapi\-v2\'/); + assert.fileContent( + 'src/controllers/ping.controller.ts', + /\'\@loopback\/openapi\-v2\'/ + ); assert.file; }); diff --git a/packages/example-hello-world/src/application.ts b/packages/example-hello-world/src/application.ts index 35098dfdfab8..bfa570c88383 100644 --- a/packages/example-hello-world/src/application.ts +++ b/packages/example-hello-world/src/application.ts @@ -3,9 +3,6 @@ // This file is licensed under the MIT License. // License text available at https://opensource.org/licenses/MIT -// tslint:disable-next-line:no-unused-variable -import {Application} from '@loopback/core'; - import {RestApplication, RestServer} from '@loopback/rest'; export class HelloWorldApplication extends RestApplication { diff --git a/packages/example-log-extension/README.md b/packages/example-log-extension/README.md index aeede0293832..18080b336179 100644 --- a/packages/example-log-extension/README.md +++ b/packages/example-log-extension/README.md @@ -32,10 +32,10 @@ import { } from 'loopback4-example-log-extension'; // Other imports ... -class LogApp extends LogLevelMixin(Application) { +class LogApp extends LogLevelMixin(RestApplication) { constructor() { super({ - components: [RestComponent, LogComponent], + components: [LogComponent], logLevel: LOG_LEVEL.ERROR, controllers: [MyController] }); diff --git a/packages/rest/README.md b/packages/rest/README.md index 1c9497509e96..8e5b330f88ff 100644 --- a/packages/rest/README.md +++ b/packages/rest/README.md @@ -26,21 +26,17 @@ Here's a basic "Hello World" application using `@loopback/core` and `@loopback/rest`: ```ts - import {Application} from '@loopback/core'; - import {RestComponent, RestServer} from '@loopback/rest'; + import {RestApplication, RestServer} from '@loopback/rest'; - const app = new Application({ - components: [ - RestComponent, - ] + const app = new RestApplication(); + app.handler((sequence, request, response) => { + sequence.send(response, 'hello world'); }); (async function start() { - const rest = await app.getServer(RestServer); - rest.handler((sequence, request, response) => { - sequence.send(response, 'hello world'); - }); await app.start(); + + const rest = await app.getServer(RestServer); console.log(`REST server running on port: ${rest.getSync('rest.port')}`); })(); ``` diff --git a/packages/rest/package.json b/packages/rest/package.json index e617a2f98cc5..ba082a0b419a 100644 --- a/packages/rest/package.json +++ b/packages/rest/package.json @@ -26,6 +26,7 @@ "@loopback/openapi-spec": "^4.0.0-alpha.25", "@loopback/openapi-v2": "^4.0.0-alpha.10", "@types/http-errors": "^1.6.1", + "@types/node": "^8.5.9", "body": "^5.1.0", "debug": "^3.1.0", "http-errors": "^1.6.1", From 09fc79188be25f1295261dd9916376a94549dc55 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Miroslav=20Bajto=C5=A1?= Date: Tue, 13 Feb 2018 14:59:36 +0100 Subject: [PATCH 06/20] fix(example-getting-started): use sinon from testlab (#984) Rework example-getting-started to use sinon from @loopback/testlab instead of importing it directly. --- packages/example-getting-started/package.json | 4 +--- .../test/unit/controllers/todo.controller.test.ts | 3 +-- 2 files changed, 2 insertions(+), 5 deletions(-) diff --git a/packages/example-getting-started/package.json b/packages/example-getting-started/package.json index 887f403da9f3..7471c4e1b9d8 100644 --- a/packages/example-getting-started/package.json +++ b/packages/example-getting-started/package.json @@ -30,9 +30,7 @@ "@loopback/openapi-spec": "^4.0.0-alpha.25", "@loopback/openapi-v2": "^4.0.0-alpha.10", "@loopback/repository": "^4.0.0-alpha.29", - "@loopback/rest": "^4.0.0-alpha.25", - "@types/sinon": "^4.1.3", - "sinon": "^4.1.5" + "@loopback/rest": "^4.0.0-alpha.25" }, "devDependencies": { "@loopback/build": "^4.0.0-alpha.13", diff --git a/packages/example-getting-started/test/unit/controllers/todo.controller.test.ts b/packages/example-getting-started/test/unit/controllers/todo.controller.test.ts index 860c2ba8c12b..f86329b11b6f 100644 --- a/packages/example-getting-started/test/unit/controllers/todo.controller.test.ts +++ b/packages/example-getting-started/test/unit/controllers/todo.controller.test.ts @@ -1,7 +1,6 @@ -import {expect} from '@loopback/testlab'; +import {expect, sinon} from '@loopback/testlab'; import {TodoController} from '../../../src/controllers'; import {TodoRepository} from '../../../src/repositories'; -import * as sinon from 'sinon'; import {Todo} from '../../../src/models/index'; import {givenTodo} from '../../helpers'; From 518d2b8b3fa93259697c76aeecdf0a7399fd5cdc Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Miroslav=20Bajto=C5=A1?= Date: Tue, 13 Feb 2018 15:44:38 +0100 Subject: [PATCH 07/20] ci(cli): increase timeout for a slow acceptance test (#986) --- packages/cli/test/clone-example.test.js | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/packages/cli/test/clone-example.test.js b/packages/cli/test/clone-example.test.js index 7ad9ff34f920..f07e15fc5038 100644 --- a/packages/cli/test/clone-example.test.js +++ b/packages/cli/test/clone-example.test.js @@ -17,8 +17,8 @@ const VALID_EXAMPLE = 'getting-started'; const SANDBOX_PATH = path.resolve(__dirname, 'sandbox'); let sandbox; -describe('cloneExampleFromGitHub', function() { - this.timeout(10000); +describe('cloneExampleFromGitHub (SLOW)', function() { + this.timeout(20000); before(createSandbox); beforeEach(resetSandbox); From 707a4017ebcd3d565509a587fc29750a8237d35e Mon Sep 17 00:00:00 2001 From: Kyusung Shim Date: Tue, 13 Feb 2018 11:10:14 -0500 Subject: [PATCH 08/20] Decouple application config from features (e.g. components) (#975) * fix(core): decouple app config from assembly * fix(core): update the readmes * fix(core): add comments * fix(rest): rebase against RestApplication PR * fix(core): get rid of array registration --- .../test/acceptance/basic-auth.ts | 6 +- .../extension/templates/src/mixins/README.md | 5 +- .../templates/src/providers/README.md | 12 ++-- packages/core/README.md | 6 +- packages/core/package.json | 4 +- packages/core/src/application.ts | 32 --------- .../test/acceptance/application.acceptance.ts | 37 +++-------- .../test/acceptance/application.feature.md | 16 ++--- packages/core/test/unit/application.test.ts | 66 +++++++++---------- .../src/application.ts | 7 -- packages/example-log-extension/README.md | 4 +- .../acceptance/log.extension.acceptance.ts | 10 ++- packages/repository/README.md | 2 +- packages/repository/src/repository-mixin.ts | 14 ---- .../repository-mixin/repository-mixin.test.ts | 21 +----- packages/rest/README.md | 4 +- packages/rest/src/rest-application.ts | 11 +--- packages/rest/src/rest-server.ts | 5 +- .../bootstrapping/rest.acceptance.ts | 4 +- .../acceptance/routing/routing.acceptance.ts | 6 +- .../sequence/sequence.acceptance.ts | 5 +- .../integration/rest-server.integration.ts | 10 ++- .../rest-application/rest-application.test.ts | 22 +------ .../rest/test/unit/rest-component.test.ts | 15 ++--- .../rest-server.controller.test.ts | 5 +- .../rest-server.open-api-spec.test.ts | 5 +- .../test/unit/rest-server/rest-server.test.ts | 19 +++--- 27 files changed, 106 insertions(+), 247 deletions(-) diff --git a/packages/authentication/test/acceptance/basic-auth.ts b/packages/authentication/test/acceptance/basic-auth.ts index 3b4cf7d4f4c2..74f8c2cd47f8 100644 --- a/packages/authentication/test/acceptance/basic-auth.ts +++ b/packages/authentication/test/acceptance/basic-auth.ts @@ -89,9 +89,9 @@ describe('Basic Authentication', () => { } async function givenAServer() { - app = new Application({ - components: [AuthenticationComponent, RestComponent], - }); + app = new Application(); + app.component(AuthenticationComponent); + app.component(RestComponent); server = await app.getServer(RestServer); } diff --git a/packages/cli/generators/extension/templates/src/mixins/README.md b/packages/cli/generators/extension/templates/src/mixins/README.md index 188814b024f6..f4f6e2a7963a 100644 --- a/packages/cli/generators/extension/templates/src/mixins/README.md +++ b/packages/cli/generators/extension/templates/src/mixins/README.md @@ -154,7 +154,6 @@ class LoggingComponent implements Component{ loggers: [ColorLogger]; } -const app = new LoggingApplication({ - components: [LoggingComponent] // Logger from MyComponent will be bound to loggers.ColorLogger -}); +const app = new LoggingApplication(); +app.component(LoggingComponent); // Logger from MyComponent will be bound to loggers.ColorLogger ``` diff --git a/packages/cli/generators/extension/templates/src/providers/README.md b/packages/cli/generators/extension/templates/src/providers/README.md index d0a6adceeb4e..4cf3dcb9d4b2 100644 --- a/packages/cli/generators/extension/templates/src/providers/README.md +++ b/packages/cli/generators/extension/templates/src/providers/README.md @@ -16,18 +16,18 @@ The logger will log the URL, the parsed request parameters, and the result. The TimerProvider is automatically bound to your Application's [Context](http://loopback.io/doc/en/lb4/Context.html) using the LogComponent which exports this provider with a binding key of `extension-starter.timer`. You can learn more about components in the [related resources section](#related-resources). -This provider makes availble to your application a timer function which given a start time _(given as an array [seconds, nanoseconds])_ can give you a total time elapsed since the start in milliseconds. The timer can also start timing if no start time is given. This is used by LogComponent to allow a user to time a Sequence. +This provider makes availble to your application a timer function which given a start time _(given as an array [seconds, nanoseconds])_ can give you a total time elapsed since the start in milliseconds. The timer can also start timing if no start time is given. This is used by LogComponent to allow a user to time a Sequence. *NOTE:* _You can get the start time in the required format by using `this.logger.startTimer()`._ You can provide your own implementation of the elapsed time function by binding it to the binding key (accessible via `ExtensionStarterBindings`) as follows: ```ts app.bind(ExtensionStarterBindings.TIMER).to(timerFn); -``` +``` ### LogProvider -LogProvider can automatically be bound to your Application's Context using the LogComponent which exports the provider with a binding key of `extension-starter.actions.log`. +LogProvider can automatically be bound to your Application's Context using the LogComponent which exports the provider with a binding key of `extension-starter.actions.log`. The key can be accessed by importing `ExtensionStarterBindings` as follows: @@ -38,7 +38,7 @@ import {ExtensionStarterBindings} from 'HelloExtensions'; const key = ExtensionStarterBindings.LOG_ACTION; ``` -LogProvider gives us a seuqence action and a `startTimer` function. In order to use the sequence action, you must define your own sequence as shown below. +LogProvider gives us a seuqence action and a `startTimer` function. In order to use the sequence action, you must define your own sequence as shown below. **Example: Sequence** ```ts @@ -85,9 +85,9 @@ Once a sequence has been written, we can just use that in our Application as fol **Example: Application** ```ts const app = new Application({ - sequence: LogSequence, - components: [LogComponent] + sequence: LogSequence }); +app.component(LogComponent); // Now all requests handled by our sequence will be logged. ``` diff --git a/packages/core/README.md b/packages/core/README.md index f69c2a693760..f4796c170171 100644 --- a/packages/core/README.md +++ b/packages/core/README.md @@ -39,10 +39,6 @@ import {RestComponent} from '@loopback/rest'; import {GrpcComponent} from '@loopback/grpc'; const app = new Application({ - components: [ - RestComponent, // REST Server - GrpcComponent, // GRPC Server - ], rest: { port: 3000, }, @@ -50,6 +46,8 @@ const app = new Application({ port: 3001, }, }); +app.component(RestComponent) // REST Server +app.component(GrpcComponent) // GRPC Server (async function start() { // Let's retrieve the bound instances of our servers. diff --git a/packages/core/package.json b/packages/core/package.json index 6310f591e98d..e68172f6838f 100644 --- a/packages/core/package.json +++ b/packages/core/package.json @@ -21,9 +21,7 @@ "copyright.owner": "IBM Corp.", "license": "MIT", "dependencies": { - "@loopback/context": "^4.0.0-alpha.31", - "lodash": "^4.17.4", - "topo": "^3.0.0" + "@loopback/context": "^4.0.0-alpha.31" }, "devDependencies": { "@loopback/build": "^4.0.0-alpha.13", diff --git a/packages/core/src/application.ts b/packages/core/src/application.ts index 777e40c2aaac..9c437d45f57f 100644 --- a/packages/core/src/application.ts +++ b/packages/core/src/application.ts @@ -23,24 +23,6 @@ export class Application extends Context { this.bind(CoreBindings.APPLICATION_INSTANCE).to(this); // Make options available to other modules as well. this.bind(CoreBindings.APPLICATION_CONFIG).to(options); - - if (options.components) { - for (const component of options.components) { - this.component(component); - } - } - - if (options.servers) { - for (const name in options.servers) { - this.server(options.servers[name], name); - } - } - - if (options.controllers) { - for (const ctor of options.controllers) { - this.controller(ctor); - } - } } /** @@ -218,20 +200,6 @@ export class Application extends Context { * Configuration for application */ export interface ApplicationConfig { - /** - * An array of component classes - */ - components?: Array>; - /** - * An array of controller classes - */ - controllers?: Array; - /** - * A map of server name/class pairs - */ - servers?: { - [name: string]: Constructor; - }; /** * Other properties */ diff --git a/packages/core/test/acceptance/application.acceptance.ts b/packages/core/test/acceptance/application.acceptance.ts index 9318bebbb60b..5bc11551c162 100644 --- a/packages/core/test/acceptance/application.acceptance.ts +++ b/packages/core/test/acceptance/application.acceptance.ts @@ -12,9 +12,8 @@ describe('Bootstrapping the application', () => { context('with user-defined components', () => { it('binds all user-defined components to the application context', () => { class AuditComponent {} - const app = new Application({ - components: [AuditComponent], - }); + const app = new Application(); + app.component(AuditComponent); const componentKeys = app.find('component.*').map(b => b.key); expect(componentKeys).to.containEql('components.AuditComponent'); @@ -32,29 +31,12 @@ describe('Bootstrapping the application', () => { class FooComponent { providers = {foo: FooProvider}; } - const app = new Application({ - components: [FooComponent], - }); + const app = new Application(); + app.component(FooComponent); const value = app.getSync('foo'); expect(value).to.equal('bar'); }); - it('registers controllers from constructor', () => { - class ProductController {} - - const app = new Application({ - controllers: [ProductController], - }); - - expect(app.find('controllers.*').map(b => b.key)).to.eql([ - 'controllers.ProductController', - ]); - - expect(app.findByTag('controller').map(b => b.key)).to.eql([ - 'controllers.ProductController', - ]); - }); - it('registers all controllers from components', async () => { // TODO(bajtos) Beef up this test. Create a real controller with // a public API endpoint and verify that this endpoint can be invoked @@ -66,9 +48,8 @@ describe('Bootstrapping the application', () => { controllers: ControllerClass[] = [ProductController]; } - const app = new Application({ - components: [ProductComponent], - }); + const app = new Application(); + app.component(ProductComponent); expect(app.find('controllers.*').map(b => b.key)).to.eql([ 'controllers.ProductController', @@ -111,9 +92,9 @@ describe('Bootstrapping the application', () => { }; } } - const app = new Application({ - components: [ConfigComponent, GreetingComponent], - }); + const app = new Application(); + app.component(ConfigComponent); + app.component(GreetingComponent); expect(app.getSync('greeting')).to.equal('Hi!'); }); diff --git a/packages/core/test/acceptance/application.feature.md b/packages/core/test/acceptance/application.feature.md index 37493b61004a..fa671fcf6f17 100644 --- a/packages/core/test/acceptance/application.feature.md +++ b/packages/core/test/acceptance/application.feature.md @@ -2,14 +2,14 @@ - In order to initialize my application - As an app developer -- I want to register components and sequences when initalizing the application +- I want to register components when initalizing the application - So that I can use them throughout the application lifecycle ## Scenario: Basic usage (config provided) - Given an importable `Application` class - When I create an application with user-defined configurations -- Then the application should register the given components and sequences +- Then the application should register the given components ```ts import {Application} from '@loopback/core'; @@ -17,11 +17,9 @@ import {Authentication} from '@loopback/authentication'; import {Authorization} from '@loopback/authorization'; import {Rejection} from '@loopback/rejection'; -const app = new Application({ - components: [Todo, Authentication, Authorization, Rejection], - sequence: [TodoSequence] -}); - -// get metadata about the registered components -console.log(app.find('sequence.*')); // [Bindings] should match the 1 sequence registered above +const app = new Application(); +app.component(Todo); +app.component(Authentication); +app.component(Authorization); +app.component(Rejection); ``` diff --git a/packages/core/test/unit/application.test.ts b/packages/core/test/unit/application.test.ts index 406a44a38c37..5fd3ece0ccc3 100644 --- a/packages/core/test/unit/application.test.ts +++ b/packages/core/test/unit/application.test.ts @@ -49,7 +49,7 @@ describe('Application', () => { ); }); - it('binds a component', () => { + it('binds a component with custom name', () => { app.component(MyComponent, 'my-component'); expect(findKeysByTag(app, 'component')).to.containEql( 'components.my-component', @@ -77,43 +77,39 @@ describe('Application', () => { const result = await app.getServer(name); expect(result.constructor.name).to.equal(FakeServer.name); }); + + it('allows binding of multiple servers as an array', async () => { + const app = new Application(); + const bindings = app.servers([FakeServer, AnotherServer]); + expect(Array.from(bindings[0].tags)).to.containEql('server'); + expect(Array.from(bindings[1].tags)).to.containEql('server'); + const fakeResult = await app.getServer(FakeServer); + expect(fakeResult.constructor.name).to.equal(FakeServer.name); + const AnotherResult = await app.getServer(AnotherServer); + expect(AnotherResult.constructor.name).to.equal(AnotherServer.name); + }); }); - describe('configuration', () => { - it('allows servers to be provided via config', async () => { - const name = 'abc123'; - const app = new Application({ - servers: { - abc123: FakeServer, - }, - }); - const result = await app.getServer(name); - expect(result.constructor.name).to.equal(FakeServer.name); + describe('start', () => { + it('starts all injected servers', async () => { + const app = new Application(); + app.component(FakeComponent); + + await app.start(); + const server = await app.getServer(FakeServer); + expect(server).to.not.be.null(); + expect(server.running).to.equal(true); + await app.stop(); }); - describe('start', () => { - it('starts all injected servers', async () => { - const app = new Application({ - components: [FakeComponent], - }); - - await app.start(); - const server = await app.getServer(FakeServer); - expect(server).to.not.be.null(); - expect(server.running).to.equal(true); - await app.stop(); - }); - - it('does not attempt to start poorly named bindings', async () => { - const app = new Application({ - components: [FakeComponent], - }); - - // The app.start should not attempt to start this binding. - app.bind('controllers.servers').to({}); - await app.start(); - await app.stop(); - }); + it('does not attempt to start poorly named bindings', async () => { + const app = new Application(); + app.component(FakeComponent); + + // The app.start should not attempt to start this binding. + app.bind('controllers.servers').to({}); + await app.start(); + await app.stop(); }); }); @@ -147,3 +143,5 @@ class FakeServer extends Context implements Server { this.running = false; } } + +class AnotherServer extends FakeServer {} diff --git a/packages/example-getting-started/src/application.ts b/packages/example-getting-started/src/application.ts index d22d0b515a59..eadb5bae3f15 100644 --- a/packages/example-getting-started/src/application.ts +++ b/packages/example-getting-started/src/application.ts @@ -20,13 +20,6 @@ import { /* tslint:enable:no-unused-variable */ export class TodoApplication extends RepositoryMixin(RestApplication) { constructor(options?: ApplicationConfig) { - // TODO(bajtos) The comment below does not make sense to me. - // Consumers of TodoApplication object should not be changing the shape - // of the app (what components are mounted, etc.) The config object should - // be used only to configure what ports the app is listening on, - // which database to connect to, etc. - // See https://github.com/strongloop/loopback-next/issues/742 - super(options); this.setupRepositories(); this.setupControllers(); diff --git a/packages/example-log-extension/README.md b/packages/example-log-extension/README.md index 18080b336179..80422623e6ea 100644 --- a/packages/example-log-extension/README.md +++ b/packages/example-log-extension/README.md @@ -35,10 +35,10 @@ import { class LogApp extends LogLevelMixin(RestApplication) { constructor() { super({ - components: [LogComponent], logLevel: LOG_LEVEL.ERROR, - controllers: [MyController] }); + this.component(LogComponent); + this.controller(MyController); }; } diff --git a/packages/example-log-extension/test/acceptance/log.extension.acceptance.ts b/packages/example-log-extension/test/acceptance/log.extension.acceptance.ts index 399de1805e54..f243d8c892d6 100644 --- a/packages/example-log-extension/test/acceptance/log.extension.acceptance.ts +++ b/packages/example-log-extension/test/acceptance/log.extension.acceptance.ts @@ -3,9 +3,8 @@ // This file is licensed under the MIT License. // License text available at https://opensource.org/licenses/MIT -import {Application} from '@loopback/core'; import { - RestComponent, + RestApplication, RestServer, get, param, @@ -46,7 +45,7 @@ describe('log extension acceptance test', () => { let server: RestServer; let spy: SinonSpy; - class LogApp extends LogLevelMixin(Application) {} + class LogApp extends LogLevelMixin(RestApplication) {} const debugMatch: string = chalk.white( 'DEBUG: /debug :: MyController.debug() => debug called', @@ -238,9 +237,8 @@ describe('log extension acceptance test', () => { } async function createApp() { - app = new LogApp({ - components: [RestComponent, LogComponent], - }); + app = new LogApp(); + app.component(LogComponent); app.bind(EXAMPLE_LOG_BINDINGS.TIMER).to(timer); server = await app.getServer(RestServer); diff --git a/packages/repository/README.md b/packages/repository/README.md index d9c249e45130..2f769969f6a3 100644 --- a/packages/repository/README.md +++ b/packages/repository/README.md @@ -373,7 +373,7 @@ ctx.bind('repositories.noteRepo').toClass(MyNoteRepository); ``` #### Using the Repository Mixin for Application -A Repository Mixin is available for Application that provides convenience methods for binding and instantiating a repository class. Bound instances can be used anywhere in your application using Dependency Injection. An array set to the key `repositories` can be passed to the constructor or the `.repository(RepositoryClass)` function can be used. The mixin will also instantiate any repositories declared by a component in its constructor using the `repositories` key. +A Repository Mixin is available for Application that provides convenience methods for binding and instantiating a repository class. Bound instances can be used anywhere in your application using Dependency Injection. The `.repository(RepositoryClass)` function can be used to bind a repository class to an Application. The mixin will also instantiate any repositories declared by a component in its constructor using the `repositories` key. Repositories will be bound to the key `repositories.RepositoryClass` where `RepositoryClass` is the name of the Repository class being bound. ```ts diff --git a/packages/repository/src/repository-mixin.ts b/packages/repository/src/repository-mixin.ts index 5ec8b2e0b9b4..2994f519d911 100644 --- a/packages/repository/src/repository-mixin.ts +++ b/packages/repository/src/repository-mixin.ts @@ -23,20 +23,6 @@ export function RepositoryMixin>(superClass: T) { // A mixin class has to take in a type any[] argument! constructor(...args: any[]) { super(...args); - if (!this.options) this.options = {}; - - if (this.options.repositories) { - for (const repo of this.options.repositories) { - this.repository(repo); - } - } - - if (this.options.components) { - // Super would have already mounted the component - for (const component of this.options.components) { - this.mountComponentRepository(component); - } - } } /** diff --git a/packages/repository/test/unit/repository-mixin/repository-mixin.test.ts b/packages/repository/test/unit/repository-mixin/repository-mixin.test.ts index 1f9a4af546c8..05031151522f 100644 --- a/packages/repository/test/unit/repository-mixin/repository-mixin.test.ts +++ b/packages/repository/test/unit/repository-mixin/repository-mixin.test.ts @@ -26,14 +26,6 @@ describe('RepositoryMixin', () => { expect(typeof myApp.repository).to.be.eql('function'); }); - it('binds repository from constructor', () => { - const myApp = new AppWithRepoMixin({ - repositories: [NoteRepo], - }); - - expectNoteRepoToBeBound(myApp); - }); - it('binds repository from app.repository()', () => { const myApp = new AppWithRepoMixin(); @@ -45,21 +37,12 @@ describe('RepositoryMixin', () => { it('binds user defined component without repository', () => { class EmptyTestComponent {} - const myApp = new AppWithRepoMixin({ - components: [EmptyTestComponent], - }); + const myApp = new AppWithRepoMixin(); + myApp.component(EmptyTestComponent); expectComponentToBeBound(myApp, EmptyTestComponent); }); - it('binds user defined component with repository in constructor', () => { - const myApp = new AppWithRepoMixin({ - components: [TestComponent], - }); - - expectNoteRepoToBeBound(myApp); - }); - it('binds user defined component with repository from .component()', () => { const myApp = new AppWithRepoMixin(); diff --git a/packages/rest/README.md b/packages/rest/README.md index 8e5b330f88ff..0948e9130063 100644 --- a/packages/rest/README.md +++ b/packages/rest/README.md @@ -47,13 +47,11 @@ Application options. ```ts const app = new Application({ - components: [ - RestComponent, - ], rest: { port: 3001 } }); + app.component(RestComponent); ``` ### `rest` options diff --git a/packages/rest/src/rest-application.ts b/packages/rest/src/rest-application.ts index e40ec01a5f26..1894beeab647 100644 --- a/packages/rest/src/rest-application.ts +++ b/packages/rest/src/rest-application.ts @@ -28,17 +28,8 @@ export const SequenceActions = RestBindings.SequenceActions; export class RestApplication extends Application { constructor(config?: ApplicationConfig) { const cfg = Object.assign({}, config); - - // If the configuration provides an array of components, we want - // to preserve that collection, but also ensure that it contains - // RestComponent. - if (!cfg.components) { - cfg.components = []; - } - if (!cfg.components.includes(RestComponent)) { - cfg.components.push(RestComponent); - } super(cfg); + this.component(RestComponent); } server(server: Constructor, name?: string): Binding { diff --git a/packages/rest/src/rest-server.ts b/packages/rest/src/rest-server.ts index 3eef1ec85aed..328cbf7cb32b 100644 --- a/packages/rest/src/rest-server.ts +++ b/packages/rest/src/rest-server.ts @@ -60,10 +60,7 @@ const OPENAPI_SPEC_MAPPING: {[key: string]: OpenApiSpecOptions} = { * A REST API server for use with Loopback. * Add this server to your application by importing the RestComponent. * ```ts - * const app = new MyApplication({ - * components: [RestComponent] - * }); - * // OR + * const app = new MyApplication(); * app.component(RestComponent); * ``` * diff --git a/packages/rest/test/acceptance/bootstrapping/rest.acceptance.ts b/packages/rest/test/acceptance/bootstrapping/rest.acceptance.ts index ca8743a248f6..bbc60c78afff 100644 --- a/packages/rest/test/acceptance/bootstrapping/rest.acceptance.ts +++ b/packages/rest/test/acceptance/bootstrapping/rest.acceptance.ts @@ -28,12 +28,12 @@ describe('Bootstrapping with RestComponent', () => { async function givenAppWithUserDefinedSequence() { class UserDefinedSequence extends DefaultSequence {} app = new Application({ - components: [RestComponent], rest: { sequence: UserDefinedSequence, port: 0, }, }); + app.component(RestComponent); server = await app.getServer(RestServer); } }); @@ -42,11 +42,11 @@ describe('Bootstrapping with RestComponent', () => { describe('Starting the application', () => { it('starts an HTTP server (using RestServer)', async () => { const app = new Application({ - components: [RestComponent], rest: { port: 0, }, }); + app.component(RestComponent); const server = await app.getServer(RestServer); server.handler(sequenceHandler); await startServerCheck(app); diff --git a/packages/rest/test/acceptance/routing/routing.acceptance.ts b/packages/rest/test/acceptance/routing/routing.acceptance.ts index f1d14e9e64a9..f0c25fdf1b7a 100644 --- a/packages/rest/test/acceptance/routing/routing.acceptance.ts +++ b/packages/rest/test/acceptance/routing/routing.acceptance.ts @@ -490,9 +490,9 @@ describe('Routing', () => { /* ===== HELPERS ===== */ function givenAnApplication() { - return new Application({ - components: [RestComponent], - }); + const app = new Application(); + app.component(RestComponent); + return app; } async function givenAServer(app: Application) { return await app.getServer(RestServer); diff --git a/packages/rest/test/acceptance/sequence/sequence.acceptance.ts b/packages/rest/test/acceptance/sequence/sequence.acceptance.ts index 34d8313f1ea1..8f36d2484c54 100644 --- a/packages/rest/test/acceptance/sequence/sequence.acceptance.ts +++ b/packages/rest/test/acceptance/sequence/sequence.acceptance.ts @@ -185,9 +185,8 @@ describe('Sequence', () => { /* ===== HELPERS ===== */ async function givenAnApplication() { - app = new Application({ - components: [RestComponent], - }); + app = new Application(); + app.component(RestComponent); server = await app.getServer(RestServer); } diff --git a/packages/rest/test/integration/rest-server.integration.ts b/packages/rest/test/integration/rest-server.integration.ts index e18916a04394..4fd0d155ec07 100644 --- a/packages/rest/test/integration/rest-server.integration.ts +++ b/packages/rest/test/integration/rest-server.integration.ts @@ -184,9 +184,8 @@ servers: }); it('exposes "GET /swagger-ui" endpoint', async () => { - const app = new Application({ - components: [RestComponent], - }); + const app = new Application(); + app.component(RestComponent); const server = await app.getServer(RestServer); const greetSpec = { responses: { @@ -216,9 +215,9 @@ servers: it('exposes "GET /swagger-ui" endpoint with apiExplorerUrl', async () => { const app = new Application({ - components: [RestComponent], rest: {apiExplorerUrl: 'http://petstore.swagger.io'}, }); + app.component(RestComponent); const server = await app.getServer(RestServer); const greetSpec = { responses: { @@ -247,9 +246,8 @@ servers: }); async function givenAServer(options?: ApplicationConfig) { - if (!options) options = {}; - options.components = [RestComponent]; const app = new Application(options); + app.component(RestComponent); return await app.getServer(RestServer); } }); diff --git a/packages/rest/test/unit/rest-application/rest-application.test.ts b/packages/rest/test/unit/rest-application/rest-application.test.ts index 993a6fc35c7f..028387fdce80 100644 --- a/packages/rest/test/unit/rest-application/rest-application.test.ts +++ b/packages/rest/test/unit/rest-application/rest-application.test.ts @@ -37,30 +37,14 @@ describe('RestApplication', () => { ); }); - it('when attempting to bind servers via configuration', () => { - expect.throws( - () => { - // tslint:disable-next-line:no-unused-variable - const app = new RestApplication({ - servers: { - foo: RestServer, - bar: RestServer, - }, - }); - }, - Error, - ERR_NO_MULTI_SERVER, - ); - }); - it('when attempting bind multiple servers via RestComponent', () => { class OtherRestComponent extends RestComponent {} expect.throws( () => { // tslint:disable-next-line:no-unused-variable - const app = new RestApplication({ - components: [RestComponent, OtherRestComponent], - }); + const app = new RestApplication(); + app.component(RestComponent); + app.component(OtherRestComponent); }, Error, ERR_NO_MULTI_SERVER, diff --git a/packages/rest/test/unit/rest-component.test.ts b/packages/rest/test/unit/rest-component.test.ts index 00fe941b85aa..97d1c22523df 100644 --- a/packages/rest/test/unit/rest-component.test.ts +++ b/packages/rest/test/unit/rest-component.test.ts @@ -20,9 +20,8 @@ const SequenceActions = RestBindings.SequenceActions; describe('RestComponent', () => { describe('Providers', () => { describe('Default implementations are bound', () => { - const app = new Application({ - components: [RestComponent], - }); + const app = new Application(); + app.component(RestComponent); // Stub constructor requirements for some providers. app.bind(RestBindings.Http.CONTEXT).to(new Context()); @@ -40,9 +39,8 @@ describe('RestComponent', () => { }); describe('LOG_ERROR', () => { it('matches expected argument signature', async () => { - const app = new Application({ - components: [RestComponent], - }); + const app = new Application(); + app.component(RestComponent); const server = await app.getServer(RestServer); const logError = await server.get(SequenceActions.LOG_ERROR); expect(logError.length).to.equal(3); // (err, statusCode, request) @@ -73,9 +71,8 @@ describe('RestComponent', () => { } } - const app = new Application({ - components: [CustomRestComponent], - }); + const app = new Application(); + app.component(CustomRestComponent); const server = await app.getServer(RestServer); const logError = await server.get(SequenceActions.LOG_ERROR); logError(new Error('test-error'), 400, new ShotRequest({url: '/'})); diff --git a/packages/rest/test/unit/rest-server/rest-server.controller.test.ts b/packages/rest/test/unit/rest-server/rest-server.controller.test.ts index 6c5152f37150..a79a5ea6c664 100644 --- a/packages/rest/test/unit/rest-server/rest-server.controller.test.ts +++ b/packages/rest/test/unit/rest-server/rest-server.controller.test.ts @@ -9,9 +9,8 @@ import {RestServer, RestComponent} from '../../..'; describe('Application.controller()', () => { it('binds the controller to "controllers.*" namespace', async () => { - const app = new Application({ - components: [RestComponent], - }); + const app = new Application(); + app.component(RestComponent); const server = await app.getServer(RestServer); class TestController {} diff --git a/packages/rest/test/unit/rest-server/rest-server.open-api-spec.test.ts b/packages/rest/test/unit/rest-server/rest-server.open-api-spec.test.ts index 0453ed960c31..3d990d99215b 100644 --- a/packages/rest/test/unit/rest-server/rest-server.open-api-spec.test.ts +++ b/packages/rest/test/unit/rest-server/rest-server.open-api-spec.test.ts @@ -174,9 +174,8 @@ describe('RestServer.getApiSpec()', () => { }); async function givenApplication() { - app = new Application({ - components: [RestComponent], - }); + app = new Application(); + app.component(RestComponent); server = await app.getServer(RestServer); } }); diff --git a/packages/rest/test/unit/rest-server/rest-server.test.ts b/packages/rest/test/unit/rest-server/rest-server.test.ts index 8124f851ba88..1430b4d86d84 100644 --- a/packages/rest/test/unit/rest-server/rest-server.test.ts +++ b/packages/rest/test/unit/rest-server/rest-server.test.ts @@ -64,17 +64,15 @@ describe('RestServer', () => { describe('configuration', () => { it('uses http port 3000 by default', async () => { - const app = new Application({ - components: [RestComponent], - }); + const app = new Application(); + app.component(RestComponent); const server = await app.getServer(RestServer); expect(server.getSync(RestBindings.PORT)).to.equal(3000); }); it('uses undefined http host by default', async () => { - const app = new Application({ - components: [RestComponent], - }); + const app = new Application(); + app.component(RestComponent); const server = await app.getServer(RestServer); const host = await server.getSync(RestBindings.HOST); expect(host).to.be.undefined(); @@ -82,18 +80,18 @@ describe('RestServer', () => { it('can set port 0', async () => { const app = new Application({ - components: [RestComponent], rest: {port: 0}, }); + app.component(RestComponent); const server = await app.getServer(RestServer); expect(server.getSync(RestBindings.PORT)).to.equal(0); }); it('honors host/port', async () => { const app = new Application({ - components: [RestComponent], rest: {port: 4000, host: 'my-host'}, }); + app.component(RestComponent); const server = await app.getServer(RestServer); expect(server.getSync(RestBindings.PORT)).to.equal(4000); expect(server.getSync(RestBindings.HOST)).to.equal('my-host'); @@ -101,9 +99,8 @@ describe('RestServer', () => { }); async function givenRequestContext() { - const app = new Application({ - components: [RestComponent], - }); + const app = new Application(); + app.component(RestComponent); const server = await app.getServer(RestServer); const requestContext = new Context(server); requestContext.bind(RestBindings.Http.CONTEXT).to(requestContext); From f13f603a9a496da8905955599452bda01250291c Mon Sep 17 00:00:00 2001 From: Raymond Feng Date: Thu, 1 Feb 2018 12:27:46 -0800 Subject: [PATCH 09/20] fix: clean up example-log-extension This PR is a follow-up to the following issue based on review comments: https://github.com/strongloop/loopback-next/pull/939 - Demonstrate how to plug in a different log (console vs memory) - Switch to @loopback/metadata for decorator implementation - Remove log level provider as it's supposed to be contributed by the app The exercise also exposes a few needs: 1. Support for optional dependency so that we can fall back a default implementation (or a better way) 2. Allow a component to bind artifacts beyond providers --- packages/example-log-extension/README.md | 167 +++++++++++------- .../example-log-extension/src/component.ts | 3 +- .../src/decorators/log.decorator.ts | 32 ++-- packages/example-log-extension/src/index.ts | 1 - packages/example-log-extension/src/keys.ts | 1 + .../src/providers/log-action.provider.ts | 53 +++--- .../src/providers/log-level.provider.ts | 17 -- packages/example-log-extension/src/types.ts | 17 ++ .../acceptance/log.extension.acceptance.ts | 18 +- .../test/in-memory-logger.ts | 44 +++++ .../example-log-extension/test/log-spy.ts | 19 ++ .../providers/log-action.provider.unit.ts | 94 ++++++---- .../unit/providers/log-level.provider.unit.ts | 14 -- 13 files changed, 308 insertions(+), 172 deletions(-) delete mode 100644 packages/example-log-extension/src/providers/log-level.provider.ts create mode 100644 packages/example-log-extension/test/in-memory-logger.ts create mode 100644 packages/example-log-extension/test/log-spy.ts delete mode 100644 packages/example-log-extension/test/unit/providers/log-level.provider.unit.ts diff --git a/packages/example-log-extension/README.md b/packages/example-log-extension/README.md index 80422623e6ea..4381ebe9ac09 100644 --- a/packages/example-log-extension/README.md +++ b/packages/example-log-extension/README.md @@ -86,13 +86,20 @@ Define `Binding` keys here for the component as well as any constants for the user (for this extension that'll be the logLevel `enum`). ```ts +/** + * Binding keys used by this component. + */ export namespace EXAMPLE_LOG_BINDINGS { export const METADATA = 'example.log.metadata'; export const APP_LOG_LEVEL = 'example.log.level'; export const TIMER = 'example.log.timer'; + export const LOGGER = 'example.log.logger'; export const LOG_ACTION = 'example.log.action'; } +/** + * Enum to define the supported log levels + */ export enum LOG_LEVEL { DEBUG, INFO, @@ -108,10 +115,14 @@ Define TypeScript type definitions / interfaces for complex types and functions ```ts import {ParsedRequest, OperationArgs} from '@loopback/rest'; +/** + * A function to perform REST req/res logging action + */ export interface LogFn { ( req: ParsedRequest, args: OperationArgs, + // tslint:disable-next-line:no-any result: any, startTime?: HighResTime, ): Promise; @@ -119,45 +130,78 @@ export interface LogFn { startTimer(): HighResTime; } +/** + * Log level metadata + */ export type LevelMetadata = {level: number}; + +/** + * High resolution time as [seconds, nanoseconds]. Used by process.hrtime(). + */ export type HighResTime = [number, number]; // [seconds, nanoseconds] + +/** + * Log writing function + */ +export type LogWriterFn = (msg: string, level: number) => void; + +/** + * Timer function for logging + */ export type TimerFn = (start?: HighResTime) => HighResTime; ``` ### `src/decorators/log.decorator.ts` -Extension users can use decorators to provide "hints" (or metadata) for our -component. These "hints" allow the extension to modify behaviour accordingly. +Extension developers can create decorators to provide "hints" (or metadata) to +user artifacts such as controllers and their methods. These "hints" allow the +extension to add extra processing accordingly. For this extension, the decorator marks which controller methods should be -logged (and optionally at which level they should be logged). -`Reflector` from `@loopback/context` is used to store and retrieve the metadata -by the extension. +logged (and optionally at which level they should be logged). We leverage +`@loopback/metadata` module to implement the decorator and inspection function. ```ts import {LOG_LEVEL, EXAMPLE_LOG_BINDINGS} from '../keys'; -import {Constructor, Reflector} from '@loopback/context'; +import { + Constructor, + MethodDecoratorFactory, + MetadataInspector, +} from '@loopback/context'; import {LevelMetadata} from '../types'; +/** + * Mark a controller method as requiring logging (input, output & timing) + * if it is set at or greater than Application LogLevel. + * LOG_LEVEL.DEBUG < LOG_LEVEL.INFO < LOG_LEVEL.WARN < LOG_LEVEL.ERROR < LOG_LEVEL.OFF + * + * @param level The Log Level at or above it should log + */ export function log(level?: number) { - return function(target: Object, methodName: string): void { - if (level === undefined) level = LOG_LEVEL.WARN; - Reflector.defineMetadata( - EXAMPLE_LOG_BINDINGS.METADATA, - {level}, - target, - methodName, - ); - }; + if (level === undefined) level = LOG_LEVEL.WARN; + return MethodDecoratorFactory.createDecorator( + EXAMPLE_LOG_BINDINGS.METADATA, + { + level, + }, + ); } +/** + * Fetch log level stored by `@log` decorator. + * + * @param controllerClass Target controller + * @param methodName Target method + */ export function getLogMetadata( controllerClass: Constructor<{}>, methodName: string, ): LevelMetadata { - return Reflector.getMetadata( - EXAMPLE_LOG_BINDINGS.METADATA, - controllerClass.prototype, - methodName, + return ( + MetadataInspector.getMethodMetadata( + EXAMPLE_LOG_BINDINGS.METADATA, + controllerClass.prototype, + methodName, + ) || {level: LOG_LEVEL.OFF} ); } ``` @@ -214,23 +258,6 @@ export class TimerProvider implements Provider { } ``` -### `src/providers/log-level.provider.ts` -A provider can set the default binding value for `example.log.level` so it's -easier to get started with the extension. User's can override the value by -binding a new value or using the mixin. - -```ts -import {Provider} from '@loopback/context'; -import {LOG_LEVEL} from '../keys'; - -export class LogLevelProvider implements Provider { - constructor() {} - value(): number { - return LOG_LEVEL.WARN; - } -} -``` - ### `src/providers/log-action.provider.ts` This will be the most important provider for the extension as it is responsible for actually logging the request. The extension will retrieve the metadata @@ -245,17 +272,28 @@ import {CoreBindings} from '@loopback/core'; import {OperationArgs, ParsedRequest} from '@loopback/rest'; import {getLogMetadata} from '../decorators/log.decorator'; import {EXAMPLE_LOG_BINDINGS, LOG_LEVEL} from '../keys'; -import {LogFn, TimerFn, HighResTime, LevelMetadata} from '../types'; +import { + LogFn, + TimerFn, + HighResTime, + LevelMetadata, + LogWriterFn, +} from '../types'; import chalk from 'chalk'; export class LogActionProvider implements Provider { + // LogWriteFn is an optional dependency and it falls back to `logToConsole` + @inject(EXAMPLE_LOG_BINDINGS.LOGGER, {optional: true}) + private logWriter: LogWriterFn = logToConsole; + + @inject(EXAMPLE_LOG_BINDINGS.APP_LOG_LEVEL, {optional: true}) + private logLevel: number = LOG_LEVEL.WARN; + constructor( @inject.getter(CoreBindings.CONTROLLER_CLASS) private readonly getController: Getter>, @inject.getter(CoreBindings.CONTROLLER_METHOD_NAME) private readonly getMethod: Getter, - @inject(EXAMPLE_LOG_BINDINGS.APP_LOG_LEVEL) - private readonly logLevel: number, @inject(EXAMPLE_LOG_BINDINGS.TIMER) public timer: TimerFn, ) {} @@ -263,6 +301,7 @@ export class LogActionProvider implements Provider { const fn = (( req: ParsedRequest, args: OperationArgs, + // tslint:disable-next-line:no-any result: any, start?: HighResTime, ) => { @@ -279,11 +318,13 @@ export class LogActionProvider implements Provider { private async action( req: ParsedRequest, args: OperationArgs, + // tslint:disable-next-line:no-any result: any, start?: HighResTime, ): Promise { const controllerClass = await this.getController(); const methodName: string = await this.getMethod(); + const metadata: LevelMetadata = getLogMetadata(controllerClass, methodName); const level: number | undefined = metadata ? metadata.level : undefined; @@ -294,36 +335,42 @@ export class LogActionProvider implements Provider { level !== LOG_LEVEL.OFF ) { if (!args) args = []; - let log = `${req.url} :: ${controllerClass.name}.`; - log += `${methodName}(${args.join(', ')}) => `; + let msg = `${req.url} :: ${controllerClass.name}.`; + msg += `${methodName}(${args.join(', ')}) => `; - if (typeof result === 'object') log += JSON.stringify(result); - else log += result; + if (typeof result === 'object') msg += JSON.stringify(result); + else msg += result; if (start) { const timeDiff: HighResTime = this.timer(start); const time: number = timeDiff[0] * 1000 + Math.round(timeDiff[1] * 1e-4) / 100; - log = `${time}ms: ${log}`; + msg = `${time}ms: ${msg}`; } - switch (level) { - case LOG_LEVEL.DEBUG: - console.log(chalk.white(`DEBUG: ${log}`)); - break; - case LOG_LEVEL.INFO: - console.log(chalk.green(`INFO: ${log}`)); - break; - case LOG_LEVEL.WARN: - console.log(chalk.yellow(`WARN: ${log}`)); - break; - case LOG_LEVEL.ERROR: - console.log(chalk.red(`ERROR: ${log}`)); - break; - } + this.logWriter(msg, level); } } } + +function logToConsole(msg: string, level: number) { + let output; + switch (level) { + case LOG_LEVEL.DEBUG: + output = chalk.white(`DEBUG: ${msg}`); + break; + case LOG_LEVEL.INFO: + output = chalk.green(`INFO: ${msg}`); + break; + case LOG_LEVEL.WARN: + output = chalk.yellow(`WARN: ${msg}`); + break; + case LOG_LEVEL.ERROR: + output = chalk.red(`ERROR: ${msg}`); + break; + } + if (output) console.log(output); +} ``` ### `src/index.ts` @@ -333,7 +380,6 @@ Export all the files to ensure a user can import the necessary components. export * from './decorators/log.decorator'; export * from './mixins/log-level.mixin'; export * from './providers/log-action.provider'; -export * from './providers/log-level.provider'; export * from './providers/timer.provider'; export * from './component'; export * from './types'; @@ -347,13 +393,12 @@ they are automatically bound when a user adds the component to their application ```ts import {EXAMPLE_LOG_BINDINGS} from './keys'; import {Component, ProviderMap} from '@loopback/core'; -import {TimerProvider, LogActionProvider, LogLevelProvider} from './'; +import {TimerProvider, LogActionProvider} from './'; export class LogComponent implements Component { providers?: ProviderMap = { [EXAMPLE_LOG_BINDINGS.TIMER]: TimerProvider, [EXAMPLE_LOG_BINDINGS.LOG_ACTION]: LogActionProvider, - [EXAMPLE_LOG_BINDINGS.APP_LOG_LEVEL]: LogLevelProvider, }; } ``` diff --git a/packages/example-log-extension/src/component.ts b/packages/example-log-extension/src/component.ts index 0000f8fd22d0..146f5a75ab86 100644 --- a/packages/example-log-extension/src/component.ts +++ b/packages/example-log-extension/src/component.ts @@ -5,12 +5,11 @@ import {EXAMPLE_LOG_BINDINGS} from './keys'; import {Component, ProviderMap} from '@loopback/core'; -import {TimerProvider, LogActionProvider, LogLevelProvider} from './'; +import {TimerProvider, LogActionProvider} from './'; export class LogComponent implements Component { providers?: ProviderMap = { [EXAMPLE_LOG_BINDINGS.TIMER]: TimerProvider, [EXAMPLE_LOG_BINDINGS.LOG_ACTION]: LogActionProvider, - [EXAMPLE_LOG_BINDINGS.APP_LOG_LEVEL]: LogLevelProvider, }; } diff --git a/packages/example-log-extension/src/decorators/log.decorator.ts b/packages/example-log-extension/src/decorators/log.decorator.ts index 4e86d63a1250..3bdab2b034f8 100644 --- a/packages/example-log-extension/src/decorators/log.decorator.ts +++ b/packages/example-log-extension/src/decorators/log.decorator.ts @@ -4,7 +4,11 @@ // License text available at https://opensource.org/licenses/MIT import {LOG_LEVEL, EXAMPLE_LOG_BINDINGS} from '../keys'; -import {Constructor, Reflector} from '@loopback/context'; +import { + Constructor, + MethodDecoratorFactory, + MetadataInspector, +} from '@loopback/context'; import {LevelMetadata} from '../types'; /** @@ -15,15 +19,13 @@ import {LevelMetadata} from '../types'; * @param level The Log Level at or above it should log */ export function log(level?: number) { - return function(target: Object, methodName: string): void { - if (level === undefined) level = LOG_LEVEL.WARN; - Reflector.defineMetadata( - EXAMPLE_LOG_BINDINGS.METADATA, - {level}, - target, - methodName, - ); - }; + if (level === undefined) level = LOG_LEVEL.WARN; + return MethodDecoratorFactory.createDecorator( + EXAMPLE_LOG_BINDINGS.METADATA, + { + level, + }, + ); } /** @@ -36,9 +38,11 @@ export function getLogMetadata( controllerClass: Constructor<{}>, methodName: string, ): LevelMetadata { - return Reflector.getMetadata( - EXAMPLE_LOG_BINDINGS.METADATA, - controllerClass.prototype, - methodName, + return ( + MetadataInspector.getMethodMetadata( + EXAMPLE_LOG_BINDINGS.METADATA, + controllerClass.prototype, + methodName, + ) || {level: LOG_LEVEL.OFF} ); } diff --git a/packages/example-log-extension/src/index.ts b/packages/example-log-extension/src/index.ts index d57d2af4bf4d..7fd3d3b8e4c0 100644 --- a/packages/example-log-extension/src/index.ts +++ b/packages/example-log-extension/src/index.ts @@ -6,7 +6,6 @@ export * from './decorators/log.decorator'; export * from './mixins/log-level.mixin'; export * from './providers/log-action.provider'; -export * from './providers/log-level.provider'; export * from './providers/timer.provider'; export * from './component'; export * from './types'; diff --git a/packages/example-log-extension/src/keys.ts b/packages/example-log-extension/src/keys.ts index b5b12d5ce9a1..48b6ebc3f62e 100644 --- a/packages/example-log-extension/src/keys.ts +++ b/packages/example-log-extension/src/keys.ts @@ -10,6 +10,7 @@ export namespace EXAMPLE_LOG_BINDINGS { export const METADATA = 'example.log.metadata'; export const APP_LOG_LEVEL = 'example.log.level'; export const TIMER = 'example.log.timer'; + export const LOGGER = 'example.log.logger'; export const LOG_ACTION = 'example.log.action'; } diff --git a/packages/example-log-extension/src/providers/log-action.provider.ts b/packages/example-log-extension/src/providers/log-action.provider.ts index 00a37e5059a5..712d047e9172 100644 --- a/packages/example-log-extension/src/providers/log-action.provider.ts +++ b/packages/example-log-extension/src/providers/log-action.provider.ts @@ -8,19 +8,28 @@ import {CoreBindings} from '@loopback/core'; import {OperationArgs, ParsedRequest} from '@loopback/rest'; import {getLogMetadata} from '../decorators/log.decorator'; import {EXAMPLE_LOG_BINDINGS, LOG_LEVEL} from '../keys'; -import {LogFn, TimerFn, HighResTime, LevelMetadata} from '../types'; +import { + LogFn, + TimerFn, + HighResTime, + LevelMetadata, + LogWriterFn, +} from '../types'; import chalk from 'chalk'; -import * as debugModule from 'debug'; -const debug = debugModule('loopback:example:extension:log'); export class LogActionProvider implements Provider { + // LogWriteFn is an optional dependency and it falls back to `logToConsole` + @inject(EXAMPLE_LOG_BINDINGS.LOGGER, {optional: true}) + writeLog: LogWriterFn = logToConsole; + + @inject(EXAMPLE_LOG_BINDINGS.APP_LOG_LEVEL, {optional: true}) + logLevel: number = LOG_LEVEL.WARN; + constructor( @inject.getter(CoreBindings.CONTROLLER_CLASS) private readonly getController: Getter>, @inject.getter(CoreBindings.CONTROLLER_METHOD_NAME) private readonly getMethod: Getter, - @inject(EXAMPLE_LOG_BINDINGS.APP_LOG_LEVEL) - private readonly logLevel: number, @inject(EXAMPLE_LOG_BINDINGS.TIMER) public timer: TimerFn, ) {} @@ -75,24 +84,26 @@ export class LogActionProvider implements Provider { msg = `${time}ms: ${msg}`; } - switch (level) { - case LOG_LEVEL.DEBUG: - this.log(chalk.white(`DEBUG: ${msg}`)); - break; - case LOG_LEVEL.INFO: - this.log(chalk.green(`INFO: ${msg}`)); - break; - case LOG_LEVEL.WARN: - this.log(chalk.yellow(`WARN: ${msg}`)); - break; - case LOG_LEVEL.ERROR: - this.log(chalk.red(`ERROR: ${msg}`)); - break; - } + this.writeLog(msg, level); } } +} - log(msg: string) { - debug(msg); +function logToConsole(msg: string, level: number) { + let output; + switch (level) { + case LOG_LEVEL.DEBUG: + output = chalk.white(`DEBUG: ${msg}`); + break; + case LOG_LEVEL.INFO: + output = chalk.green(`INFO: ${msg}`); + break; + case LOG_LEVEL.WARN: + output = chalk.yellow(`WARN: ${msg}`); + break; + case LOG_LEVEL.ERROR: + output = chalk.red(`ERROR: ${msg}`); + break; } + if (output) console.log(output); } diff --git a/packages/example-log-extension/src/providers/log-level.provider.ts b/packages/example-log-extension/src/providers/log-level.provider.ts deleted file mode 100644 index 619ad96b0dfd..000000000000 --- a/packages/example-log-extension/src/providers/log-level.provider.ts +++ /dev/null @@ -1,17 +0,0 @@ -// Copyright IBM Corp. 2018. All Rights Reserved. -// Node module: @loopback/example-log-extension -// This file is licensed under the MIT License. -// License text available at https://opensource.org/licenses/MIT - -import {Provider} from '@loopback/context'; -import {LOG_LEVEL} from '../keys'; - -export class LogLevelProvider implements Provider { - constructor() {} - - value(): number { - const level = Number(process.env.LOG_LEVEL); - if (!isNaN(level) && typeof level === 'number') return level; - return LOG_LEVEL.WARN; - } -} diff --git a/packages/example-log-extension/src/types.ts b/packages/example-log-extension/src/types.ts index edec0daeab9d..8e060d95e391 100644 --- a/packages/example-log-extension/src/types.ts +++ b/packages/example-log-extension/src/types.ts @@ -7,6 +7,9 @@ import {ParsedRequest, OperationArgs} from '@loopback/rest'; +/** + * A function to perform REST req/res logging action + */ export interface LogFn { ( req: ParsedRequest, @@ -19,8 +22,22 @@ export interface LogFn { startTimer(): HighResTime; } +/** + * Log level metadata + */ export type LevelMetadata = {level: number}; +/** + * High resolution time as [seconds, nanoseconds]. Used by process.hrtime(). + */ export type HighResTime = [number, number]; // [seconds, nanoseconds] +/** + * Log writing function + */ +export type LogWriterFn = (msg: string, level: number) => void; + +/** + * Timer function for logging + */ export type TimerFn = (start?: HighResTime) => HighResTime; diff --git a/packages/example-log-extension/test/acceptance/log.extension.acceptance.ts b/packages/example-log-extension/test/acceptance/log.extension.acceptance.ts index f243d8c892d6..5382fba8990e 100644 --- a/packages/example-log-extension/test/acceptance/log.extension.acceptance.ts +++ b/packages/example-log-extension/test/acceptance/log.extension.acceptance.ts @@ -20,7 +20,6 @@ import { } from '@loopback/rest'; import { LogComponent, - LogActionProvider, LogLevelMixin, LOG_LEVEL, log, @@ -40,6 +39,9 @@ import chalk from 'chalk'; const SequenceActions = RestBindings.SequenceActions; +import {createLogSpy, restoreLogSpy} from '../log-spy'; +import {logToMemory, resetLogs} from '../in-memory-logger'; + describe('log extension acceptance test', () => { let app: LogApp; let server: RestServer; @@ -66,9 +68,10 @@ describe('log extension acceptance test', () => { beforeEach(createApp); beforeEach(createController); beforeEach(createSequence); - beforeEach(createLogSpy); - afterEach(restoreLogSpy); + beforeEach(resetLogs); + beforeEach(() => (spy = createLogSpy())); + afterEach(() => restoreLogSpy(spy)); it('logs information at DEBUG or higher', async () => { setAppLogToDebug(); @@ -241,6 +244,7 @@ describe('log extension acceptance test', () => { app.component(LogComponent); app.bind(EXAMPLE_LOG_BINDINGS.TIMER).to(timer); + app.bind(EXAMPLE_LOG_BINDINGS.LOGGER).to(logToMemory); server = await app.getServer(RestServer); } @@ -315,12 +319,4 @@ describe('log extension acceptance test', () => { if (!startTime) return [3, 3]; return [2, 2]; } - - function createLogSpy() { - spy = sinon.spy(LogActionProvider.prototype, 'log'); - } - - function restoreLogSpy() { - spy.restore(); - } }); diff --git a/packages/example-log-extension/test/in-memory-logger.ts b/packages/example-log-extension/test/in-memory-logger.ts new file mode 100644 index 000000000000..a5b3f282b223 --- /dev/null +++ b/packages/example-log-extension/test/in-memory-logger.ts @@ -0,0 +1,44 @@ +// Copyright IBM Corp. 2018. All Rights Reserved. +// Node module: @loopback/example-log-extension +// This file is licensed under the MIT License. +// License text available at https://opensource.org/licenses/MIT + +import {LOG_LEVEL} from '../'; +import chalk from 'chalk'; + +export class InMemoryLog { + private entries: string[] = []; + + add(msg?: string) { + if (msg) this.entries.push(msg); + } + + reset() { + this.entries = []; + } +} + +export const inMemLog = new InMemoryLog(); + +export function logToMemory(msg: string, level: number) { + let output; + switch (level) { + case LOG_LEVEL.DEBUG: + output = chalk.white(`DEBUG: ${msg}`); + break; + case LOG_LEVEL.INFO: + output = chalk.green(`INFO: ${msg}`); + break; + case LOG_LEVEL.WARN: + output = chalk.yellow(`WARN: ${msg}`); + break; + case LOG_LEVEL.ERROR: + output = chalk.red(`ERROR: ${msg}`); + break; + } + inMemLog.add(output); +} + +export function resetLogs() { + inMemLog.reset(); +} diff --git a/packages/example-log-extension/test/log-spy.ts b/packages/example-log-extension/test/log-spy.ts new file mode 100644 index 000000000000..4612813b3e10 --- /dev/null +++ b/packages/example-log-extension/test/log-spy.ts @@ -0,0 +1,19 @@ +// Copyright IBM Corp. 2018. All Rights Reserved. +// Node module: @loopback/example-log-extension +// This file is licensed under the MIT License. +// License text available at https://opensource.org/licenses/MIT + +import {sinon} from '@loopback/testlab'; +import {InMemoryLog} from './in-memory-logger'; + +export function createLogSpy() { + return sinon.spy(InMemoryLog.prototype, 'add'); +} + +export function restoreLogSpy(spy: sinon.SinonSpy) { + spy.restore(); +} + +export function createConsoleStub() { + return sinon.stub(console, 'log'); +} diff --git a/packages/example-log-extension/test/unit/providers/log-action.provider.unit.ts b/packages/example-log-extension/test/unit/providers/log-action.provider.unit.ts index ced97445bb5b..ac913bd195e9 100644 --- a/packages/example-log-extension/test/unit/providers/log-action.provider.unit.ts +++ b/packages/example-log-extension/test/unit/providers/log-action.provider.unit.ts @@ -5,27 +5,28 @@ import {sinon} from '@loopback/testlab'; import {ParsedRequest} from '@loopback/rest'; -import {Context} from '@loopback/context'; import { LogActionProvider, LogFn, + LogWriterFn, log, - EXAMPLE_LOG_BINDINGS, LOG_LEVEL, HighResTime, } from '../../..'; -import {CoreBindings} from '@loopback/core'; import chalk from 'chalk'; -describe('LogActionProvider (unit)', () => { +import {createLogSpy, restoreLogSpy, createConsoleStub} from '../../log-spy'; +import {logToMemory} from '../../in-memory-logger'; + +describe('LogActionProvider with in-memory logger', () => { let spy: sinon.SinonSpy; let logger: LogFn; const req = {url: '/test'}; - beforeEach(createLogSpy); - beforeEach(getLogger); + beforeEach(() => (spy = createLogSpy())); + beforeEach(async () => (logger = await getLogger(logToMemory))); - afterEach(restoreLogSpy); + afterEach(() => restoreLogSpy(spy)); it('logs a value without a start time', async () => { const match = chalk.red('ERROR: /test :: TestClass.test() => test message'); @@ -52,32 +53,63 @@ describe('LogActionProvider (unit)', () => { await logger(req, ['test', 'message'], 'test message'); sinon.assert.calledWith(spy, match); }); +}); - async function getLogger() { - class TestClass { - @log(LOG_LEVEL.ERROR) - test() {} - } - - const context: Context = new Context(); - context.bind(CoreBindings.CONTROLLER_CLASS).to(TestClass); - context.bind(CoreBindings.CONTROLLER_METHOD_NAME).to('test'); - context.bind(EXAMPLE_LOG_BINDINGS.APP_LOG_LEVEL).to(LOG_LEVEL.WARN); - context.bind(EXAMPLE_LOG_BINDINGS.TIMER).to(timer); - context.bind(EXAMPLE_LOG_BINDINGS.LOG_ACTION).toProvider(LogActionProvider); - logger = await context.get(EXAMPLE_LOG_BINDINGS.LOG_ACTION); - } +describe('LogActionProvider with default logger', () => { + let stub: sinon.SinonSpy; + let logger: LogFn; + const req = {url: '/test'}; - function createLogSpy() { - spy = sinon.spy(LogActionProvider.prototype, 'log'); - } + beforeEach(() => (stub = createConsoleStub())); + beforeEach(async () => (logger = await getLogger())); - function restoreLogSpy() { - spy.restore(); - } + afterEach(() => restoreLogSpy(stub)); - function timer(startTime?: HighResTime): HighResTime { - if (!startTime) return [3, 3]; - else return [0, 100000002]; - } + it('logs a value without a start time', async () => { + const match = chalk.red('ERROR: /test :: TestClass.test() => test message'); + + await logger(req, [], 'test message'); + sinon.assert.calledWith(stub, match); + }); + + it('logs a value with a start time', async () => { + const match = chalk.red( + 'ERROR: 100ms: /test :: TestClass.test() => test message', + ); + const startTime: HighResTime = logger.startTimer(); + + await logger(req, [], 'test message', startTime); + sinon.assert.calledWith(stub, match); + }); + + it('logs a value with args present', async () => { + const match = chalk.red( + 'ERROR: /test :: TestClass.test(test, message) => test message', + ); + + await logger(req, ['test', 'message'], 'test message'); + sinon.assert.calledWith(stub, match); + }); }); + +async function getLogger(logWriter?: LogWriterFn) { + class TestClass { + @log(LOG_LEVEL.ERROR) + test() {} + } + + const provider = new LogActionProvider( + () => Promise.resolve(TestClass), + () => Promise.resolve('test'), + timer, + ); + + if (logWriter) provider.writeLog = logWriter; + + return provider.value(); +} + +function timer(startTime?: HighResTime): HighResTime { + if (!startTime) return [3, 3]; + else return [0, 100000002]; +} diff --git a/packages/example-log-extension/test/unit/providers/log-level.provider.unit.ts b/packages/example-log-extension/test/unit/providers/log-level.provider.unit.ts deleted file mode 100644 index 259d7f010469..000000000000 --- a/packages/example-log-extension/test/unit/providers/log-level.provider.unit.ts +++ /dev/null @@ -1,14 +0,0 @@ -// Copyright IBM Corp. 2018. All Rights Reserved. -// Node module: @loopback/example-log-extension -// This file is licensed under the MIT License. -// License text available at https://opensource.org/licenses/MIT - -import {expect} from '@loopback/testlab'; -import {LogLevelProvider, LOG_LEVEL} from '../../..'; - -describe('LogLevelProvider (unit)', () => { - it('returns LOG_LEVEL.WARN as default level', () => { - const level = new LogLevelProvider().value(); - expect(level).to.be.eql(LOG_LEVEL.WARN); - }); -}); From c987b28b07a210444b3c1b593bfd4c98896be300 Mon Sep 17 00:00:00 2001 From: Taranveer Virk Date: Tue, 13 Feb 2018 20:13:39 -0500 Subject: [PATCH 10/20] fix(cli): remove copyright header from generated app (#991) * fix(cli): remove copyright header from generated app use `.template` extension for templates. Rename during project generation. Removes copyright headers from templates. Fixes #944 * fixup! apply feedback - use RegExp for matching instead of extname --- .../cli/generators/app/templates/index.js | 11 ---------- .../app/templates/index.js.template | 6 ++++++ ...application.ts => application.ts.template} | 5 ----- ...troller.ts => ping.controller.ts.template} | 5 ----- .../src/{index.ts => index.ts.template} | 5 ----- .../src/{sequence.ts => sequence.ts.template} | 5 ----- ...st.ts => ping.controller.test.ts.template} | 5 ----- packages/cli/generators/controller/index.js | 4 ++-- ...s => controller-rest-template.ts.template} | 5 ----- .../src/controllers/controller-template.ts | 13 ------------ .../controller-template.ts.template | 8 +++++++ .../generators/extension/templates/index.d.ts | 6 ------ .../extension/templates/index.d.ts.template | 1 + .../generators/extension/templates/index.js | 6 ------ .../extension/templates/index.js.template | 1 + .../extension/templates/src/component.ts | 14 ------------- .../templates/src/component.ts.template | 9 ++++++++ .../extension/templates/src/index.ts | 6 ------ .../extension/templates/src/index.ts.template | 1 + .../cli/generators/project/templates/index.ts | 11 ---------- .../project/templates/index.ts.template | 1 + packages/cli/lib/project-generator.js | 21 +++++++++++++++++++ packages/cli/package.json | 1 + 23 files changed, 51 insertions(+), 99 deletions(-) delete mode 100644 packages/cli/generators/app/templates/index.js create mode 100644 packages/cli/generators/app/templates/index.js.template rename packages/cli/generators/app/templates/src/{application.ts => application.ts.template} (78%) rename packages/cli/generators/app/templates/src/controllers/{ping.controller.ts => ping.controller.ts.template} (72%) rename packages/cli/generators/app/templates/src/{index.ts => index.ts.template} (66%) rename packages/cli/generators/app/templates/src/{sequence.ts => sequence.ts.template} (84%) rename packages/cli/generators/app/templates/test/{ping.controller.test.ts => ping.controller.test.ts.template} (81%) rename packages/cli/generators/controller/templates/src/controllers/{controller-rest-template.ts => controller-rest-template.ts.template} (91%) delete mode 100644 packages/cli/generators/controller/templates/src/controllers/controller-template.ts create mode 100644 packages/cli/generators/controller/templates/src/controllers/controller-template.ts.template delete mode 100644 packages/cli/generators/extension/templates/index.d.ts create mode 100644 packages/cli/generators/extension/templates/index.d.ts.template delete mode 100644 packages/cli/generators/extension/templates/index.js create mode 100644 packages/cli/generators/extension/templates/index.js.template delete mode 100644 packages/cli/generators/extension/templates/src/component.ts create mode 100644 packages/cli/generators/extension/templates/src/component.ts.template delete mode 100644 packages/cli/generators/extension/templates/src/index.ts create mode 100644 packages/cli/generators/extension/templates/src/index.ts.template delete mode 100644 packages/cli/generators/project/templates/index.ts create mode 100644 packages/cli/generators/project/templates/index.ts.template diff --git a/packages/cli/generators/app/templates/index.js b/packages/cli/generators/app/templates/index.js deleted file mode 100644 index e9e309f61490..000000000000 --- a/packages/cli/generators/app/templates/index.js +++ /dev/null @@ -1,11 +0,0 @@ -// Copyright IBM Corp. 2017,2018. All Rights Reserved. -// Node module: @loopback/cli -// This file is licensed under the MIT License. -// License text available at https://opensource.org/licenses/MIT - -const application = (module.exports = require('./dist')); - -if (require.main === module) { - // Run the application - application.main(); -} diff --git a/packages/cli/generators/app/templates/index.js.template b/packages/cli/generators/app/templates/index.js.template new file mode 100644 index 000000000000..5513dd876e24 --- /dev/null +++ b/packages/cli/generators/app/templates/index.js.template @@ -0,0 +1,6 @@ +const application = (module.exports = require('./dist')); + +if (require.main === module) { + // Run the application + application.main(); +} diff --git a/packages/cli/generators/app/templates/src/application.ts b/packages/cli/generators/app/templates/src/application.ts.template similarity index 78% rename from packages/cli/generators/app/templates/src/application.ts rename to packages/cli/generators/app/templates/src/application.ts.template index a89100142af0..aff1d0823908 100644 --- a/packages/cli/generators/app/templates/src/application.ts +++ b/packages/cli/generators/app/templates/src/application.ts.template @@ -1,8 +1,3 @@ -// Copyright IBM Corp. 2017,2018. All Rights Reserved. -// Node module: @loopback/cli -// This file is licensed under the MIT License. -// License text available at https://opensource.org/licenses/MIT - import {ApplicationConfig} from '@loopback/core'; import {RestApplication, RestServer} from '@loopback/rest'; import {PingController} from './controllers/ping.controller'; diff --git a/packages/cli/generators/app/templates/src/controllers/ping.controller.ts b/packages/cli/generators/app/templates/src/controllers/ping.controller.ts.template similarity index 72% rename from packages/cli/generators/app/templates/src/controllers/ping.controller.ts rename to packages/cli/generators/app/templates/src/controllers/ping.controller.ts.template index 1c04490cc1dc..08f2ccd32019 100644 --- a/packages/cli/generators/app/templates/src/controllers/ping.controller.ts +++ b/packages/cli/generators/app/templates/src/controllers/ping.controller.ts.template @@ -1,8 +1,3 @@ -// Copyright IBM Corp. 2017,2018. All Rights Reserved. -// Node module: @loopback/cli -// This file is licensed under the MIT License. -// License text available at https://opensource.org/licenses/MIT - import {ServerRequest} from '@loopback/rest'; import {get} from '@loopback/openapi-v2'; import {inject} from '@loopback/context'; diff --git a/packages/cli/generators/app/templates/src/index.ts b/packages/cli/generators/app/templates/src/index.ts.template similarity index 66% rename from packages/cli/generators/app/templates/src/index.ts rename to packages/cli/generators/app/templates/src/index.ts.template index 9a582f38f33b..beaf7c162e2e 100644 --- a/packages/cli/generators/app/templates/src/index.ts +++ b/packages/cli/generators/app/templates/src/index.ts.template @@ -1,8 +1,3 @@ -// Copyright IBM Corp. 2017,2018. All Rights Reserved. -// Node module: @loopback/cli -// This file is licensed under the MIT License. -// License text available at https://opensource.org/licenses/MIT - import {<%= project.applicationName %>} from './application'; import {ApplicationConfig} from '@loopback/core'; diff --git a/packages/cli/generators/app/templates/src/sequence.ts b/packages/cli/generators/app/templates/src/sequence.ts.template similarity index 84% rename from packages/cli/generators/app/templates/src/sequence.ts rename to packages/cli/generators/app/templates/src/sequence.ts.template index 8c0daad95c6a..66e2a7b9cb05 100644 --- a/packages/cli/generators/app/templates/src/sequence.ts +++ b/packages/cli/generators/app/templates/src/sequence.ts.template @@ -1,8 +1,3 @@ -// Copyright IBM Corp. 2017,2018. All Rights Reserved. -// Node module: @loopback/cli -// This file is licensed under the MIT License. -// License text available at https://opensource.org/licenses/MIT - import {Context, inject} from '@loopback/context'; import { FindRoute, diff --git a/packages/cli/generators/app/templates/test/ping.controller.test.ts b/packages/cli/generators/app/templates/test/ping.controller.test.ts.template similarity index 81% rename from packages/cli/generators/app/templates/test/ping.controller.test.ts rename to packages/cli/generators/app/templates/test/ping.controller.test.ts.template index 700d17b05f98..bcee8172185f 100644 --- a/packages/cli/generators/app/templates/test/ping.controller.test.ts +++ b/packages/cli/generators/app/templates/test/ping.controller.test.ts.template @@ -1,8 +1,3 @@ -// Copyright IBM Corp. 2017,2018. All Rights Reserved. -// Node module: @loopback/cli -// This file is licensed under the MIT License. -// License text available at https://opensource.org/licenses/MIT - import {createClientForHandler, supertest} from '@loopback/testlab'; import {RestServer} from '@loopback/rest'; import {<%= project.applicationName %>} from '../'; diff --git a/packages/cli/generators/controller/index.js b/packages/cli/generators/controller/index.js index 8dee4d5b1768..55ddd47c7ba0 100644 --- a/packages/cli/generators/controller/index.js +++ b/packages/cli/generators/controller/index.js @@ -186,10 +186,10 @@ module.exports = class ControllerGenerator extends ArtifactGenerator { debug(`Artifact filename set to: ${this.artifactInfo.filename}`); } // renames the file - let template = 'controller-template.ts'; + let template = 'controller-template.ts.template'; switch (this.artifactInfo.controllerType) { case ControllerGenerator.REST: - template = 'controller-rest-template.ts'; + template = 'controller-rest-template.ts.template'; break; default: break; diff --git a/packages/cli/generators/controller/templates/src/controllers/controller-rest-template.ts b/packages/cli/generators/controller/templates/src/controllers/controller-rest-template.ts.template similarity index 91% rename from packages/cli/generators/controller/templates/src/controllers/controller-rest-template.ts rename to packages/cli/generators/controller/templates/src/controllers/controller-rest-template.ts.template index 7b1903502030..803333101cda 100644 --- a/packages/cli/generators/controller/templates/src/controllers/controller-rest-template.ts +++ b/packages/cli/generators/controller/templates/src/controllers/controller-rest-template.ts.template @@ -1,8 +1,3 @@ -// Copyright IBM Corp. 2018. All Rights Reserved. -// Node module: @loopback/cli -// This file is licensed under the MIT License. -// License text available at https://opensource.org/licenses/MIT - import {Filter, Where} from '@loopback/repository'; import {post, param, get, put, patch, del} from '@loopback/openapi-v2'; import {inject} from '@loopback/context'; diff --git a/packages/cli/generators/controller/templates/src/controllers/controller-template.ts b/packages/cli/generators/controller/templates/src/controllers/controller-template.ts deleted file mode 100644 index 2f473b236983..000000000000 --- a/packages/cli/generators/controller/templates/src/controllers/controller-template.ts +++ /dev/null @@ -1,13 +0,0 @@ -// Copyright IBM Corp. 2017,2018. All Rights Reserved. -// Node module: @loopback/cli -// This file is licensed under the MIT License. -// License text available at https://opensource.org/licenses/MIT - -// Uncomment these imports to begin using these cool features! - -// import {inject} from @loopback/context; - - -export class <%= name %>Controller { - constructor() {} -} diff --git a/packages/cli/generators/controller/templates/src/controllers/controller-template.ts.template b/packages/cli/generators/controller/templates/src/controllers/controller-template.ts.template new file mode 100644 index 000000000000..5b93945d564f --- /dev/null +++ b/packages/cli/generators/controller/templates/src/controllers/controller-template.ts.template @@ -0,0 +1,8 @@ +// Uncomment these imports to begin using these cool features! + +// import {inject} from @loopback/context; + + +export class <%= name %>Controller { + constructor() {} +} diff --git a/packages/cli/generators/extension/templates/index.d.ts b/packages/cli/generators/extension/templates/index.d.ts deleted file mode 100644 index 458ba04a7fe0..000000000000 --- a/packages/cli/generators/extension/templates/index.d.ts +++ /dev/null @@ -1,6 +0,0 @@ -// Copyright IBM Corp. 2017,2018. All Rights Reserved. -// Node module: @loopback/cli -// This file is licensed under the MIT License. -// License text available at https://opensource.org/licenses/MIT - -export * from './dist'; diff --git a/packages/cli/generators/extension/templates/index.d.ts.template b/packages/cli/generators/extension/templates/index.d.ts.template new file mode 100644 index 000000000000..5703fb5fd91d --- /dev/null +++ b/packages/cli/generators/extension/templates/index.d.ts.template @@ -0,0 +1 @@ +export * from './dist'; diff --git a/packages/cli/generators/extension/templates/index.js b/packages/cli/generators/extension/templates/index.js deleted file mode 100644 index 290f483a6e50..000000000000 --- a/packages/cli/generators/extension/templates/index.js +++ /dev/null @@ -1,6 +0,0 @@ -// Copyright IBM Corp. 2017,2018. All Rights Reserved. -// Node module: @loopback/cli -// This file is licensed under the MIT License. -// License text available at https://opensource.org/licenses/MIT - -module.exports = require('./dist'); diff --git a/packages/cli/generators/extension/templates/index.js.template b/packages/cli/generators/extension/templates/index.js.template new file mode 100644 index 000000000000..cce6933584cd --- /dev/null +++ b/packages/cli/generators/extension/templates/index.js.template @@ -0,0 +1 @@ +module.exports = require('./dist'); diff --git a/packages/cli/generators/extension/templates/src/component.ts b/packages/cli/generators/extension/templates/src/component.ts deleted file mode 100644 index 6a79f12e83bf..000000000000 --- a/packages/cli/generators/extension/templates/src/component.ts +++ /dev/null @@ -1,14 +0,0 @@ -// Copyright IBM Corp. 2017,2018. All Rights Reserved. -// Node module: @loopback/cli -// This file is licensed under the MIT License. -// License text available at https://opensource.org/licenses/MIT - -import {Component, ProviderMap} from '@loopback/core'; - -export class <%= project.componentName %> implements Component { - constructor() {} - - providers?: ProviderMap = { - }; - -} diff --git a/packages/cli/generators/extension/templates/src/component.ts.template b/packages/cli/generators/extension/templates/src/component.ts.template new file mode 100644 index 000000000000..d4c65c56cdab --- /dev/null +++ b/packages/cli/generators/extension/templates/src/component.ts.template @@ -0,0 +1,9 @@ +import {Component, ProviderMap} from '@loopback/core'; + +export class <%= project.componentName %> implements Component { + constructor() {} + + providers?: ProviderMap = { + }; + +} diff --git a/packages/cli/generators/extension/templates/src/index.ts b/packages/cli/generators/extension/templates/src/index.ts deleted file mode 100644 index 28b460b8fdf5..000000000000 --- a/packages/cli/generators/extension/templates/src/index.ts +++ /dev/null @@ -1,6 +0,0 @@ -// Copyright IBM Corp. 2017,2018. All Rights Reserved. -// Node module: @loopback/cli -// This file is licensed under the MIT License. -// License text available at https://opensource.org/licenses/MIT - -export * from './component'; diff --git a/packages/cli/generators/extension/templates/src/index.ts.template b/packages/cli/generators/extension/templates/src/index.ts.template new file mode 100644 index 000000000000..bb824842ced8 --- /dev/null +++ b/packages/cli/generators/extension/templates/src/index.ts.template @@ -0,0 +1 @@ +export * from './component'; diff --git a/packages/cli/generators/project/templates/index.ts b/packages/cli/generators/project/templates/index.ts deleted file mode 100644 index 8e6496387761..000000000000 --- a/packages/cli/generators/project/templates/index.ts +++ /dev/null @@ -1,11 +0,0 @@ -// Copyright IBM Corp. 2017,2018. All Rights Reserved. -// Node module: @loopback/cli -// This file is licensed under the MIT License. -// License text available at https://opensource.org/licenses/MIT - -// NOTE(bajtos) This file is used by TypeScript compiler to resolve imports -// from "test" files against original TypeScript sources in "src" directory. -// As a side effect, `tsc` also produces "dist/index.{js,d.ts,map} files -// that allow test files to import paths pointing to {src,test} root directory, -// which is project root for TS sources but "dist" for transpiled sources. -export * from './src'; diff --git a/packages/cli/generators/project/templates/index.ts.template b/packages/cli/generators/project/templates/index.ts.template new file mode 100644 index 000000000000..8420b1093fdb --- /dev/null +++ b/packages/cli/generators/project/templates/index.ts.template @@ -0,0 +1 @@ +export * from './src'; diff --git a/packages/cli/lib/project-generator.js b/packages/cli/lib/project-generator.js index 5f5c3ba4a57d..2bdcaafb1dc6 100644 --- a/packages/cli/lib/project-generator.js +++ b/packages/cli/lib/project-generator.js @@ -4,6 +4,7 @@ // License text available at https://opensource.org/licenses/MIT 'use strict'; +const rename = require('gulp-rename'); const BaseGenerator = require('./base-generator'); const utils = require('./utils'); @@ -55,6 +56,26 @@ module.exports = class ProjectGenerator extends BaseGenerator { const isValid = utils.validate(this.args[0]); if (typeof isValid === 'string') throw new Error(isValid); } + + this.setupRenameTransformer(); + } + + /** + * Registers a Transform Stream with Yeoman. Removes `.template` extension + * from files that have it during project generation. + */ + setupRenameTransformer() { + this.registerTransformStream( + rename(function(file) { + // extname already contains a leading '.' + const fileName = `${file.basename}${file.extname}`; + const result = fileName.match(/(.+)(.ts|.js)\.template$/); + if (result) { + file.extname = result[2]; + file.basename = result[1]; + } + }) + ); } setOptions() { diff --git a/packages/cli/package.json b/packages/cli/package.json index d1a0f4daf841..4d48c50c6ea6 100644 --- a/packages/cli/package.json +++ b/packages/cli/package.json @@ -42,6 +42,7 @@ "chalk": "^2.3.0", "change-case": "^3.0.1", "debug": "^3.1.0", + "gulp-rename": "^1.2.2", "gunzip-maybe": "^1.4.1", "lodash": "^4.17.4", "minimist": "^1.2.0", From 0593b51424d8d0f40ecfb7ae35d745154379c6cb Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Miroslav=20Bajto=C5=A1?= Date: Mon, 12 Feb 2018 16:14:12 +0100 Subject: [PATCH 11/20] build: configure prettier to ignore ALL json files (#987) --- .prettierignore | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/.prettierignore b/.prettierignore index 2d3791d21ef7..2e982d6e1aae 100644 --- a/.prettierignore +++ b/.prettierignore @@ -1,6 +1,5 @@ packages/*/dist packages/*/api-docs packages/cli/generators/*/templates -package.json -packages/*/package.json +*.json *.md From 712529b4bcf54d74600125198788b9e9eb9e42da Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Miroslav=20Bajto=C5=A1?= Date: Mon, 12 Feb 2018 16:16:01 +0100 Subject: [PATCH 12/20] build: upgrade VSCode tasks.json to version 2.0.0 (#987) --- .vscode/tasks.json | 36 ++++++++++++++++++++---------------- 1 file changed, 20 insertions(+), 16 deletions(-) diff --git a/.vscode/tasks.json b/.vscode/tasks.json index 97bdd4779e77..fbf6c8f6a425 100644 --- a/.vscode/tasks.json +++ b/.vscode/tasks.json @@ -1,41 +1,44 @@ { // See https://go.microsoft.com/fwlink/?LinkId=733558 // for the documentation about the tasks.json format - "version": "0.1.0", + "version": "2.0.0", "tasks": [ { - "taskName": "Install all dependencies", + "label": "Install all dependencies", + "group": "build", "command": "./node_modules/.bin/lerna", - "isShellCommand": true, + "type": "shell", "args": [ "bootstrap" ] }, { - "taskName": "Remove all dependencies", + "label": "Remove all dependencies", + "group": "build", "command": "./node_modules/.bin/lerna", - "isShellCommand": true, + "type": "shell", "args": [ "clean", "--yes" ] }, { - "taskName": "Run all tests", + "label": "Build and run and lint", + "group": "test", "command": "npm", - "isShellCommand": true, + "type": "shell", "args": [ "run", "test", "-s" ], - "problemMatcher": ["$tsc", "$tslint5"], - "isTestCommand": true + "problemMatcher": ["$tsc", "$tslint5"] }, { - "taskName": "Lint all packages", + "label": "Lint all packages", + "group": "test", "command": "npm", - "isShellCommand": true, + "type": "shell", "args": [ "run", "lint" @@ -102,9 +105,9 @@ ] }, { - "taskName": "Compile TypeScript sources", - "isBuildCommand": true, - "isShellCommand": true, + "label": "Compile TypeScript sources", + "group": "build", + "type": "shell", "command": "./node_modules/.bin/lerna", "args": [ "run", @@ -114,8 +117,9 @@ "problemMatcher": "$tsc" }, { - "taskName": "Clean all generated files", - "isShellCommand": true, + "label": "Clean all generated files", + "group": "build", + "type": "shell", "command": "npm", "args": [ "run", From cf4a55c944912fff403c23eb8cda5c6c3cc1b06a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Miroslav=20Bajto=C5=A1?= Date: Tue, 13 Feb 2018 13:41:33 +0100 Subject: [PATCH 13/20] build: remove redundant "mocha" dependencies (#987) Clean up per-package `package.json` files and remove dependency on `mocha` in all places where `lb-mocha` is already in use. --- packages/cli/package.json | 1 - packages/cli/test/utils.js | 1 - packages/example-getting-started/package.json | 2 -- packages/example-log-extension/package.json | 1 - packages/example-rpc-server/package.json | 2 -- .../test/controllers/greet.controller.test.ts | 1 - packages/example-rpc-server/test/servers/rpc-router.test.ts | 1 - 7 files changed, 9 deletions(-) diff --git a/packages/cli/package.json b/packages/cli/package.json index 4d48c50c6ea6..e8e84ecdc8a1 100644 --- a/packages/cli/package.json +++ b/packages/cli/package.json @@ -30,7 +30,6 @@ "glob": "^7.1.2", "mem-fs": "^1.1.3", "mem-fs-editor": "^4.0.0", - "mocha": "^5.0.0", "nsp": "^3.1.0", "rimraf": "^2.6.2", "sinon": "^4.1.2", diff --git a/packages/cli/test/utils.js b/packages/cli/test/utils.js index cf06005a608b..465b71648393 100644 --- a/packages/cli/test/utils.js +++ b/packages/cli/test/utils.js @@ -3,7 +3,6 @@ // This file is licensed under the MIT License. // License text available at https://opensource.org/licenses/MIT -require('mocha'); const expect = require('@loopback/testlab').expect; const path = require('path'); const utils = require('../lib/utils'); diff --git a/packages/example-getting-started/package.json b/packages/example-getting-started/package.json index 7471c4e1b9d8..0b5eabf0723e 100644 --- a/packages/example-getting-started/package.json +++ b/packages/example-getting-started/package.json @@ -35,9 +35,7 @@ "devDependencies": { "@loopback/build": "^4.0.0-alpha.13", "@loopback/testlab": "^4.0.0-alpha.24", - "@types/mocha": "^2.2.46", "@types/node": "^8.5.9", - "mocha": "^5.0.0", "source-map-support": "^0.5.2", "typescript": "^2.5.2" }, diff --git a/packages/example-log-extension/package.json b/packages/example-log-extension/package.json index 4ebb1714dfaf..2e49b364f363 100644 --- a/packages/example-log-extension/package.json +++ b/packages/example-log-extension/package.json @@ -42,7 +42,6 @@ "@loopback/build": "^4.0.0-alpha.13", "@loopback/testlab": "^4.0.0-alpha.24", "@types/debug": "0.0.30", - "mocha": "^4.0.0", "source-map-support": "^0.5.2" }, "dependencies": { diff --git a/packages/example-rpc-server/package.json b/packages/example-rpc-server/package.json index 0335ff0458b5..4e136090294b 100644 --- a/packages/example-rpc-server/package.json +++ b/packages/example-rpc-server/package.json @@ -51,8 +51,6 @@ "devDependencies": { "@loopback/build": "^4.0.0-alpha.13", "@loopback/testlab": "^4.0.0-alpha.24", - "@types/mocha": "^2.2.43", - "mocha": "^5.0.0", "source-map-support": "^0.5.2" } } diff --git a/packages/example-rpc-server/test/controllers/greet.controller.test.ts b/packages/example-rpc-server/test/controllers/greet.controller.test.ts index 6ccb50f00f03..80e1f7c530e2 100644 --- a/packages/example-rpc-server/test/controllers/greet.controller.test.ts +++ b/packages/example-rpc-server/test/controllers/greet.controller.test.ts @@ -3,7 +3,6 @@ // This file is licensed under the MIT License. // License text available at https://opensource.org/licenses/MIT -import 'mocha'; import {GreetController} from '../../src/controllers'; import {expect} from '@loopback/testlab'; diff --git a/packages/example-rpc-server/test/servers/rpc-router.test.ts b/packages/example-rpc-server/test/servers/rpc-router.test.ts index 402839f89c51..d32b8340c34d 100644 --- a/packages/example-rpc-server/test/servers/rpc-router.test.ts +++ b/packages/example-rpc-server/test/servers/rpc-router.test.ts @@ -3,7 +3,6 @@ // This file is licensed under the MIT License. // License text available at https://opensource.org/licenses/MIT -import 'mocha'; import * as express from 'express'; import {RPCServer, routeHandler} from '../../src/servers'; import {expect, sinon} from '@loopback/testlab'; From baf9928613ccde82c02abbc8249f4163e6a5570b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Miroslav=20Bajto=C5=A1?= Date: Tue, 13 Feb 2018 13:54:04 +0100 Subject: [PATCH 14/20] chore: clean up VSCode tasks (#987) - Use npm scripts instead of invoking lerna directly - Clean up task names --- .vscode/tasks.json | 38 ++++++++++++++++++++++---------------- 1 file changed, 22 insertions(+), 16 deletions(-) diff --git a/.vscode/tasks.json b/.vscode/tasks.json index fbf6c8f6a425..8e5d3d58636f 100644 --- a/.vscode/tasks.json +++ b/.vscode/tasks.json @@ -6,31 +6,34 @@ { "label": "Install all dependencies", "group": "build", - "command": "./node_modules/.bin/lerna", + "command": "npm", "type": "shell", "args": [ + "run", + "-s", "bootstrap" - ] + ], + "problemMatcher": [] }, { "label": "Remove all dependencies", "group": "build", - "command": "./node_modules/.bin/lerna", + "command": "npm", "type": "shell", "args": [ - "clean", - "--yes" - ] + "run", + "-s", + "clean:lerna" + ], + "problemMatcher": [] }, { - "label": "Build and run and lint", + "label": "Build, test and lint", "group": "test", "command": "npm", "type": "shell", "args": [ - "run", - "test", - "-s" + "test" ], "problemMatcher": ["$tsc", "$tslint5"] }, @@ -41,6 +44,7 @@ "type": "shell", "args": [ "run", + "-s", "lint" ], "problemMatcher": [ @@ -82,7 +86,7 @@ ], "severity": "error", "pattern": { - "regexp": "^Error at ([^\/]\\S.*):(\\d+):(\\d+):\\s+(.*)$", + "regexp": "^Error at ([^/]\\S.*):(\\d+):(\\d+):\\s+(.*)$", "file": 1, "line": 2, "column": 3, @@ -105,26 +109,28 @@ ] }, { - "label": "Compile TypeScript sources", + "label": "Build project", "group": "build", "type": "shell", - "command": "./node_modules/.bin/lerna", + "command": "npm", "args": [ "run", - "--loglevel=silent", + "-s", "build" ], "problemMatcher": "$tsc" }, { - "label": "Clean all generated files", + "label": "Clean project", "group": "build", "type": "shell", "command": "npm", "args": [ "run", + "-s", "clean" - ] + ], + "problemMatcher": [] } ] } From a27e856fa4ce1c63a9e20ae58baf0bdab6ab3df3 Mon Sep 17 00:00:00 2001 From: Taranveer Virk Date: Thu, 15 Feb 2018 11:32:24 -0500 Subject: [PATCH 15/20] feat(cli): switch .template to .ejs (#996) * feat(cli): switch .template to .ejs * revert: undo removing dist6 from templates --- .../app/templates/{index.js.template => index.js.ejs} | 0 .../{application.ts.template => application.ts.ejs} | 0 ...g.controller.ts.template => ping.controller.ts.ejs} | 0 .../templates/src/{index.ts.template => index.ts.ejs} | 0 .../src/{sequence.ts.template => sequence.ts.ejs} | 0 ...er.test.ts.template => ping.controller.test.ts.ejs} | 0 .../templates/{index.d.ts.template => index.d.ts.ejs} | 0 .../templates/{index.js.template => index.js.ejs} | 0 .../src/{component.ts.template => component.ts.ejs} | 0 .../templates/src/{index.ts.template => index.ts.ejs} | 0 .../project/templates/{README.md => README.md.ejs} | 0 .../templates/{index.ts.template => index.ts.ejs} | 0 .../templates/{package.json => package.json.ejs} | 0 .../{package.plain.json => package.plain.json.ejs} | 0 .../templates/{tsconfig.json => tsconfig.json.ejs} | 0 .../{tslint.build.json => tslint.build.json.ejs} | 0 .../project/templates/{tslint.json => tslint.json.ejs} | 0 packages/cli/lib/project-generator.js | 10 +++++----- 18 files changed, 5 insertions(+), 5 deletions(-) rename packages/cli/generators/app/templates/{index.js.template => index.js.ejs} (100%) rename packages/cli/generators/app/templates/src/{application.ts.template => application.ts.ejs} (100%) rename packages/cli/generators/app/templates/src/controllers/{ping.controller.ts.template => ping.controller.ts.ejs} (100%) rename packages/cli/generators/app/templates/src/{index.ts.template => index.ts.ejs} (100%) rename packages/cli/generators/app/templates/src/{sequence.ts.template => sequence.ts.ejs} (100%) rename packages/cli/generators/app/templates/test/{ping.controller.test.ts.template => ping.controller.test.ts.ejs} (100%) rename packages/cli/generators/extension/templates/{index.d.ts.template => index.d.ts.ejs} (100%) rename packages/cli/generators/extension/templates/{index.js.template => index.js.ejs} (100%) rename packages/cli/generators/extension/templates/src/{component.ts.template => component.ts.ejs} (100%) rename packages/cli/generators/extension/templates/src/{index.ts.template => index.ts.ejs} (100%) rename packages/cli/generators/project/templates/{README.md => README.md.ejs} (100%) rename packages/cli/generators/project/templates/{index.ts.template => index.ts.ejs} (100%) rename packages/cli/generators/project/templates/{package.json => package.json.ejs} (100%) rename packages/cli/generators/project/templates/{package.plain.json => package.plain.json.ejs} (100%) rename packages/cli/generators/project/templates/{tsconfig.json => tsconfig.json.ejs} (100%) rename packages/cli/generators/project/templates/{tslint.build.json => tslint.build.json.ejs} (100%) rename packages/cli/generators/project/templates/{tslint.json => tslint.json.ejs} (100%) diff --git a/packages/cli/generators/app/templates/index.js.template b/packages/cli/generators/app/templates/index.js.ejs similarity index 100% rename from packages/cli/generators/app/templates/index.js.template rename to packages/cli/generators/app/templates/index.js.ejs diff --git a/packages/cli/generators/app/templates/src/application.ts.template b/packages/cli/generators/app/templates/src/application.ts.ejs similarity index 100% rename from packages/cli/generators/app/templates/src/application.ts.template rename to packages/cli/generators/app/templates/src/application.ts.ejs diff --git a/packages/cli/generators/app/templates/src/controllers/ping.controller.ts.template b/packages/cli/generators/app/templates/src/controllers/ping.controller.ts.ejs similarity index 100% rename from packages/cli/generators/app/templates/src/controllers/ping.controller.ts.template rename to packages/cli/generators/app/templates/src/controllers/ping.controller.ts.ejs diff --git a/packages/cli/generators/app/templates/src/index.ts.template b/packages/cli/generators/app/templates/src/index.ts.ejs similarity index 100% rename from packages/cli/generators/app/templates/src/index.ts.template rename to packages/cli/generators/app/templates/src/index.ts.ejs diff --git a/packages/cli/generators/app/templates/src/sequence.ts.template b/packages/cli/generators/app/templates/src/sequence.ts.ejs similarity index 100% rename from packages/cli/generators/app/templates/src/sequence.ts.template rename to packages/cli/generators/app/templates/src/sequence.ts.ejs diff --git a/packages/cli/generators/app/templates/test/ping.controller.test.ts.template b/packages/cli/generators/app/templates/test/ping.controller.test.ts.ejs similarity index 100% rename from packages/cli/generators/app/templates/test/ping.controller.test.ts.template rename to packages/cli/generators/app/templates/test/ping.controller.test.ts.ejs diff --git a/packages/cli/generators/extension/templates/index.d.ts.template b/packages/cli/generators/extension/templates/index.d.ts.ejs similarity index 100% rename from packages/cli/generators/extension/templates/index.d.ts.template rename to packages/cli/generators/extension/templates/index.d.ts.ejs diff --git a/packages/cli/generators/extension/templates/index.js.template b/packages/cli/generators/extension/templates/index.js.ejs similarity index 100% rename from packages/cli/generators/extension/templates/index.js.template rename to packages/cli/generators/extension/templates/index.js.ejs diff --git a/packages/cli/generators/extension/templates/src/component.ts.template b/packages/cli/generators/extension/templates/src/component.ts.ejs similarity index 100% rename from packages/cli/generators/extension/templates/src/component.ts.template rename to packages/cli/generators/extension/templates/src/component.ts.ejs diff --git a/packages/cli/generators/extension/templates/src/index.ts.template b/packages/cli/generators/extension/templates/src/index.ts.ejs similarity index 100% rename from packages/cli/generators/extension/templates/src/index.ts.template rename to packages/cli/generators/extension/templates/src/index.ts.ejs diff --git a/packages/cli/generators/project/templates/README.md b/packages/cli/generators/project/templates/README.md.ejs similarity index 100% rename from packages/cli/generators/project/templates/README.md rename to packages/cli/generators/project/templates/README.md.ejs diff --git a/packages/cli/generators/project/templates/index.ts.template b/packages/cli/generators/project/templates/index.ts.ejs similarity index 100% rename from packages/cli/generators/project/templates/index.ts.template rename to packages/cli/generators/project/templates/index.ts.ejs diff --git a/packages/cli/generators/project/templates/package.json b/packages/cli/generators/project/templates/package.json.ejs similarity index 100% rename from packages/cli/generators/project/templates/package.json rename to packages/cli/generators/project/templates/package.json.ejs diff --git a/packages/cli/generators/project/templates/package.plain.json b/packages/cli/generators/project/templates/package.plain.json.ejs similarity index 100% rename from packages/cli/generators/project/templates/package.plain.json rename to packages/cli/generators/project/templates/package.plain.json.ejs diff --git a/packages/cli/generators/project/templates/tsconfig.json b/packages/cli/generators/project/templates/tsconfig.json.ejs similarity index 100% rename from packages/cli/generators/project/templates/tsconfig.json rename to packages/cli/generators/project/templates/tsconfig.json.ejs diff --git a/packages/cli/generators/project/templates/tslint.build.json b/packages/cli/generators/project/templates/tslint.build.json.ejs similarity index 100% rename from packages/cli/generators/project/templates/tslint.build.json rename to packages/cli/generators/project/templates/tslint.build.json.ejs diff --git a/packages/cli/generators/project/templates/tslint.json b/packages/cli/generators/project/templates/tslint.json.ejs similarity index 100% rename from packages/cli/generators/project/templates/tslint.json rename to packages/cli/generators/project/templates/tslint.json.ejs diff --git a/packages/cli/lib/project-generator.js b/packages/cli/lib/project-generator.js index 2bdcaafb1dc6..cd27e05ce4a0 100644 --- a/packages/cli/lib/project-generator.js +++ b/packages/cli/lib/project-generator.js @@ -69,7 +69,7 @@ module.exports = class ProjectGenerator extends BaseGenerator { rename(function(file) { // extname already contains a leading '.' const fileName = `${file.basename}${file.extname}`; - const result = fileName.match(/(.+)(.ts|.js)\.template$/); + const result = fileName.match(/(.+)(.ts|.json|.js|.md)\.ejs$/); if (result) { file.extname = result[2]; file.basename = result[1]; @@ -210,7 +210,7 @@ module.exports = class ProjectGenerator extends BaseGenerator { ); if (!this.projectInfo.tslint) { - this.fs.delete(this.destinationPath('tslint.*json')); + this.fs.delete(this.destinationPath('tslint.*json.ejs')); } if (!this.projectInfo.prettier) { @@ -219,11 +219,11 @@ module.exports = class ProjectGenerator extends BaseGenerator { if (!this.projectInfo.loopbackBuild) { this.fs.move( - this.destinationPath('package.plain.json'), - this.destinationPath('package.json') + this.destinationPath('package.plain.json.ejs'), + this.destinationPath('package.json.ejs') ); } else { - this.fs.delete(this.destinationPath('package.plain.json')); + this.fs.delete(this.destinationPath('package.plain.json.ejs')); } if (!this.projectInfo.mocha) { From 69bd4f773d38eb1896cd2a860da3e429dfe0a0d7 Mon Sep 17 00:00:00 2001 From: Taranveer Virk Date: Thu, 15 Feb 2018 13:51:37 -0500 Subject: [PATCH 16/20] chore(cli): update templates to remove Node 6 remnants (#998) Follow up from #996 and related to dropping Node 6 support. CLI template had references to `dist6` folders which are no longer generated. --- packages/cli/generators/project/templates/.prettierignore | 1 - packages/cli/generators/project/templates/_.gitignore | 2 -- packages/cli/generators/project/templates/tsconfig.json.ejs | 2 +- 3 files changed, 1 insertion(+), 4 deletions(-) diff --git a/packages/cli/generators/project/templates/.prettierignore b/packages/cli/generators/project/templates/.prettierignore index 12693a8cc16d..31c26b65c93a 100644 --- a/packages/cli/generators/project/templates/.prettierignore +++ b/packages/cli/generators/project/templates/.prettierignore @@ -1,4 +1,3 @@ dist -dist6 api-docs *.json diff --git a/packages/cli/generators/project/templates/_.gitignore b/packages/cli/generators/project/templates/_.gitignore index 496a8d74d7bd..317e827297fb 100644 --- a/packages/cli/generators/project/templates/_.gitignore +++ b/packages/cli/generators/project/templates/_.gitignore @@ -62,5 +62,3 @@ api-docs/ # Transpiled JavaScript files from Typescript dist/ -dist6/ - diff --git a/packages/cli/generators/project/templates/tsconfig.json.ejs b/packages/cli/generators/project/templates/tsconfig.json.ejs index fe67c1d17267..cecb553cf8e0 100644 --- a/packages/cli/generators/project/templates/tsconfig.json.ejs +++ b/packages/cli/generators/project/templates/tsconfig.json.ejs @@ -12,7 +12,7 @@ "lib": ["es2017", "dom"], "module": "commonjs", "moduleResolution": "node", - "target": "es6", + "target": "es2017", "sourceMap": true, "declaration": true }, From 99ae1813969986dae63f78a98e38292288dc7812 Mon Sep 17 00:00:00 2001 From: Raymond Feng Date: Thu, 15 Feb 2018 10:43:50 -0800 Subject: [PATCH 17/20] docs: add description of packages for the monorepo --- MONOREPO.md | 67 +++++++++++++++++++++++++++++++++++++++++++++++++++++ README.md | 6 +++++ 2 files changed, 73 insertions(+) create mode 100644 MONOREPO.md diff --git a/MONOREPO.md b/MONOREPO.md new file mode 100644 index 000000000000..ec677909de69 --- /dev/null +++ b/MONOREPO.md @@ -0,0 +1,67 @@ +# loopback-next + +The [loopback-next](https://github.com/strongloop/loopback-next) repository uses +[lerna](https://lernajs.io/) to manage multiple packages for LoopBack 4. + +## Packages + +| Package | npm | Description | +|-----------------------------------------------------------|-------------------------------|---------------------------| +|[build](packages/build) |@loopback/build | A set of common scripts and default configurations to build LoopBack 4 or other TypeScript modules | +|[testlab](packages/testlab) |@loopback/testlib | A collection of test utilities we use to write LoopBack tests | +|[cli](packages/cli) |@loopback/cli | CLI for LoopBack 4 | +|[metadata](packages/metadata) |@loopback/metadata | Utilities to help developers implement TypeScript decorators, define/merge metadata, and inspect metadata | +|[context](packages/context) |@loopback/context | Facilities to manage artifacts and their dependencies in your Node.js applications. The module exposes TypeScript/JavaScript APIs and decorators to register artifacts, declare dependencies, and resolve artifacts by keys. It also serves as an IoC container to support dependency injection. | +|[core](packages/core) |@loopback/core | Define and implement core constructs such as Application and Component | +|[openapi-spec](packages/openapi-spec) |@loopback/openapi-spec | TypeScript type definitions for OpenAPI Spec/Swagger documents | +|[openapi-spec-builder](packages/openapi-spec-builder) |@loopback/openapi-spec-builder | Builders to create OpenAPI (Swagger) specification documents in tests | +|[openapi-v2](packages/openapi-v2) |@loopback/openapi-v2 | Decorators that annotate LoopBack artifacts with OpenAPI v2 (Swagger) metadata and utilities that transform LoopBack metadata to OpenAPI v2 (Swagger) specifications| +|[rest](packages/rest) |@loopback/rest | Expose controllers as REST endpoints and route REST API requests to controller methods | +|[repository](packages/repository) |@loopback/repository | Define and implement a common set of interfaces for interacting with databases| +|[repository-json-schema](packages/repository-json-schema) |@loopback/repository-json-schema| Convert a TypeScript class/model to a JSON Schema | +|[authentication](packages/authentication) |@loopback/authentication | A component for authentication support | +|[example-hello-world](packages/example-hello-world) | | A simple hello-world application using LoopBack 4 | +|[example-getting-started](packages/example-getting-started)| | A basic tutorial for getting started with Loopback 4 | +|[example-log-extension](packages/example-log-extension) | | An example showing how to write a complex log extension for LoopBack 4 | +|[example-rpc-server](packages/example-rpc-server) | | An example RPC server and application to demonstrate the creation of your own custom server | + +## Working with the repository + +We use npm scripts declared in [package.json](package.json) to work with the +monorepo managed by lerna. + +### Set up the project +```sh +git clone https://github.com/strongloop/loopback-next.git +cd loopback-next +npm run bootstrap +``` + +### Common tasks + +| Task | Command | Description | +|------------------|-----------------------|-------------| +|Bootstrap packages|`npm run bootstrap` |Install npm dependencies for all packages and create symbolic links for intra-dependencies. It's required for the initial setup or the list of packages is changed | +|Build packages |`npm run build` |Transpile TypeScript files into JavaScript | +|Run tests |`npm test` |Clean, build, run mocha tests, and perform lint checks | +|Fix lint issues |`npm run lint:fix` |Fix lint issues, including tslint rules and prettier formatting | + +### Build a release + +When we are ready to tag and publish a release, run the following commands: +```sh +cd loopback-next +git checkout master +git pull +npm run release +``` + +The `release` script will automatically perform the tasks for all packages: + +- Clean up `node_modules` +- Install/link dependencies +- Transpile TypeScript files into JavaScript +- Run mocha tests +- Check lint (tslint and prettier) issues + +If all steps are successful, it prompts you to publish packages into npm repository. diff --git a/README.md b/README.md index 0716e658f3d2..942d001aff07 100644 --- a/README.md +++ b/README.md @@ -9,6 +9,11 @@ LoopBack makes it easy to build modern applications that require complex integra - Define your data and endpoints with OpenAPI - No maintenance of generated code +# Work with this repository + +This repository uses [lerna](https://lernajs.io/) to manage multiple packages/modules +for LoopBack 4. Please see [MONOREPO.md](MONOREPO.md) for instructions to work with this monorepo. + # Status: Developer Preview #1 LoopBack 4 is a work in progress, the public API is frequently changed in @@ -82,6 +87,7 @@ See [all contributors](https://github.com/strongloop/loopback-next/graphs/contri # Contributing +- [Working with this repository](MONOREPO.md) - [Guidelines](https://github.com/strongloop/loopback-next/wiki/Contributing#guidelines) - [Join the team](https://github.com/strongloop/loopback-next/issues/110) From 14f63337524f42e41cc2b34497a46820284d4568 Mon Sep 17 00:00:00 2001 From: Kevin Delisle Date: Thu, 15 Feb 2018 13:09:55 -0500 Subject: [PATCH 18/20] test(cli): disable GitHub cloning tests The check for equality on example packages after cloning prevents update of the contents of those packages, since they are no longer equivalent! As a result, we are disabling this test for now until we can come up with something that suitably validates the behaviour we expect, but in a way that doesn't prevent future changes from passing tests. --- packages/cli/test/clone-example.test.js | 14 +++++++++++++- 1 file changed, 13 insertions(+), 1 deletion(-) diff --git a/packages/cli/test/clone-example.test.js b/packages/cli/test/clone-example.test.js index f07e15fc5038..0e4f404c55b8 100644 --- a/packages/cli/test/clone-example.test.js +++ b/packages/cli/test/clone-example.test.js @@ -17,11 +17,23 @@ const VALID_EXAMPLE = 'getting-started'; const SANDBOX_PATH = path.resolve(__dirname, 'sandbox'); let sandbox; -describe('cloneExampleFromGitHub (SLOW)', function() { +describe.skip('cloneExampleFromGitHub (SLOW)', function() { this.timeout(20000); before(createSandbox); beforeEach(resetSandbox); + /** + * FIXME(kjdelisle): This test will prevent any meaningful changes from + * landing in example repositories, since it will always fail the equality + * test provided when new files are added, or when existing files are + * removed as a part of refactor/cleanup. + * + * While I do value the idea of verifying that the example packages are + * being cloned properly, we can't hang that idea on validating the + * particular presence of any content that isn't perpetually required. + * + * This test can be removed once strongloop/loopback-next#932 is complete. + */ it('extracts all project files', () => { return cloneExampleFromGitHub(VALID_EXAMPLE, SANDBOX_PATH) .then(outDir => { From e5410b6e2a24cfbe252c1b0935e6242edfd50f99 Mon Sep 17 00:00:00 2001 From: Kevin Delisle Date: Thu, 15 Feb 2018 12:42:59 -0500 Subject: [PATCH 19/20] docs(example-getting-started): split tutorial into separate files Break up the pieces of the tutorial so that they can be served as individual links on LoopBack.io under the LB4 docs. re #729 --- packages/example-getting-started/README.md | 391 ------------------ .../docs/1-prerequisites-and-setup.md | 30 ++ .../docs/2-scaffold-app.md | 13 + .../docs/3-add-legacy-juggler.md | 54 +++ .../docs/4-todo-model.md | 104 +++++ .../docs/5-datasource.md | 34 ++ .../docs/6-repository.md | 28 ++ .../docs/7-controller.md | 65 +++ .../docs/8-putting-it-together.md | 54 +++ 9 files changed, 382 insertions(+), 391 deletions(-) create mode 100644 packages/example-getting-started/docs/1-prerequisites-and-setup.md create mode 100644 packages/example-getting-started/docs/2-scaffold-app.md create mode 100644 packages/example-getting-started/docs/3-add-legacy-juggler.md create mode 100644 packages/example-getting-started/docs/4-todo-model.md create mode 100644 packages/example-getting-started/docs/5-datasource.md create mode 100644 packages/example-getting-started/docs/6-repository.md create mode 100644 packages/example-getting-started/docs/7-controller.md create mode 100644 packages/example-getting-started/docs/8-putting-it-together.md diff --git a/packages/example-getting-started/README.md b/packages/example-getting-started/README.md index 29c922893e4d..c8eba4a84872 100644 --- a/packages/example-getting-started/README.md +++ b/packages/example-getting-started/README.md @@ -2,397 +2,6 @@ This is the basic tutorial for getting started with Loopback 4! -**NOTICE**: This tutorial is currently under construction! This notice will be -removed when it is ready for use! - -## Prerequisites - -Before we can begin, you'll need to make sure you have some things installed: -- [Node.js](https://nodejs.org/en/) at v6.x or greater - -Additionally, this tutorial assumes that you are comfortable with -certain technologies, languages and concepts. -- JavaScript (ES6) -- [npm](https://www.npmjs.com/) -- [REST](https://en.wikipedia.org/wiki/Representational_state_transfer) - -## Setup -1. Install the new loopback CLI toolkit. -``` -npm i -g @loopback/cli -``` -2. Download the "getting-started" application. -``` -lb4 example getting-started -``` - -3. Switch to the directory and install dependencies. -``` -cd loopback-example-getting-started && npm i -``` - -4. Start the app! -``` -npm start -``` - -## Tutorial - -Here's a step-by-step guide of how to build this repository! - -### Create your app scaffolding -Install the `@loopback/cli` package. This will give you the command-line -toolkit that can generate a basic REST app for you. -`npm i -g @loopback/cli` - -Next, navigate to whichever directory you'd like to create your new project -and run `lb4`. Follow the prompts to generate your application. For this -tutorial, when prompted with the options for selecting things like whether or -not to enable certain project features (loopback's build, tslint, mocha, etc.), -leave them all enabled. - - - -### Adding Legacy Juggler Capabilities -Jump into the directory for your new application. You'll see a folder structure -similar to this: -``` -dist\ -node_modules\ -src\ - controllers\ - ping.controller.ts - README.md - repositories\ - README.md - application.ts - index.ts -test\ - mocha.opts - ping.controller.test.ts - README.md -index.js -index.d.ts -index.ts -``` - -The application template comes with a controller, and some default wireup in -`src/application.ts` that handles the basic configuration for your application. -For this tutorial, we won't need `ping.controller.ts` or its corresponding test, -but you can leave them in for now. - -Now that you have your setup, it's time to modify it to add in -`@loopback/repository`. Install this dependency by running -`npm i --save @loopback/repository`. - -Next, modify `src/application.ts` to change the base class of your app to use -the `RepositoryMixin`: - -#### src/application.ts -```ts -import {ApplicationConfig} from '@loopback/core'; -import {RestApplication} from '@loopback/rest'; -import {PingController} from './controllers/ping-controller'; -import {Class, Repository, RepositoryMixin} from '@loopback/repository'; - -export class TodoApplication extends RepositoryMixin(RestApplication) { - constructor(options?: ApplicationConfig) { - super(options); - this.setupControllers(); - } - - setupControllers() { - this.controller(PingController); - } -} -``` - -### Building the Todo model -The Todo model will be the object we use both as a Data Transfer Object (DTO) on -the controller, and as a LoopBack model for the Legacy Juggler implementation. - -Create another folder in `src` called `repositories` and inside of that folder, -create two files: -- `index.ts` -- `todo.repository.ts` - ->**NOTE:** -The `index.ts` file is an export helper file; this pattern is a huge time-saver -as the number of models in your project grows, because it allows you to point -to the _directory_ when attempting to import types from a file within the target -folder. We will use this concept throughout the tutorial! -```ts -// in src/models/index.ts -export * from './foo.model'; -export * from './bar.model'; -export * from './baz.model'; - -// elsewhere... - -// with index.ts -import {Foo, Bar, Baz} from './models'; -// ...and without index.ts -import {Foo} from './models/foo.model'; -import {Bar} from './models/bar.model'; -import {Baz} from './models/baz.model'; -``` - -In our Todo model, we'll create a basic representation of what would go in -a Todo list. Our model will include: -- a unique id -- a title -- a description that details what the todo is all about -- a boolean flag for whether or not we've completed the task. - -For the Legacy Juggler to understand how to work with our model class, it -will need to extend the `Entity` type, as well as provide an override for -the `getId` function, so that it can retrieve a Todo model's ID as needed. - -Additionally, we'll define a `SchemaObject` that represents our Todo model -as an [OpenAPI Schema Object](https://github.com/OAI/OpenAPI-Specification/blob/master/versions/3.0.0.md#schema-object). -This will give the OpenAPI spec builder the information it needs to describe the -Todo model on your app's OpenAPI endpoints. - -#### src/models/todo.model.ts -```ts -import {Entity, property, model} from '@loopback/repository'; -import {SchemaObject} from '@loopback/openapi-spec'; - -@model() -export class Todo extends Entity { - @property({ - type: 'number', - id: true - }) - id?: number; - - @property({ - type: 'string', - required: true - }) - title: string; - - @property({ - type: 'string' - }) - desc?: string; - - @property({ - type: 'boolean' - }) - isComplete: boolean; - - getId() { - return this.id; - } -} - -export const TodoSchema: SchemaObject = { - title: 'todoItem', - properties: { - id: { - type: 'number', - description: 'ID number of the Todo entry.' - }, - title: { - type: 'string', - description: 'Title of the Todo entry.' - }, - desc: { - type: 'number', - description: 'ID number of the Todo entry.' - }, - isComplete: { - type: 'boolean', - description: 'Whether or not the Todo entry is complete.' - } - }, - required: ['title'], -}; -``` - -### Building a Datasource -Before we can begin constructing controllers and repositories for our -application, we need to define our datasource. - -Create a new folder in the root directory of the project called `config`, -and then inside that folder, create a `datasources.json` file. For now, we'll -be using the memory connector provided with the Juggler. - -#### config/datasources.json -```json -{ - "name": "ds", - "connector": "memory" -} -``` - -Create another folder called `datasources` in the `src` directory, and inside -that folder, create a new file called `db.datasource.ts`. - -#### src/datasources/db.datasource.ts - -```ts -import * as path from 'path'; -import * as fs from 'fs'; -import { DataSourceConstructor, juggler } from '@loopback/repository'; - -const dsConfigPath = path.resolve('config', 'datasources.json'); -const config = require(dsConfigPath); -export const db = new DataSourceConstructor(config); -``` - -This will give us a strongly-typed datasource export that we can work with to -construct our TodoRepository definition. - -### Create your repository -Create another folder in `src` called `repositories` and inside of that folder, -create two files: -- `index.ts` (our export helper) -- `todo.repository.ts` - -Our TodoRepository will contain a small base class that uses the -`DefaultCrudRepository` class from `@loopback/repository` and will define the -model type we're working with, as well as its ID type. We'll also inject our -datasource so that this repository can connect to it when executing data -operations. - -#### src/repositories/todo.repository.ts -```ts -import { DefaultCrudRepository, DataSourceType } from '@loopback/repository'; -import { Todo } from '../models'; -import { inject } from '@loopback/core'; - -export class TodoRepository extends DefaultCrudRepository< - Todo, - typeof Todo.prototype.id -> { - constructor(@inject('datasource') protected datasource: DataSourceType) { - super(Todo, datasource); - } -} -``` - - -### Create your controller -Now, we'll create a controller to handle our Todo routes. Create the -`src/controllers` directory and two files inside: -- `index.ts` (export helper) -- `todo.controller.ts` - -In addition to creating the CRUD methods themselves, we'll also be adding -decorators that setup the routing as well as the expected parameters of -incoming requests. - -#### src/controllers/todo.controller.ts -```ts -import {post, param, get, put, patch, del} from '@loopback/openapi-v2'; -import {HttpErrors} from '@loopback/rest'; -import {TodoSchema, Todo} from '../models'; -import {repository} from '@loopback/repository'; -import {TodoRepository} from '../repositories/index'; - -export class TodoController { - constructor( - @repository(TodoRepository.name) protected todoRepo: TodoRepository, - ) {} - @post('/todo') - @param.body('todo', TodoSchema) - async createTodo(todo: Todo) { - if (!todo.title) { - return Promise.reject(new HttpErrors.BadRequest('title is required')); - } - return await this.todoRepo.create(todo); - } - - @get('/todo/{id}') - @param.path.number('id') - @param.query.boolean('items') - async findTodoById(id: number, items?: boolean): Promise { - return await this.todoRepo.findById(id); - } - - @get('/todo') - async findTodos(): Promise { - return await this.todoRepo.find(); - } - - @put('/todo/{id}') - @param.path.number('id') - @param.body('todo', TodoSchema) - async replaceTodo(id: number, todo: Todo): Promise { - return await this.todoRepo.replaceById(id, todo); - } - - @patch('/todo/{id}') - @param.path.number('id') - @param.body('todo', TodoSchema) - async updateTodo(id: number, todo: Todo): Promise { - return await this.todoRepo.updateById(id, todo); - } - - @del('/todo/{id}') - @param.path.number('id') - async deleteTodo(id: number): Promise { - return await this.todoRepo.deleteById(id); - } -} -``` - -### Putting it all together - -Now that we've got all of our artifacts made, let's set them up in our -application! - -We'll define a new helper function for setting up the repositories, as well -as adding in our new controller binding. - -#### src/application.ts -```ts -import {ApplicationConfig} from '@loopback/core'; -import {RestApplication} from '@loopback/rest'; -import {TodoController, PingController} from './controllers'; -import { - Class, - Repository, - RepositoryMixin, - DataSourceConstructor, -} from '@loopback/repository'; -import {db} from './datasources/db.datasource'; -import {TodoRepository} from './repositories'; - -export class TodoApplication extends RepositoryMixin(RestApplication) { - constructor(options?: ApplicationConfig) { - super(options); - this.setupControllers(); - this.setupRepositories(); - } - - setupControllers() { - this.controller(TodoController); - this.controller(PingController); - } - - setupRepositories() { - // This will allow you to test your application without needing to - // use the "real" datasource! - const datasource = - this.options && this.options.datasource - ? new DataSourceConstructor(this.options.datasource) - : db; - this.bind('datasource').to(datasource); - this.repository(TodoRepository); - } -} -``` - -### Try it out -Now that your app is ready to go, try it out with your favourite REST client! -Start the app (`npm start`) and then make some REST requests: -- `POST /todo` with a body of `{ "title": "get the milk" }` -- `GET /todo/1` and see if you get your Todo object back. -- `PATCH /todo/1` with a body of `{ "desc": "need milk for cereal" }` - ### Stuck? Check out our [Gitter channel](https://gitter.im/strongloop/loopback) and ask for help with this tutorial! diff --git a/packages/example-getting-started/docs/1-prerequisites-and-setup.md b/packages/example-getting-started/docs/1-prerequisites-and-setup.md new file mode 100644 index 000000000000..cb75fb59965d --- /dev/null +++ b/packages/example-getting-started/docs/1-prerequisites-and-setup.md @@ -0,0 +1,30 @@ +## Prerequisites + +Before we can begin, you'll need to make sure you have some things installed: +- [Node.js](https://nodejs.org/en/) at v6.x or greater + +Additionally, this tutorial assumes that you are comfortable with +certain technologies, languages and concepts. +- JavaScript (ES6) +- [npm](https://www.npmjs.com/) +- [REST](https://en.wikipedia.org/wiki/Representational_state_transfer) + +## Setup +1. Install the new loopback CLI toolkit. +``` +npm i -g @loopback/cli +``` +2. Download the "getting-started" application. +``` +lb4 example getting-started +``` + +3. Switch to the directory and install dependencies. +``` +cd loopback-example-getting-started && npm i +``` + +4. Start the app! +``` +npm start +``` diff --git a/packages/example-getting-started/docs/2-scaffold-app.md b/packages/example-getting-started/docs/2-scaffold-app.md new file mode 100644 index 000000000000..edb3a160adfc --- /dev/null +++ b/packages/example-getting-started/docs/2-scaffold-app.md @@ -0,0 +1,13 @@ +### Create your app scaffolding + +Install the `@loopback/cli` package. This will give you the command-line +toolkit that can generate a basic REST app for you. +`npm i -g @loopback/cli` + +Next, navigate to whichever directory you'd like to create your new project +and run `lb4`. Follow the prompts to generate your application. For this +tutorial, when prompted with the options for selecting things like whether or +not to enable certain project features (loopback's build, tslint, mocha, etc.), +leave them all enabled. + + diff --git a/packages/example-getting-started/docs/3-add-legacy-juggler.md b/packages/example-getting-started/docs/3-add-legacy-juggler.md new file mode 100644 index 000000000000..e44d07de19f2 --- /dev/null +++ b/packages/example-getting-started/docs/3-add-legacy-juggler.md @@ -0,0 +1,54 @@ +### Adding Legacy Juggler Capabilities + +Jump into the directory for your new application. You'll see a folder structure +similar to this: +``` +dist\ +node_modules\ +src\ + controllers\ + ping.controller.ts + README.md + repositories\ + README.md + application.ts + index.ts +test\ + mocha.opts + ping.controller.test.ts + README.md +index.js +index.d.ts +index.ts +``` + +The application template comes with a controller, and some default wireup in +`src/application.ts` that handles the basic configuration for your application. +For this tutorial, we won't need `ping.controller.ts` or its corresponding test, +but you can leave them in for now. + +Now that you have your setup, it's time to modify it to add in +`@loopback/repository`. Install this dependency by running +`npm i --save @loopback/repository`. + +Next, modify `src/application.ts` to change the base class of your app to use +the `RepositoryMixin`: + +#### src/application.ts +```ts +import {ApplicationConfig} from '@loopback/core'; +import {RestApplication} from '@loopback/rest'; +import {PingController} from './controllers/ping-controller'; +import {Class, Repository, RepositoryMixin} from '@loopback/repository'; + +export class TodoApplication extends RepositoryMixin(RestApplication) { + constructor(options?: ApplicationConfig) { + super(options); + this.setupControllers(); + } + + setupControllers() { + this.controller(PingController); + } +} +``` diff --git a/packages/example-getting-started/docs/4-todo-model.md b/packages/example-getting-started/docs/4-todo-model.md new file mode 100644 index 000000000000..408758a746a7 --- /dev/null +++ b/packages/example-getting-started/docs/4-todo-model.md @@ -0,0 +1,104 @@ +### Building the Todo model + +The Todo model will be the object we use both as a Data Transfer Object (DTO) on +the controller, and as a LoopBack model for the Legacy Juggler implementation. + +Create another folder in `src` called `repositories` and inside of that folder, +create two files: +- `index.ts` +- `todo.repository.ts` + +>**NOTE:** +The `index.ts` file is an export helper file; this pattern is a huge time-saver +as the number of models in your project grows, because it allows you to point +to the _directory_ when attempting to import types from a file within the target +folder. We will use this concept throughout the tutorial! +```ts +// in src/models/index.ts +export * from './foo.model'; +export * from './bar.model'; +export * from './baz.model'; + +// elsewhere... + +// with index.ts +import {Foo, Bar, Baz} from './models'; +// ...and without index.ts +import {Foo} from './models/foo.model'; +import {Bar} from './models/bar.model'; +import {Baz} from './models/baz.model'; +``` + +In our Todo model, we'll create a basic representation of what would go in +a Todo list. Our model will include: +- a unique id +- a title +- a description that details what the todo is all about +- a boolean flag for whether or not we've completed the task. + +For the Legacy Juggler to understand how to work with our model class, it +will need to extend the `Entity` type, as well as provide an override for +the `getId` function, so that it can retrieve a Todo model's ID as needed. + +Additionally, we'll define a `SchemaObject` that represents our Todo model +as an [OpenAPI Schema Object](https://github.com/OAI/OpenAPI-Specification/blob/master/versions/3.0.0.md#schema-object). +This will give the OpenAPI spec builder the information it needs to describe the +Todo model on your app's OpenAPI endpoints. + +#### src/models/todo.model.ts +```ts +import {Entity, property, model} from '@loopback/repository'; +import {SchemaObject} from '@loopback/openapi-spec'; + +@model() +export class Todo extends Entity { + @property({ + type: 'number', + id: true + }) + id?: number; + + @property({ + type: 'string', + required: true + }) + title: string; + + @property({ + type: 'string' + }) + desc?: string; + + @property({ + type: 'boolean' + }) + isComplete: boolean; + + getId() { + return this.id; + } +} + +export const TodoSchema: SchemaObject = { + title: 'todoItem', + properties: { + id: { + type: 'number', + description: 'ID number of the Todo entry.' + }, + title: { + type: 'string', + description: 'Title of the Todo entry.' + }, + desc: { + type: 'number', + description: 'ID number of the Todo entry.' + }, + isComplete: { + type: 'boolean', + description: 'Whether or not the Todo entry is complete.' + } + }, + required: ['title'], +}; +``` diff --git a/packages/example-getting-started/docs/5-datasource.md b/packages/example-getting-started/docs/5-datasource.md new file mode 100644 index 000000000000..9cc2ff210e2a --- /dev/null +++ b/packages/example-getting-started/docs/5-datasource.md @@ -0,0 +1,34 @@ +### Building a Datasource + +Before we can begin constructing controllers and repositories for our +application, we need to define our datasource. + +Create a new folder in the root directory of the project called `config`, +and then inside that folder, create a `datasources.json` file. For now, we'll +be using the memory connector provided with the Juggler. + +#### config/datasources.json +```json +{ + "name": "ds", + "connector": "memory" +} +``` + +Create another folder called `datasources` in the `src` directory, and inside +that folder, create a new file called `db.datasource.ts`. + +#### src/datasources/db.datasource.ts + +```ts +import * as path from 'path'; +import * as fs from 'fs'; +import { DataSourceConstructor, juggler } from '@loopback/repository'; + +const dsConfigPath = path.resolve('config', 'datasources.json'); +const config = require(dsConfigPath); +export const db = new DataSourceConstructor(config); +``` + +This will give us a strongly-typed datasource export that we can work with to +construct our TodoRepository definition. diff --git a/packages/example-getting-started/docs/6-repository.md b/packages/example-getting-started/docs/6-repository.md new file mode 100644 index 000000000000..b2520590985a --- /dev/null +++ b/packages/example-getting-started/docs/6-repository.md @@ -0,0 +1,28 @@ +### Create your repository + +Create another folder in `src` called `repositories` and inside of that folder, +create two files: +- `index.ts` (our export helper) +- `todo.repository.ts` + +Our TodoRepository will contain a small base class that uses the +`DefaultCrudRepository` class from `@loopback/repository` and will define the +model type we're working with, as well as its ID type. We'll also inject our +datasource so that this repository can connect to it when executing data +operations. + +#### src/repositories/todo.repository.ts +```ts +import { DefaultCrudRepository, DataSourceType } from '@loopback/repository'; +import { Todo } from '../models'; +import { inject } from '@loopback/core'; + +export class TodoRepository extends DefaultCrudRepository< + Todo, + typeof Todo.prototype.id +> { + constructor(@inject('datasource') protected datasource: DataSourceType) { + super(Todo, datasource); + } +} +``` diff --git a/packages/example-getting-started/docs/7-controller.md b/packages/example-getting-started/docs/7-controller.md new file mode 100644 index 000000000000..484cd906b096 --- /dev/null +++ b/packages/example-getting-started/docs/7-controller.md @@ -0,0 +1,65 @@ +### Create your controller + +Now, we'll create a controller to handle our Todo routes. Create the +`src/controllers` directory and two files inside: +- `index.ts` (export helper) +- `todo.controller.ts` + +In addition to creating the CRUD methods themselves, we'll also be adding +decorators that setup the routing as well as the expected parameters of +incoming requests. + +#### src/controllers/todo.controller.ts +```ts +import {post, param, get, put, patch, del} from '@loopback/openapi-v2'; +import {HttpErrors} from '@loopback/rest'; +import {TodoSchema, Todo} from '../models'; +import {repository} from '@loopback/repository'; +import {TodoRepository} from '../repositories/index'; + +export class TodoController { + constructor( + @repository(TodoRepository.name) protected todoRepo: TodoRepository, + ) {} + @post('/todo') + @param.body('todo', TodoSchema) + async createTodo(todo: Todo) { + if (!todo.title) { + return Promise.reject(new HttpErrors.BadRequest('title is required')); + } + return await this.todoRepo.create(todo); + } + + @get('/todo/{id}') + @param.path.number('id') + @param.query.boolean('items') + async findTodoById(id: number, items?: boolean): Promise { + return await this.todoRepo.findById(id); + } + + @get('/todo') + async findTodos(): Promise { + return await this.todoRepo.find(); + } + + @put('/todo/{id}') + @param.path.number('id') + @param.body('todo', TodoSchema) + async replaceTodo(id: number, todo: Todo): Promise { + return await this.todoRepo.replaceById(id, todo); + } + + @patch('/todo/{id}') + @param.path.number('id') + @param.body('todo', TodoSchema) + async updateTodo(id: number, todo: Todo): Promise { + return await this.todoRepo.updateById(id, todo); + } + + @del('/todo/{id}') + @param.path.number('id') + async deleteTodo(id: number): Promise { + return await this.todoRepo.deleteById(id); + } +} +``` diff --git a/packages/example-getting-started/docs/8-putting-it-together.md b/packages/example-getting-started/docs/8-putting-it-together.md new file mode 100644 index 000000000000..11bbac64ecfa --- /dev/null +++ b/packages/example-getting-started/docs/8-putting-it-together.md @@ -0,0 +1,54 @@ +### Putting it all together + +Now that we've got all of our artifacts made, let's set them up in our +application! + +We'll define a new helper function for setting up the repositories, as well +as adding in our new controller binding. + +#### src/application.ts +```ts +import {ApplicationConfig} from '@loopback/core'; +import {RestApplication} from '@loopback/rest'; +import {TodoController, PingController} from './controllers'; +import { + Class, + Repository, + RepositoryMixin, + DataSourceConstructor, +} from '@loopback/repository'; +import {db} from './datasources/db.datasource'; +import {TodoRepository} from './repositories'; + +export class TodoApplication extends RepositoryMixin(RestApplication) { + constructor(options?: ApplicationConfig) { + super(options); + this.setupControllers(); + this.setupRepositories(); + } + + setupControllers() { + this.controller(TodoController); + this.controller(PingController); + } + + setupRepositories() { + // This will allow you to test your application without needing to + // use the "real" datasource! + const datasource = + this.options && this.options.datasource + ? new DataSourceConstructor(this.options.datasource) + : db; + this.bind('datasource').to(datasource); + this.repository(TodoRepository); + } +} +``` + +### Try it out + +Now that your app is ready to go, try it out with your favourite REST client! +Start the app (`npm start`) and then make some REST requests: +- `POST /todo` with a body of `{ "title": "get the milk" }` +- `GET /todo/1` and see if you get your Todo object back. +- `PATCH /todo/1` with a body of `{ "desc": "need milk for cereal" }` From 18eed2dea2c258d26dfbd1329595f0e83d4c3eb3 Mon Sep 17 00:00:00 2001 From: Raymond Feng Date: Thu, 15 Feb 2018 14:57:05 -0800 Subject: [PATCH 20/20] Publish - @loopback/authentication@4.0.0-alpha.33 - @loopback/cli@4.0.0-alpha.23 - @loopback/context@4.0.0-alpha.32 - @loopback/core@4.0.0-alpha.34 - @loopback/example-getting-started@1.0.1-alpha.7 - @loopback/example-hello-world@4.0.0-alpha.4 - @loopback/example-log-extension@4.0.0-alpha.7 - @loopback/example-rpc-server@4.0.0-alpha.5 - @loopback/openapi-v2@4.0.0-alpha.11 - @loopback/repository-json-schema@4.0.0-alpha.8 - @loopback/repository@4.0.0-alpha.30 - @loopback/rest@4.0.0-alpha.26 --- packages/authentication/CHANGELOG.md | 8 ++++++++ packages/authentication/package.json | 10 +++++----- packages/cli/CHANGELOG.md | 16 ++++++++++++++++ packages/cli/package.json | 2 +- packages/context/CHANGELOG.md | 11 +++++++++++ packages/context/package.json | 2 +- packages/core/CHANGELOG.md | 8 ++++++++ packages/core/package.json | 4 ++-- packages/example-getting-started/CHANGELOG.md | 12 ++++++++++++ packages/example-getting-started/package.json | 12 ++++++------ packages/example-hello-world/CHANGELOG.md | 8 ++++++++ packages/example-hello-world/package.json | 6 +++--- packages/example-log-extension/CHANGELOG.md | 11 +++++++++++ packages/example-log-extension/package.json | 8 ++++---- packages/example-rpc-server/CHANGELOG.md | 8 ++++++++ packages/example-rpc-server/package.json | 6 +++--- packages/openapi-v2/CHANGELOG.md | 8 ++++++++ packages/openapi-v2/package.json | 8 ++++---- packages/repository-json-schema/CHANGELOG.md | 8 ++++++++ packages/repository-json-schema/package.json | 6 +++--- packages/repository/CHANGELOG.md | 8 ++++++++ packages/repository/package.json | 6 +++--- packages/rest/CHANGELOG.md | 8 ++++++++ packages/rest/package.json | 10 +++++----- 24 files changed, 154 insertions(+), 40 deletions(-) diff --git a/packages/authentication/CHANGELOG.md b/packages/authentication/CHANGELOG.md index 847268b17067..d5517a5e0d9f 100644 --- a/packages/authentication/CHANGELOG.md +++ b/packages/authentication/CHANGELOG.md @@ -3,6 +3,14 @@ All notable changes to this project will be documented in this file. See [Conventional Commits](https://conventionalcommits.org) for commit guidelines. + +# [4.0.0-alpha.33](https://github.com/strongloop/loopback-next/compare/@loopback/authentication@4.0.0-alpha.32...@loopback/authentication@4.0.0-alpha.33) (2018-02-15) + + + + +**Note:** Version bump only for package @loopback/authentication + # [4.0.0-alpha.32](https://github.com/strongloop/loopback-next/compare/@loopback/authentication@4.0.0-alpha.31...@loopback/authentication@4.0.0-alpha.32) (2018-02-07) diff --git a/packages/authentication/package.json b/packages/authentication/package.json index 746aea76a06d..86c91552c0ea 100644 --- a/packages/authentication/package.json +++ b/packages/authentication/package.json @@ -1,6 +1,6 @@ { "name": "@loopback/authentication", - "version": "4.0.0-alpha.32", + "version": "4.0.0-alpha.33", "description": "A LoopBack component for authentication support.", "engines": { "node": ">=8" @@ -21,10 +21,10 @@ "copyright.owner": "IBM Corp.", "license": "MIT", "dependencies": { - "@loopback/context": "^4.0.0-alpha.31", - "@loopback/core": "^4.0.0-alpha.33", - "@loopback/openapi-v2": "^4.0.0-alpha.10", - "@loopback/rest": "^4.0.0-alpha.25", + "@loopback/context": "^4.0.0-alpha.32", + "@loopback/core": "^4.0.0-alpha.34", + "@loopback/openapi-v2": "^4.0.0-alpha.11", + "@loopback/rest": "^4.0.0-alpha.26", "passport": "^0.4.0", "passport-strategy": "^1.0.0" }, diff --git a/packages/cli/CHANGELOG.md b/packages/cli/CHANGELOG.md index 75c3fde90c58..affa9839f9fc 100644 --- a/packages/cli/CHANGELOG.md +++ b/packages/cli/CHANGELOG.md @@ -3,6 +3,22 @@ All notable changes to this project will be documented in this file. See [Conventional Commits](https://conventionalcommits.org) for commit guidelines. + +# [4.0.0-alpha.23](https://github.com/strongloop/loopback-next/compare/@loopback/cli@4.0.0-alpha.22...@loopback/cli@4.0.0-alpha.23) (2018-02-15) + + +### Bug Fixes + +* **cli:** remove copyright header from generated app ([#991](https://github.com/strongloop/loopback-next/issues/991)) ([c987b28](https://github.com/strongloop/loopback-next/commit/c987b28)), closes [#944](https://github.com/strongloop/loopback-next/issues/944) + + +### Features + +* **cli:** switch .template to .ejs ([#996](https://github.com/strongloop/loopback-next/issues/996)) ([a27e856](https://github.com/strongloop/loopback-next/commit/a27e856)) + + + + # [4.0.0-alpha.22](https://github.com/strongloop/loopback-next/compare/@loopback/cli@4.0.0-alpha.21...@loopback/cli@4.0.0-alpha.22) (2018-02-07) diff --git a/packages/cli/package.json b/packages/cli/package.json index e8e84ecdc8a1..85a4ba3e707f 100644 --- a/packages/cli/package.json +++ b/packages/cli/package.json @@ -1,6 +1,6 @@ { "name": "@loopback/cli", - "version": "4.0.0-alpha.22", + "version": "4.0.0-alpha.23", "description": "Yeoman generator for LoopBack 4", "homepage": "https://github.com/strongloop/loopback-next/tree/master/packages/cli", "author": { diff --git a/packages/context/CHANGELOG.md b/packages/context/CHANGELOG.md index cf5d254a047e..7dd6cbd1ca35 100644 --- a/packages/context/CHANGELOG.md +++ b/packages/context/CHANGELOG.md @@ -3,6 +3,17 @@ All notable changes to this project will be documented in this file. See [Conventional Commits](https://conventionalcommits.org) for commit guidelines. + +# [4.0.0-alpha.32](https://github.com/strongloop/loopback-next/compare/@loopback/context@4.0.0-alpha.31...@loopback/context@4.0.0-alpha.32) (2018-02-15) + + +### Features + +* **context:** formalize injection metadata as an interface ([7ffc1e5](https://github.com/strongloop/loopback-next/commit/7ffc1e5)) + + + + # [4.0.0-alpha.31](https://github.com/strongloop/loopback-next/compare/@loopback/context@4.0.0-alpha.30...@loopback/context@4.0.0-alpha.31) (2018-02-07) diff --git a/packages/context/package.json b/packages/context/package.json index 025c2f1bc813..232b02add0b3 100644 --- a/packages/context/package.json +++ b/packages/context/package.json @@ -1,6 +1,6 @@ { "name": "@loopback/context", - "version": "4.0.0-alpha.31", + "version": "4.0.0-alpha.32", "description": "LoopBack's container for Inversion of Control", "engines": { "node": ">=8" diff --git a/packages/core/CHANGELOG.md b/packages/core/CHANGELOG.md index 6cbbed16599f..af2509c1b187 100644 --- a/packages/core/CHANGELOG.md +++ b/packages/core/CHANGELOG.md @@ -3,6 +3,14 @@ All notable changes to this project will be documented in this file. See [Conventional Commits](https://conventionalcommits.org) for commit guidelines. + +# [4.0.0-alpha.34](https://github.com/strongloop/loopback-next/compare/@loopback/core@4.0.0-alpha.33...@loopback/core@4.0.0-alpha.34) (2018-02-15) + + + + +**Note:** Version bump only for package @loopback/core + # [4.0.0-alpha.33](https://github.com/strongloop/loopback-next/compare/@loopback/core@4.0.0-alpha.32...@loopback/core@4.0.0-alpha.33) (2018-02-07) diff --git a/packages/core/package.json b/packages/core/package.json index e68172f6838f..257efdee6ea1 100644 --- a/packages/core/package.json +++ b/packages/core/package.json @@ -1,6 +1,6 @@ { "name": "@loopback/core", - "version": "4.0.0-alpha.33", + "version": "4.0.0-alpha.34", "description": "", "engines": { "node": ">=8" @@ -21,7 +21,7 @@ "copyright.owner": "IBM Corp.", "license": "MIT", "dependencies": { - "@loopback/context": "^4.0.0-alpha.31" + "@loopback/context": "^4.0.0-alpha.32" }, "devDependencies": { "@loopback/build": "^4.0.0-alpha.13", diff --git a/packages/example-getting-started/CHANGELOG.md b/packages/example-getting-started/CHANGELOG.md index 3678f97c8418..62b8dbeb62d9 100644 --- a/packages/example-getting-started/CHANGELOG.md +++ b/packages/example-getting-started/CHANGELOG.md @@ -3,6 +3,18 @@ All notable changes to this project will be documented in this file. See [Conventional Commits](https://conventionalcommits.org) for commit guidelines. + +## [1.0.1-alpha.7](https://github.com/strongloop/loopback-next/compare/@loopback/example-getting-started@1.0.1-alpha.6...@loopback/example-getting-started@1.0.1-alpha.7) (2018-02-15) + + +### Bug Fixes + +* **example-getting-started:** remove juggler warning ([86139f6](https://github.com/strongloop/loopback-next/commit/86139f6)) +* **example-getting-started:** use sinon from testlab ([#984](https://github.com/strongloop/loopback-next/issues/984)) ([09fc791](https://github.com/strongloop/loopback-next/commit/09fc791)) + + + + ## [1.0.1-alpha.6](https://github.com/strongloop/loopback-next/compare/@loopback/example-getting-started@1.0.1-alpha.5...@loopback/example-getting-started@1.0.1-alpha.6) (2018-02-07) diff --git a/packages/example-getting-started/package.json b/packages/example-getting-started/package.json index 0b5eabf0723e..d8321f150df9 100644 --- a/packages/example-getting-started/package.json +++ b/packages/example-getting-started/package.json @@ -1,6 +1,6 @@ { "name": "@loopback/example-getting-started", - "version": "1.0.1-alpha.6", + "version": "1.0.1-alpha.7", "description": "An application and tutorial on how to build with LoopBack 4.", "private": true, "main": "index.js", @@ -25,12 +25,12 @@ }, "license": "MIT", "dependencies": { - "@loopback/context": "^4.0.0-alpha.31", - "@loopback/core": "^4.0.0-alpha.33", + "@loopback/context": "^4.0.0-alpha.32", + "@loopback/core": "^4.0.0-alpha.34", "@loopback/openapi-spec": "^4.0.0-alpha.25", - "@loopback/openapi-v2": "^4.0.0-alpha.10", - "@loopback/repository": "^4.0.0-alpha.29", - "@loopback/rest": "^4.0.0-alpha.25" + "@loopback/openapi-v2": "^4.0.0-alpha.11", + "@loopback/repository": "^4.0.0-alpha.30", + "@loopback/rest": "^4.0.0-alpha.26" }, "devDependencies": { "@loopback/build": "^4.0.0-alpha.13", diff --git a/packages/example-hello-world/CHANGELOG.md b/packages/example-hello-world/CHANGELOG.md index 799545b19382..e2fe1222dec1 100644 --- a/packages/example-hello-world/CHANGELOG.md +++ b/packages/example-hello-world/CHANGELOG.md @@ -3,6 +3,14 @@ All notable changes to this project will be documented in this file. See [Conventional Commits](https://conventionalcommits.org) for commit guidelines. + +# [4.0.0-alpha.4](https://github.com/strongloop/loopback-next/compare/@loopback/example-hello-world@4.0.0-alpha.3...@loopback/example-hello-world@4.0.0-alpha.4) (2018-02-15) + + + + +**Note:** Version bump only for package @loopback/example-hello-world + # [4.0.0-alpha.3](https://github.com/strongloop/loopback-next/compare/@loopback/example-hello-world@4.0.0-alpha.2...@loopback/example-hello-world@4.0.0-alpha.3) (2018-02-07) diff --git a/packages/example-hello-world/package.json b/packages/example-hello-world/package.json index 4329d85d22aa..71355ab4fc37 100644 --- a/packages/example-hello-world/package.json +++ b/packages/example-hello-world/package.json @@ -1,6 +1,6 @@ { "name": "@loopback/example-hello-world", - "version": "4.0.0-alpha.3", + "version": "4.0.0-alpha.4", "description": "A simple hello-world Application using LoopBack 4", "private": true, "main": "index.js", @@ -22,8 +22,8 @@ }, "license": "MIT", "dependencies": { - "@loopback/core": "^4.0.0-alpha.33", - "@loopback/rest": "^4.0.0-alpha.25" + "@loopback/core": "^4.0.0-alpha.34", + "@loopback/rest": "^4.0.0-alpha.26" }, "devDependencies": { "@loopback/build": "^4.0.0-alpha.13", diff --git a/packages/example-log-extension/CHANGELOG.md b/packages/example-log-extension/CHANGELOG.md index 87517106f5eb..019a9961baa5 100644 --- a/packages/example-log-extension/CHANGELOG.md +++ b/packages/example-log-extension/CHANGELOG.md @@ -3,6 +3,17 @@ All notable changes to this project will be documented in this file. See [Conventional Commits](https://conventionalcommits.org) for commit guidelines. + +# [4.0.0-alpha.7](https://github.com/strongloop/loopback-next/compare/@loopback/example-log-extension@4.0.0-alpha.6...@loopback/example-log-extension@4.0.0-alpha.7) (2018-02-15) + + +### Bug Fixes + +* clean up example-log-extension ([f13f603](https://github.com/strongloop/loopback-next/commit/f13f603)) + + + + # [4.0.0-alpha.6](https://github.com/strongloop/loopback-next/compare/@loopback/example-log-extension@4.0.0-alpha.5...@loopback/example-log-extension@4.0.0-alpha.6) (2018-02-07) diff --git a/packages/example-log-extension/package.json b/packages/example-log-extension/package.json index 2e49b364f363..b041801c3e92 100644 --- a/packages/example-log-extension/package.json +++ b/packages/example-log-extension/package.json @@ -1,6 +1,6 @@ { "name": "@loopback/example-log-extension", - "version": "4.0.0-alpha.6", + "version": "4.0.0-alpha.7", "description": "An example extension project for LoopBack 4", "private": true, "main": "index.js", @@ -45,9 +45,9 @@ "source-map-support": "^0.5.2" }, "dependencies": { - "@loopback/context": "^4.0.0-alpha.31", - "@loopback/core": "^4.0.0-alpha.33", - "@loopback/rest": "^4.0.0-alpha.25", + "@loopback/context": "^4.0.0-alpha.32", + "@loopback/core": "^4.0.0-alpha.34", + "@loopback/rest": "^4.0.0-alpha.26", "chalk": "^2.3.0", "debug": "^3.1.0" } diff --git a/packages/example-rpc-server/CHANGELOG.md b/packages/example-rpc-server/CHANGELOG.md index 61c4be10ffe5..476ad135b79d 100644 --- a/packages/example-rpc-server/CHANGELOG.md +++ b/packages/example-rpc-server/CHANGELOG.md @@ -3,6 +3,14 @@ All notable changes to this project will be documented in this file. See [Conventional Commits](https://conventionalcommits.org) for commit guidelines. + +# [4.0.0-alpha.5](https://github.com/strongloop/loopback-next/compare/@loopback/example-rpc-server@4.0.0-alpha.4...@loopback/example-rpc-server@4.0.0-alpha.5) (2018-02-15) + + + + +**Note:** Version bump only for package @loopback/example-rpc-server + # [4.0.0-alpha.4](https://github.com/strongloop/loopback-next/compare/@loopback/example-rpc-server@4.0.0-alpha.3...@loopback/example-rpc-server@4.0.0-alpha.4) (2018-02-07) diff --git a/packages/example-rpc-server/package.json b/packages/example-rpc-server/package.json index 4e136090294b..a7c1a0409eaa 100644 --- a/packages/example-rpc-server/package.json +++ b/packages/example-rpc-server/package.json @@ -1,6 +1,6 @@ { "name": "@loopback/example-rpc-server", - "version": "4.0.0-alpha.4", + "version": "4.0.0-alpha.5", "description": "A basic RPC server using a made-up protocol.", "private": true, "keywords": [ @@ -40,8 +40,8 @@ "dist" ], "dependencies": { - "@loopback/context": "^4.0.0-alpha.31", - "@loopback/core": "^4.0.0-alpha.33", + "@loopback/context": "^4.0.0-alpha.32", + "@loopback/core": "^4.0.0-alpha.34", "@types/express": "^4.0.39", "@types/node": "^8.0.51", "@types/p-event": "^1.3.0", diff --git a/packages/openapi-v2/CHANGELOG.md b/packages/openapi-v2/CHANGELOG.md index 1e57f4469174..700fbf46616f 100644 --- a/packages/openapi-v2/CHANGELOG.md +++ b/packages/openapi-v2/CHANGELOG.md @@ -3,6 +3,14 @@ All notable changes to this project will be documented in this file. See [Conventional Commits](https://conventionalcommits.org) for commit guidelines. + +# [4.0.0-alpha.11](https://github.com/strongloop/loopback-next/compare/@loopback/openapi-v2@4.0.0-alpha.10...@loopback/openapi-v2@4.0.0-alpha.11) (2018-02-15) + + + + +**Note:** Version bump only for package @loopback/openapi-v2 + # [4.0.0-alpha.10](https://github.com/strongloop/loopback-next/compare/@loopback/openapi-v2@4.0.0-alpha.9...@loopback/openapi-v2@4.0.0-alpha.10) (2018-02-07) diff --git a/packages/openapi-v2/package.json b/packages/openapi-v2/package.json index 495e17e179aa..ce0896027016 100644 --- a/packages/openapi-v2/package.json +++ b/packages/openapi-v2/package.json @@ -1,6 +1,6 @@ { "name": "@loopback/openapi-v2", - "version": "4.0.0-alpha.10", + "version": "4.0.0-alpha.11", "description": "Processes openapi v2 related metadata", "engines": { "node": ">=8" @@ -8,7 +8,7 @@ "devDependencies": { "@loopback/build": "^4.0.0-alpha.13", "@loopback/openapi-spec-builder": "^4.0.0-alpha.22", - "@loopback/repository": "^4.0.0-alpha.29", + "@loopback/repository": "^4.0.0-alpha.30", "@loopback/testlab": "^4.0.0-alpha.24", "@types/debug": "0.0.30", "@types/lodash": "^4.14.96" @@ -46,9 +46,9 @@ "url": "https://github.com/strongloop/loopback-next.git" }, "dependencies": { - "@loopback/context": "^4.0.0-alpha.31", + "@loopback/context": "^4.0.0-alpha.32", "@loopback/openapi-spec": "^4.0.0-alpha.25", - "@loopback/repository-json-schema": "^4.0.0-alpha.7", + "@loopback/repository-json-schema": "^4.0.0-alpha.8", "debug": "^3.1.0", "lodash": "^4.17.4" } diff --git a/packages/repository-json-schema/CHANGELOG.md b/packages/repository-json-schema/CHANGELOG.md index dca4b64b90ec..143cd104fabc 100644 --- a/packages/repository-json-schema/CHANGELOG.md +++ b/packages/repository-json-schema/CHANGELOG.md @@ -3,6 +3,14 @@ All notable changes to this project will be documented in this file. See [Conventional Commits](https://conventionalcommits.org) for commit guidelines. + +# [4.0.0-alpha.8](https://github.com/strongloop/loopback-next/compare/@loopback/repository-json-schema@4.0.0-alpha.7...@loopback/repository-json-schema@4.0.0-alpha.8) (2018-02-15) + + + + +**Note:** Version bump only for package @loopback/repository-json-schema + # [4.0.0-alpha.7](https://github.com/strongloop/loopback-next/compare/@loopback/repository-json-schema@4.0.0-alpha.6...@loopback/repository-json-schema@4.0.0-alpha.7) (2018-02-07) diff --git a/packages/repository-json-schema/package.json b/packages/repository-json-schema/package.json index b88156311849..176e600141b9 100644 --- a/packages/repository-json-schema/package.json +++ b/packages/repository-json-schema/package.json @@ -1,6 +1,6 @@ { "name": "@loopback/repository-json-schema", - "version": "4.0.0-alpha.7", + "version": "4.0.0-alpha.8", "description": "Converts TS classes into JSON Schemas using TypeScript's reflection API", "engines": { "node": ">=8" @@ -25,8 +25,8 @@ "access": "public" }, "dependencies": { - "@loopback/context": "^4.0.0-alpha.31", - "@loopback/repository": "^4.0.0-alpha.29", + "@loopback/context": "^4.0.0-alpha.32", + "@loopback/repository": "^4.0.0-alpha.30", "lodash": "^4.17.4", "typescript-json-schema": "^0.20.0" }, diff --git a/packages/repository/CHANGELOG.md b/packages/repository/CHANGELOG.md index 7af9fc2974f6..81784dcbdd70 100644 --- a/packages/repository/CHANGELOG.md +++ b/packages/repository/CHANGELOG.md @@ -3,6 +3,14 @@ All notable changes to this project will be documented in this file. See [Conventional Commits](https://conventionalcommits.org) for commit guidelines. + +# [4.0.0-alpha.30](https://github.com/strongloop/loopback-next/compare/@loopback/repository@4.0.0-alpha.29...@loopback/repository@4.0.0-alpha.30) (2018-02-15) + + + + +**Note:** Version bump only for package @loopback/repository + # [4.0.0-alpha.29](https://github.com/strongloop/loopback-next/compare/@loopback/repository@4.0.0-alpha.28...@loopback/repository@4.0.0-alpha.29) (2018-02-07) diff --git a/packages/repository/package.json b/packages/repository/package.json index 8193badce558..638b7184c599 100644 --- a/packages/repository/package.json +++ b/packages/repository/package.json @@ -1,6 +1,6 @@ { "name": "@loopback/repository", - "version": "4.0.0-alpha.29", + "version": "4.0.0-alpha.30", "description": "Repository based persistence for LoopBack 4", "engines": { "node": ">=8" @@ -22,11 +22,11 @@ "license": "MIT", "devDependencies": { "@loopback/build": "^4.0.0-alpha.13", - "@loopback/core": "^4.0.0-alpha.33", + "@loopback/core": "^4.0.0-alpha.34", "@loopback/testlab": "^4.0.0-alpha.24" }, "dependencies": { - "@loopback/context": "^4.0.0-alpha.31", + "@loopback/context": "^4.0.0-alpha.32", "loopback-datasource-juggler": "^3.9.2" }, "files": [ diff --git a/packages/rest/CHANGELOG.md b/packages/rest/CHANGELOG.md index 8d4f41187ec9..5e80b6372b0f 100644 --- a/packages/rest/CHANGELOG.md +++ b/packages/rest/CHANGELOG.md @@ -3,6 +3,14 @@ All notable changes to this project will be documented in this file. See [Conventional Commits](https://conventionalcommits.org) for commit guidelines. + +# [4.0.0-alpha.26](https://github.com/strongloop/loopback-next/compare/@loopback/rest@4.0.0-alpha.25...@loopback/rest@4.0.0-alpha.26) (2018-02-15) + + + + +**Note:** Version bump only for package @loopback/rest + # [4.0.0-alpha.25](https://github.com/strongloop/loopback-next/compare/@loopback/rest@4.0.0-alpha.24...@loopback/rest@4.0.0-alpha.25) (2018-02-07) diff --git a/packages/rest/package.json b/packages/rest/package.json index ba082a0b419a..cf199398dd0b 100644 --- a/packages/rest/package.json +++ b/packages/rest/package.json @@ -1,6 +1,6 @@ { "name": "@loopback/rest", - "version": "4.0.0-alpha.25", + "version": "4.0.0-alpha.26", "description": "", "engines": { "node": ">=8" @@ -21,10 +21,10 @@ "copyright.owner": "IBM Corp.", "license": "MIT", "dependencies": { - "@loopback/context": "^4.0.0-alpha.31", - "@loopback/core": "^4.0.0-alpha.33", + "@loopback/context": "^4.0.0-alpha.32", + "@loopback/core": "^4.0.0-alpha.34", "@loopback/openapi-spec": "^4.0.0-alpha.25", - "@loopback/openapi-v2": "^4.0.0-alpha.10", + "@loopback/openapi-v2": "^4.0.0-alpha.11", "@types/http-errors": "^1.6.1", "@types/node": "^8.5.9", "body": "^5.1.0", @@ -38,7 +38,7 @@ "devDependencies": { "@loopback/build": "^4.0.0-alpha.13", "@loopback/openapi-spec-builder": "^4.0.0-alpha.22", - "@loopback/repository": "^4.0.0-alpha.29", + "@loopback/repository": "^4.0.0-alpha.30", "@loopback/testlab": "^4.0.0-alpha.24", "@types/debug": "0.0.30", "@types/js-yaml": "^3.9.1",