diff --git a/packages/boot/src/boot.component.ts b/packages/boot/src/boot.component.ts index ec43376b922b..e5474f0eace0 100644 --- a/packages/boot/src/boot.component.ts +++ b/packages/boot/src/boot.component.ts @@ -11,6 +11,7 @@ import { RepositoryBooter, DataSourceBooter, ServiceBooter, + ApplicationMetadataBooter, } from './booters'; import {BootBindings} from './keys'; @@ -23,6 +24,7 @@ export class BootComponent implements Component { // Export a list of default booters in the component so they get bound // automatically when this component is mounted. booters = [ + ApplicationMetadataBooter, ControllerBooter, RepositoryBooter, ServiceBooter, diff --git a/packages/boot/src/booters/application-metadata.booter.ts b/packages/boot/src/booters/application-metadata.booter.ts new file mode 100644 index 000000000000..a5cd33aac067 --- /dev/null +++ b/packages/boot/src/booters/application-metadata.booter.ts @@ -0,0 +1,36 @@ +// Copyright IBM Corp. 2018. All Rights Reserved. +// Node module: @loopback/boot +// This file is licensed under the MIT License. +// License text available at https://opensource.org/licenses/MIT + +import {CoreBindings, Application} from '@loopback/core'; +import {inject} from '@loopback/context'; +import {BootBindings} from '../keys'; +import {Booter} from '../interfaces'; +import path = require('path'); + +import * as debugModule from 'debug'; +const debug = debugModule('loopback:boot:booter:application-metadata'); + +/** + * + * Configure the application with metadata from `package.json` + * + * @param app Application instance + * @param projectRoot Root of User Project + */ +export class ApplicationMetadataBooter implements Booter { + constructor( + @inject(CoreBindings.APPLICATION_INSTANCE) public app: Application, + @inject(BootBindings.PROJECT_ROOT) private projectRoot: string, + ) {} + + async configure() { + try { + const pkg = require(path.resolve(this.projectRoot, 'package.json')); + this.app.setMetadata(pkg); + } catch (err) { + debug('package.json not found', err); + } + } +} diff --git a/packages/boot/src/booters/index.ts b/packages/boot/src/booters/index.ts index fcf21cce42fd..6bb4f8676110 100644 --- a/packages/boot/src/booters/index.ts +++ b/packages/boot/src/booters/index.ts @@ -9,3 +9,4 @@ export * from './controller.booter'; export * from './datasource.booter'; export * from './repository.booter'; export * from './service.booter'; +export * from './application-metadata.booter'; diff --git a/packages/boot/test/acceptance/application-metadata.booter.acceptance.ts b/packages/boot/test/acceptance/application-metadata.booter.acceptance.ts new file mode 100644 index 000000000000..19a1caa458ef --- /dev/null +++ b/packages/boot/test/acceptance/application-metadata.booter.acceptance.ts @@ -0,0 +1,48 @@ +// Copyright IBM Corp. 2018. All Rights Reserved. +// Node module: @loopback/boot +// This file is licensed under the MIT License. +// License text available at https://opensource.org/licenses/MIT + +import {givenHttpServerConfig, TestSandbox, expect} from '@loopback/testlab'; +import {resolve} from 'path'; +import {BooterApp} from '../fixtures/application'; +import {CoreBindings} from '@loopback/core'; + +describe('application metadata booter acceptance tests', () => { + let app: BooterApp; + const SANDBOX_PATH = resolve(__dirname, '../../.sandbox'); + const sandbox = new TestSandbox(SANDBOX_PATH); + + beforeEach('reset sandbox', () => sandbox.reset()); + beforeEach(getApp); + + afterEach(stopApp); + + it('binds content of package.json to application metadata', async () => { + await app.boot(); + const metadata = await app.get(CoreBindings.APPLICATION_METADATA); + expect(metadata).containEql({ + name: 'boot-test-app', + version: '1.0.0', + description: 'boot-test-app', + }); + }); + + async function getApp() { + await sandbox.copyFile(resolve(__dirname, '../fixtures/package.json')); + await sandbox.copyFile(resolve(__dirname, '../fixtures/application.js')); + + const MyApp = require(resolve(SANDBOX_PATH, 'application.js')).BooterApp; + app = new MyApp({ + rest: givenHttpServerConfig(), + }); + } + + async function stopApp() { + try { + await app.stop(); + } catch (err) { + console.log(`Stopping the app threw an error: ${err}`); + } + } +}); diff --git a/packages/boot/test/acceptance/controller.booter.acceptance.ts b/packages/boot/test/acceptance/controller.booter.acceptance.ts index 05508dc1bd4c..0e92889251e3 100644 --- a/packages/boot/test/acceptance/controller.booter.acceptance.ts +++ b/packages/boot/test/acceptance/controller.booter.acceptance.ts @@ -33,6 +33,7 @@ describe('controller booter acceptance tests', () => { }); async function getApp() { + await sandbox.copyFile(resolve(__dirname, '../fixtures/package.json')); await sandbox.copyFile(resolve(__dirname, '../fixtures/application.js')); await sandbox.copyFile( resolve(__dirname, '../fixtures/multiple.artifact.js'), diff --git a/packages/boot/test/fixtures/application.ts b/packages/boot/test/fixtures/application.ts index 492e1130a5e3..6d129389c5c1 100644 --- a/packages/boot/test/fixtures/application.ts +++ b/packages/boot/test/fixtures/application.ts @@ -9,6 +9,10 @@ import {RestApplication} from '@loopback/rest'; import {ServiceMixin} from '@loopback/service-proxy'; import {BootMixin} from '../..'; +// Force package.json to be copied to `dist` by `tsc` +//tslint:disable-next-line:no-unused-variable +import * as pkg from './package.json'; + export class BooterApp extends BootMixin( ServiceMixin(RepositoryMixin(RestApplication)), ) { diff --git a/packages/boot/test/fixtures/package.json b/packages/boot/test/fixtures/package.json new file mode 100644 index 000000000000..e827db359b36 --- /dev/null +++ b/packages/boot/test/fixtures/package.json @@ -0,0 +1,19 @@ +{ + "name": "boot-test-app", + "version": "1.0.0", + "description": "boot-test-app", + "keywords": [ + "loopback-application", + "loopback" + ], + "engines": { + "node": ">=8.9" + }, + "scripts": { + }, + "repository": { + "type": "git" + }, + "author": "", + "license": "" +} diff --git a/packages/core/src/application.ts b/packages/core/src/application.ts index a94596ae36bf..75693f44c8a1 100644 --- a/packages/core/src/application.ts +++ b/packages/core/src/application.ts @@ -192,6 +192,16 @@ export class Application extends Context { const instance = this.getSync(componentKey); mountComponent(this, instance); } + + /** + * Set application metadata. `@loopback/boot` calls this method to populate + * the metadata from `package.json`. + * + * @param metadata Application metadata + */ + public setMetadata(metadata: ApplicationMetadata) { + this.bind(CoreBindings.APPLICATION_METADATA).to(metadata); + } } /** @@ -207,3 +217,22 @@ export interface ApplicationConfig { // tslint:disable-next-line:no-any export type ControllerClass = Constructor; + +/** + * Type definition for JSON + */ +export type JSONPrimitive = string | number | boolean | null; +export type JSONValue = JSONPrimitive | JSONObject | JSONArray; +export interface JSONObject { + [property: string]: JSONValue; +} +export interface JSONArray extends Array {} + +/** + * Type description for `package.json` + */ +export interface ApplicationMetadata extends JSONObject { + name: string; + version: string; + description: string; +} diff --git a/packages/core/src/keys.ts b/packages/core/src/keys.ts index 3050150bdefe..847388fc339c 100644 --- a/packages/core/src/keys.ts +++ b/packages/core/src/keys.ts @@ -4,7 +4,7 @@ // License text available at https://opensource.org/licenses/MIT import {BindingKey} from '@loopback/context'; -import {Application, ControllerClass} from './application'; +import {Application, ControllerClass, ApplicationMetadata} from './application'; /** * Namespace for core binding keys @@ -25,6 +25,13 @@ export namespace CoreBindings { 'application.config', ); + /** + * Binding key for the content of `package.json` + */ + export const APPLICATION_METADATA = BindingKey.create( + 'application.metadata', + ); + // server /** * Binding key for servers