Compare commits

..

40 Commits

Author SHA1 Message Date
Miroslav Bajtoš 2746bf5bde
2.27.1
* Upgrade lodash from 3.x to 4.x (Miroslav Bajtoš)
2018-02-26 16:52:59 +01:00
Miroslav Bajtoš 17cbdf58f1
Merge pull request #278 from strongloop/upgrade-lodash
Upgrade lodash from 3.x to 4.x
2018-02-26 16:52:25 +01:00
Miroslav Bajtoš 81cdf6b42d
Upgrade lodash from 3.x to 4.x 2018-02-19 11:52:33 +01:00
Miroslav Bajtoš 5060032d26
2.27.0
* Drop support for Node.js versions 0.10 and 0.12 (Miroslav Bajtoš)
 * CODEOWNERS: move @lehni to Alumni section (Miroslav Bajtoš)
 * CODEOWNERS: add zbarbuto (Miroslav Bajtoš)
 * Add CODEOWNERS file (Miroslav Bajtoš)
2017-10-23 09:28:19 +02:00
Miroslav Bajtoš 11058e9afa Merge pull request #272 from strongloop/drop-support-node-0.x
Drop support for Node.js versions 0.10 and 0.12
2017-10-23 09:27:52 +02:00
Miroslav Bajtoš b0408f250d
Drop support for Node.js versions 0.10 and 0.12
Some of our dependencies are no longer supporting pre-4.0 versions of
Node.js. As a result, our CI builds are failing on these platforms.

This pull request removes 0.10 and 0.12 from our Travis CI build matrix
and also adds "engines" field to package.json to tell our internal
Jenkins CI to stop testing 0.10 and 0.12 versions too.
2017-10-19 10:57:06 +02:00
Miroslav Bajtoš 7156baa2d0 Merge pull request #270 from strongloop/good-bye-lehni-2x
Good bye lehni 2x
2017-10-19 10:51:36 +02:00
Miroslav Bajtoš ff5db9696b
CODEOWNERS: move @lehni to Alumni section 2017-10-19 10:50:31 +02:00
Miroslav Bajtoš 223a2668a8 Merge pull request #267 from strongloop/welcome-zbarbuto-2x
CODEOWNERS: add zbarbuto
2017-09-27 11:12:03 +02:00
Miroslav Bajtoš cb5651c584 CODEOWNERS: add zbarbuto 2017-09-25 09:56:54 +02:00
Miroslav Bajtoš 0038e4c939 Merge pull request #265 from strongloop/chore/add-codeowners-2.x
Add CODEOWNERS to 2.x branch
2017-09-20 10:05:41 +02:00
Miroslav Bajtoš b668116fab
2.26.2
* Ignore js sourcemap files from boot (Zak Barbuto)
2017-09-15 10:43:45 +02:00
Miroslav Bajtoš 35e668318b Merge pull request #263 from NextFaze/fix/ignore-maps
Ignore js sourcemap files from boot
2017-09-15 10:43:03 +02:00
Zak Barbuto 3d3609defe Ignore js sourcemap files from boot 2017-09-15 09:46:20 +09:30
Miroslav Bajtoš e81e1952b5
Add CODEOWNERS file 2017-09-14 11:16:43 +02:00
Miroslav Bajtoš cdab45d40e
2.26.1
* Do not call callbacks twice in async boot scripts (Jürg Lehni)
2017-08-17 09:58:23 +02:00
Miroslav Bajtoš ded190ee8a Merge pull request #260 from lehni/feature/async-boot-scripts-2.x
Do not call callbacks twice in async boot scripts
2017-08-17 09:57:42 +02:00
Jürg Lehni c9a1e0cc69 Do not call callbacks twice in async boot scripts
See #252 for details
2017-08-16 20:32:06 +02:00
Miroslav Bajtoš fa656e4bee
2.26.0
* Add support for ES6 style async boot scripts (Jürg Lehni)
 * Update Italian translated strings Q2 2017 (Allen Boone)
 * Update translated strings Q2 2017 (Allen Boone)
2017-07-31 17:34:06 +02:00
Miroslav Bajtoš 7f34b0dff6 Merge pull request #252 from lehni/feature/async-boot-scripts-2.x
Add support for ES6 style async boot scripts
2017-07-31 17:33:34 +02:00
Jürg Lehni 51326091b2
Add support for ES6 style async boot scripts 2017-07-31 16:11:22 +02:00
Diana Lau 39b885f9fb Merge pull request #251 from strongloop/update-translations-2.x-backport
Update translations 2.x backport
2017-06-26 09:09:35 -04:00
Miroslav Bajtoš 6db7cadc9b
2.25.0
* Support es2015 module exports (Babel, TypeScript) (John McLaughlin)
2017-06-26 15:04:00 +02:00
Miroslav Bajtoš 06855c1fcd Merge pull request #250 from zamb3zi/2.x
Support for Typescript/Babel emitted ES2015 modules
2017-06-26 15:03:22 +02:00
John McLaughlin 2b596c8c8b
Support es2015 module exports (Babel, TypeScript)
Detect when a script file is an es2015 module created by
a transpiler like Babel or TypeScript and use the `default`
export instead of `module.exports` in such case.

The following artefacts support the new syntax now:

  - boot scripts
  - components
  - middleware
  - model scripts
2017-06-26 14:35:43 +02:00
Allen Boone 4f91a4f78e Update Italian translated strings Q2 2017 2017-06-23 16:16:16 -04:00
Allen Boone b607f969b8 Update translated strings Q2 2017 2017-06-22 10:23:58 -04:00
Miroslav Bajtoš e5224368d4
2.24.1
* add support for absolute middleware paths on win32 (Benjamin Kroeger)
2017-06-07 14:08:51 +02:00
Miroslav Bajtoš b076286235 Merge pull request #233 from benkroeger/2.x
adds support for absolute middleware paths on win32
2017-06-07 14:07:52 +02:00
Miroslav Bajtoš 7f14a24ed2
2.24.0
* Provide options.scriptExtensions (Supasate Choochaisri)
 * Fix browser tests on Node.js 0.10 (Miroslav Bajtoš)
2017-04-03 15:14:51 +02:00
Miroslav Bajtoš 440a75d6f5 Merge pull request #242 from supasate/2.x
Provide options.scriptExtensions for 2.x
2017-04-03 15:13:49 +02:00
Supasate Choochaisri 95e5201255 Provide options.scriptExtensions 2017-04-01 02:10:31 +07:00
Miroslav Bajtoš cda21ea481 Merge pull request #244 from strongloop/fix/node-0.10-ci
Fix browser tests on Node.js 0.10
2017-03-24 12:22:09 +01:00
Miroslav Bajtoš 67c4f1e1a3
Fix browser tests on Node.js 0.10 2017-03-24 12:04:25 +01:00
Benjamin Kroeger 49016b4e66 add support for absolute middleware paths on win32
uses simple regex check to test if a path matches NTFS notation
2016-11-29 11:03:09 +01:00
Miroslav Bajtoš 9cb3d0648b 2.23.0
* Fix resolution of middleware modules (Miroslav Bajtoš)
 * Normalize line endings to support both LF and CRLF (Miroslav Bajtoš)
2016-10-13 10:15:17 +02:00
David Cheung d9e744a6a7 Merge pull request #222 from strongloop/fix/middleware-outside-node-modules-2x
Fix resolution of middleware modules [2.x]
2016-10-12 14:20:47 -04:00
Miroslav Bajtoš d2d9e8789e Fix resolution of middleware modules
Fix the code loading "loopback#errorhandler" (for example) to correctly
look up the "loopback" module in node_modules of the bootstrapped
application, instead of looking it up in node_modules of loopback-boot.
2016-10-12 14:38:37 +02:00
Miroslav Bajtoš eeea74e0f7 Merge pull request #217 from strongloop/fix-ci-windows
Normalize line endings to support both LF and CRLF [2.x]
2016-09-23 15:55:29 +02:00
Miroslav Bajtoš efea20feb5 Normalize line endings to support both LF and CRLF
This should fix build failures on Windows caused by line-ending
mismatch.
2016-09-23 12:42:56 +02:00
87 changed files with 3989 additions and 6024 deletions

View File

@ -1,50 +0,0 @@
---
name: Bug report
about: Create a report to help us improve
labels: bug
---
<!-- 🚨 STOP 🚨 STOP 🚨 STOP 🚨
HELP US HELP YOU, PLEASE
- Do a quick search to avoid duplicate issues
- Provide as much information as possible (reproduction sandbox, use case for features, etc.)
- Consider using a more suitable venue for questions such as Stack Overflow, Gitter, etc.
Please fill in the *entire* template below.
-->
## Steps to reproduce
<!-- Describe how to reproduce the issue -->
## Current Behavior
<!-- Describe the observed result -->
## Expected Behavior
<!-- Describe what did you expect instead, what is the desired outcome? -->
## Link to reproduction sandbox
<!--
See https://loopback.io/doc/en/contrib/Reporting-issues.html#loopback-3x-bugs
Note: Failure to provide a sandbox application for reproduction purposes will result in the issue being closed.
-->
## Additional information
<!--
Copy+paste the output of these two commands:
node -e 'console.log(process.platform, process.arch, process.versions.node)'
npm ls --prod --depth 0 | grep loopback
-->
## Related Issues
<!-- Did you find other bugs that looked similar? -->
_See [Reporting Issues](http://loopback.io/doc/en/contrib/Reporting-issues.html) for more tips on writing good issues_

View File

@ -1,25 +0,0 @@
---
name: Feature request
about: Suggest an idea for this project
labels: feature
---
## Suggestion
<!-- A summary of what you'd like to see added or changed -->
## Use Cases
<!--
What do you want to use this for?
What shortcomings exist with current approaches?
-->
## Examples
<!-- Show how this would be used and what the behavior would be -->
## Acceptance criteria
TBD - will be filled by the team.

View File

@ -1,27 +0,0 @@
---
name: Question
about: The issue tracker is not for questions. Please use Stack Overflow or other resources for help.
labels: question
---
<!-- 🚨 STOP 🚨 STOP 🚨 STOP 🚨
THE ISSUE TRACKER IS NOT FOR QUESTIONS.
DO NOT CREATE A NEW ISSUE TO ASK A QUESTION.
Please use one of the following resources for help:
**Questions**
- https://stackoverflow.com/tags/loopbackjs
- https://groups.google.com/forum/#!forum/loopbackjs
- https://gitter.im/strongloop/loopback
**Immediate support**
- https://strongloop.com/api-connect-faqs/
- https://strongloop.com/node-js/subscription-plans/
-->

View File

@ -1,11 +0,0 @@
blank_issues_enabled: false
contact_links:
- name: Report a security vulnerability
url: https://loopback.io/doc/en/contrib/Reporting-issues.html#security-issues
about: Do not report security vulnerabilities using GitHub issues. Please send an email to `reachsl@us.ibm.com` instead.
- name: Get help on StackOverflow
url: https://stackoverflow.com/tags/loopbackjs
about: Please ask and answer questions on StackOverflow.
- name: Join our mailing list
url: https://groups.google.com/forum/#!forum/loopbackjs
about: You can also post your question to our mailing list.

View File

@ -1,18 +0,0 @@
<!--
Please provide a high-level description of the changes made by your pull request.
Include references to all related GitHub issues and other pull requests, for example:
Fixes #123
Implements #254
See also #23
-->
## Checklist
👉 [Read and sign the CLA (Contributor License Agreement)](https://cla.strongloop.com/agreements/strongloop/loopback-boot) 👈
- [ ] `npm test` passes on your machine
- [ ] New tests added or existing tests modified to cover all changes
- [ ] Code conforms with the [style guide](https://loopback.io/doc/en/contrib/style-guide-es6.html)
- [ ] Commit messages are following our [guidelines](https://loopback.io/doc/en/contrib/git-commit-messages.html)

24
.github/stale.yml vendored
View File

@ -1,24 +0,0 @@
# Number of days of inactivity before an issue becomes stale
daysUntilStale: 60
# Number of days of inactivity before a stale issue is closed
daysUntilClose: 14
# Issues with these labels will never be considered stale
exemptLabels:
- pinned
- security
- critical
- p1
- major
# Label to use when marking an issue as stale
staleLabel: stale
# Comment to post when marking an issue as stale. Set to `false` to disable
markComment: >
This issue has been automatically marked as stale because it has not had
recent activity. It will be closed if no further activity occurs. Thank you
for your contributions.
# Comment to post when closing a stale issue. Set to `false` to disable
closeComment: >
This issue has been closed due to continued inactivity. Thank you for your understanding.
If you believe this to be in error, please contact one of the code owners,
listed in the `CODEOWNERS` file at the top-level of this repository.

3
.gitignore vendored
View File

@ -16,4 +16,5 @@ generated-instructions*.json
checkstyle.xml
loopback-boot-*.tgz
/test/sandbox/
intl/zz/
intl/*
!intl/en/

1
.npmrc
View File

@ -1 +0,0 @@
package-lock=false

View File

@ -1,6 +1,7 @@
sudo: false
language: node_js
node_js:
- "4"
- "6"
- "8"
- "10"

View File

@ -1,30 +0,0 @@
# List of notable changes made between 2.x and 3.0
All breaking changes must be described here. When adding a new entry,
always describe the impact on users and instructions for upgrading
applications from 2.x to 3.0.
## boot.compile is now async and returns context with instructions
Users that uses `boot.compile()` in the following syntax would need to update
their implementation, it now calls callback function with `instructions` in
`context` object rather than synchronously returning instructions.
Before:
```js
var APP_PATH = path.resolve('../sandbox');
var instructions = boot.compile(APP_PATH);
```
New signature:
```js
var APP_PATH = path.resolve('../sandbox');
var instructions;
boot.compile(APP_PATH, function(err, context) {
instructions = context.instructions;
});
```
Please see [PR #181](https://github.com/strongloop/loopback-boot/pull/181) for full details of change.

View File

@ -1,110 +1,70 @@
2019-06-24, Version 3.3.1
=========================
2018-02-26, Version 2.27.1
==========================
* chore: update LTS status (Diana Lau)
* chore: update copyrights years (Agnes Lin)
* Upgrade lodash from 3.x to 4.x (Miroslav Bajtoš)
2019-03-28, Version 3.3.0
=========================
2017-10-23, Version 2.27.0
==========================
* chore: upgrade deps to avoid npm audit warnings (Raymond Feng)
2019-03-22, Version 3.2.1
=========================
* fix: set `app.booting` flag immediately (Miroslav Bajtoš)
* fix: update lodash (jannyHou)
2018-10-18, Version 3.2.0
=========================
* README: update LTS status (Miroslav Bajtoš)
* Add support for es6 modules for boot scripts (Walker)
2018-07-26, Version 3.1.1
=========================
* update: dependency (jannyHou)
* chore: update dependencies (Diana Lau)
* [WebFM] cs/pl/ru translation (candytangnb)
* chore: update license (Diana Lau)
* Drop support for Node.js versions 0.10 and 0.12 (Miroslav Bajtoš)
* CODEOWNERS: move @lehni to Alumni section (Miroslav Bajtoš)
* Add support for ES6 style async boot scripts (Jürg Lehni)
2017-10-13, Version 3.1.0
=========================
* update strong-globalize to 3.1.0 (shimks)
* CODEOWNERS: add zbarbuto (Miroslav Bajtoš)
* Ignore source maps in boot (Zak Barbuto)
* CODEOWNERS: add lehni (Miroslav Bajtoš)
* Create Issue and PR Templates (#261) (Sakib Hasan)
* Add CODEOWNER file (Diana Lau)
* Add CODEOWNERS file (Miroslav Bajtoš)
2017-06-22, Version 3.0.1
=========================
2017-09-15, Version 2.26.2
==========================
* Ignore js sourcemap files from boot (Zak Barbuto)
2017-08-17, Version 2.26.1
==========================
* Do not call callbacks twice in async boot scripts (Jürg Lehni)
2017-07-31, Version 2.26.0
==========================
* Add support for ES6 style async boot scripts (Jürg Lehni)
* Update Italian translated strings Q2 2017 (Allen Boone)
* Update translated strings Q2 2017 (Allen Boone)
* Replicate new issue_template from loopback (Siddhi Pai)
* Replicate issue_template from loopback repo (Siddhi Pai)
2017-06-26, Version 2.25.0
==========================
* Support es2015 module exports (Babel, TypeScript) (John McLaughlin)
2017-05-22, Version 3.0.0
=========================
2017-06-07, Version 2.24.1
==========================
* Upgrade deps and fix style issues (Raymond Feng)
* add support for absolute middleware paths on win32 (Benjamin Kroeger)
* Provide scriptExtensions option (Supasate Choochaisri)
* Update paid support URL (Siddhi Pai)
2017-04-03, Version 2.24.0
==========================
* Refactor for modular and pluggable design (Raymond Feng)
* Provide options.scriptExtensions (Supasate Choochaisri)
* Add Node v7 to Travis CI platforms (Miroslav Bajtoš)
* Fix browser tests on Node.js 0.10 (Miroslav Bajtoš)
* Drop support for Node v0.10 and v0.12 (Miroslav Bajtoš)
* readme: update URL to new doc site (David Cheung)
2016-10-13, Version 2.23.0
==========================
* Update ja translation file (Candy)
* Update header-browser.md (Sequoia McDowell)
* Update translation files - round#2 (Candy)
* Fix resolution of middleware modules (Miroslav Bajtoš)
* Normalize line endings to support both LF and CRLF (Miroslav Bajtoš)
* Remove "defaultForType" from datasource config (Miroslav Bajtoš)
* Update deps to loopback 3.0.0 RC (Miroslav Bajtoš)
* globalization: add translated strings (gunjpan)
* Start development of 3.0 (Miroslav Bajtoš)
2016-09-05, Version 2.22.0
==========================

View File

@ -4,7 +4,7 @@
# Current maintainers
* @raymondfeng @zbarbuto
* @bajtos @zbarbuto
# Alumni
#

View File

@ -1,4 +1,4 @@
Copyright (c) IBM Corp. 2014,2017. All Rights Reserved.
Copyright (c) IBM Corp. 2014,2016. All Rights Reserved.
Node module: loopback-boot
This project is licensed under the MIT License, full text below.

View File

@ -1,20 +1,10 @@
# LoopBack Boot
**⚠️ LoopBack 3 has reached end of life. We are no longer accepting pull requests or providing
support for community users. The only exception is fixes for critical bugs and security
vulnerabilities provided as part of support for IBM API Connect customers. (See
[Module Long Term Support Policy](#module-long-term-support-policy) below.)**
We urge all LoopBack 3 users to migrate their applications to LoopBack 4 as
soon as possible. Refer to our
[Migration Guide](https://loopback.io/doc/en/lb4/migration-overview.html)
for more information on how to upgrade.
## Overview
A convention-based bootstrapper for LoopBack applications.
For full documentation, see the official StrongLoop documentation: [Defining boot scripts](https://loopback.io/doc/en/lb2/Defining-boot-scripts) and [Creating a LoopBack application](https://loopback.io/doc/en/lb2/Creating-an-application).
For full documentation, see the official StrongLoop documentation: [Defining boot scripts](http://docs.strongloop.com/display/LB/Defining+boot+scripts) and [Creating a LoopBack application](http://docs.strongloop.com/display/LB/Creating+an+application).
## Overview
The loopback-boot module initializes (bootstraps) a LoopBack application. Specifically, it:
- Configures data-sources.
@ -23,7 +13,7 @@ The loopback-boot module initializes (bootstraps) a LoopBack application. Speci
- Configures application settings
- Runs additional boot scripts, so you can put custom setup code in multiple small files instead of in the main application file.
For more information, see [Defining boot scripts](https://loopback.io/doc/en/lb2/Defining-boot-scripts).
For more information, see [Defining boot scripts](http://docs.strongloop.com/display/LB/Defining+boot+scripts).
### Version notes
@ -56,19 +46,6 @@ app.listen();
See [API docs](http://apidocs.strongloop.com/loopback-boot/) for
complete API reference.
## Module Long Term Support Policy
This module adopts the [
Module Long Term Support (LTS)](http://github.com/CloudNativeJS/ModuleLTS) policy,
with the following End Of Life (EOL) dates:
| Version | Status | Published | EOL |
| ------- | --------------- | --------- | -------- |
| 3.x | End-of-Life | May 2017 | Dec 2020 |
| 2.x | End-of-Life | Jul 2014 | Apr 2019 |
Learn more about our LTS plan in [docs](https://loopback.io/doc/en/contrib/Long-term-support.html).
## License
This module is provided under dual MIT/StrongLoop license. See [LICENSE](LICENSE) for details.

View File

@ -1,11 +1,9 @@
// Copyright IBM Corp. 2014,2019. All Rights Reserved.
// Copyright IBM Corp. 2014,2016. All Rights Reserved.
// Node module: loopback-boot
// This file is licensed under the MIT License.
// License text available at https://opensource.org/licenses/MIT
'use strict';
const Bootstrapper = require('./lib/bootstrapper');
var execute = require('./lib/executor');
/**
* The browser version of `bootLoopBackApp`.
@ -23,24 +21,17 @@ const Bootstrapper = require('./lib/bootstrapper');
* @header boot(app)
*/
exports = module.exports = function bootBrowserApp(app, options, callback) {
exports = module.exports = function bootBrowserApp(app, options) {
// Only using options.id to identify the browserified bundle to load for
// this application. If no Id was provided, load the default bundle.
let moduleName = 'loopback-boot#instructions';
const appId = options && typeof options === 'object' && options.appId;
if (appId)
moduleName += '-' + appId;
var moduleName = 'loopback-boot#instructions';
if (options && typeof options === 'object' && options.appId)
moduleName += '-' + options.appId;
// The name of the module containing instructions
// is hard-coded in lib/bundler
const instructions = require(moduleName);
const bootstrapper = new Bootstrapper(options);
bootstrapper.phases = ['starting', 'start', 'started'];
const context = {
app: app,
instructions: instructions,
};
return bootstrapper.run(context, callback);
var instructions = require(moduleName);
execute(app, instructions);
};
exports.execute = execute;

View File

@ -9,7 +9,3 @@ var boot = require('loopback-boot');
var app = module.exports = loopback();
boot(app);
```
### Browserify Note
Loopback-boot will *not work correctly* with `fullpaths` option set in browserify/watchify.

View File

@ -1,16 +1,17 @@
// Copyright IBM Corp. 2014,2019. All Rights Reserved.
// Copyright IBM Corp. 2014,2016. All Rights Reserved.
// Node module: loopback-boot
// This file is licensed under the MIT License.
// License text available at https://opensource.org/licenses/MIT
'use strict';
// Strong globalize
const g = require('./lib/globalize');
var SG = require('strong-globalize');
SG.SetRootDir(__dirname);
const PluginBase = require('./lib/plugin-base');
const Bootstrapper = require('./lib/bootstrapper');
const addInstructionsToBrowserify = require('./lib/bundler');
var ConfigLoader = require('./lib/config-loader');
var compile = require('./lib/compiler');
var execute = require('./lib/executor');
var addInstructionsToBrowserify = require('./lib/bundler');
var utils = require('./lib/utils');
/**
* Initialize an application from an options object or
@ -146,33 +147,11 @@ const addInstructionsToBrowserify = require('./lib/bundler');
*/
exports = module.exports = function bootLoopBackApp(app, options, callback) {
if (typeof options === 'string') {
// The 2nd arg is appRootDir
options = {appRootDir: options};
}
if (typeof options === 'function' && callback === undefined) {
callback = options;
options = {};
}
options = options || {};
// backwards compatibility with loopback's app.boot
options.env = options.env || app.get('env');
const bootstrapper = new Bootstrapper(options);
const context = {
bootstrapper: bootstrapper,
app: app,
};
return bootstrapper.run(context, callback);
};
exports.compile = function(options, done) {
const bootstrapper = new Bootstrapper(options);
bootstrapper.phases = ['load', 'compile'];
const context = {};
return bootstrapper.run(context, done);
var instructions = compile(options);
execute(app, instructions, callback);
};
/**
@ -185,26 +164,14 @@ exports.compile = function(options, done) {
*
* @header boot.compileToBrowserify(options, bundler)
*/
exports.compileToBrowserify = function(options, bundler, done) {
return exports.compile(options, function(err, context) {
if (err) return done(err);
addInstructionsToBrowserify({instructions: context.instructions},
bundler);
done();
});
exports.compileToBrowserify = function(options, bundler) {
addInstructionsToBrowserify(compile(options), bundler);
};
/* -- undocumented low-level API -- */
exports.ConfigLoader = ConfigLoader;
exports.compile = compile;
exports.execute = execute;
exports.utils = utils;
exports.addInstructionsToBrowserify = addInstructionsToBrowserify;
exports.Bootstrapper = Bootstrapper;
exports.PluginBase = PluginBase;
exports.execute = function(app, instructions, done) {
const bootstrapper = new Bootstrapper(
{phases: ['starting', 'start', 'started']},
);
const context = {
app: app,
instructions: instructions,
};
return bootstrapper.run(context, done);
};

View File

@ -1,17 +0,0 @@
{
"1e5fea50eef843cbffd1d438494912c8": "Nelze vyřešit cestu \"{0}\"",
"34319676975b1abf107da7a056abb434": "Neplatný formát normalizace - \"{0}\"",
"3a7049e42006e8bc19e0f4fc8df63b6b": "Aplikace `app` je založena na nekompatibilní verzí Loopback {0}. Podporované verze: {1}",
"3f93b626dd9a1c33d67490f6e71018b5": "VAROVÁNÍ: Hlavní konfigurační soubor \"{0}{{.json}}\" chybí",
"4d052d84c8620730afd4a30832f11724": "Nelze konfigurovat neznámý model {0}",
"4ed668e9187650d898acf97707df445a": "Fáze {{phase}} \"{0}\" není definována v hlavní konfiguraci.",
"6447e6b342a2c51ab0bc53b3cbdf3742": "Konflikt řazení: Nelze přidat \"{0}\" za \"{1}\", protože již bylo uvedené opačné pořadí",
"70654dc6eb565613a33344efed3de998": "Nezdařilo se načíst zaváděcí skript: {0}\n{1}",
"7f7bdcadb75abfef1bd8a126d547dd6d": "{0} se neinterpretuje na platnou hodnotu, vráceno jako {1}. \"{2}\" musí být rozdělitelný v proměnné prostředí nebo {{app.get()}}.",
"91a742b7c3568cf6b6755741a70b3c52": "{{middleware}} \"{0}\" v {{phase}} \"{1}\" není definováno v hlavní konfiguraci.",
"a3aa22086ae4976cd013065c9a3ff81c": "Nelze použít {0}: ",
"be2cf2868ba54624fe38e9908dde5e9e": "Data v {{model-config.json}} jsou v nepodporovaném formátu {{1.x}}.",
"ec551b6f2fafd8d40af801ebe5bb09f6": "Vyřazení pokynů {{middleware}}, klient {{loopback}} nepodporuje {{middleware}}.",
"fdc23df1bd0fe55fe3faabcc89ff60f3": "Middleware \"{0}\" nelze nalézt: {1}"
}

View File

@ -1,16 +1,25 @@
{
"ec551b6f2fafd8d40af801ebe5bb09f6": "Discarding {{middleware}} instructions, {{loopback}} client does not support {{middleware}}.",
"1e5fea50eef843cbffd1d438494912c8": "Cannot resolve path \"{0}\"",
"34319676975b1abf107da7a056abb434": "Invalid normalization format - \"{0}\"",
"3a7049e42006e8bc19e0f4fc8df63b6b": "The `app` is powered by an incompatible loopback version {0}. Supported versions: {1}",
"3f93b626dd9a1c33d67490f6e71018b5": "WARNING: Main config file \"{0}{{.json}}\" is missing",
"4d052d84c8620730afd4a30832f11724": "Cannot configure unknown model {0}",
"46e3ab0ef1149ce0a171b5fac2612ea3": "{{Middleware}} \"{0}\" not found: {1}",
"79e93b2a95e969788590c14e26bb2c1b": "The data in {{model-config.json}} is in the unsupported 1.x format.",
"978a25819e71602cad691dbe7ba17592": "{0} config must be a valid JSON object",
"be2dcdab7aa493ed8d77287eb45cfec8": "cannot require directory contents without directory name",
"2634623ad4b2c5902f6c6bb25e68b733": "WARNING: Main {{config}} file \"{0}.json\" is missing",
"4ed668e9187650d898acf97707df445a": "The {{phase}} \"{0}\" is not defined in the main config.",
"6447e6b342a2c51ab0bc53b3cbdf3742": "Ordering conflict: cannot add \"{0}\" after \"{1}\", because the opposite order was already specified",
"6de7e97f033f2cf477297b3d05a93608": "The {{middleware}} \"{0}\" in phase \"{1}\"is not defined in the main config.",
"94a0c7d5ab6462f7892b90c63f316f42": "invalid array: {0}",
"ec34cc58612cb654742e4cd0a57aca78": "Cannot apply {0}: {1}",
"0b91d122f6459c8bbe7865be0936fc4a": "{{app.restBasePath}} is required",
"1cda77c9954be299bb7154f73cb6ab74": "{{instructions.middleware.phases}} must be an {{array}}",
"22549489736fb0d7eba5a4b08977505f": "{{app.host}} must be a {{string}}",
"4c581cc529a7aeda620d5c4b4ef5cfa8": "{{app.restApiRoot}} must start with \"/\"",
"6037512314fac9d12af6c654a3804823": "Built-in model {0} should have been defined",
"69746d336c89bf4bb371a6c2fe56304d": "{0} does not resolve to a valid value, returned as {1}. \"{2}\" must be resolvable in Environment variable or by app.get().",
"70654dc6eb565613a33344efed3de998": "Failed loading boot script: {0}\n{1}",
"7f7bdcadb75abfef1bd8a126d547dd6d": "{0} does not resolve to a valid value, returned as {1}. \"{2}\" must be resolvable in Environment variable or by {{app.get()}}.",
"91a742b7c3568cf6b6755741a70b3c52": "The {{middleware}} \"{0}\" in {{phase}} \"{1}\"is not defined in the main config.",
"a3aa22086ae4976cd013065c9a3ff81c": "Cannot apply {0}: ",
"be2cf2868ba54624fe38e9908dde5e9e": "The data in {{model-config.json}} is in the unsupported {{1.x}} format.",
"ec551b6f2fafd8d40af801ebe5bb09f6": "Discarding {{middleware}} instructions, {{loopback}} client does not support {{middleware}}.",
"fdc23df1bd0fe55fe3faabcc89ff60f3": "Middleware \"{0}\" not found: {1}"
"b078ccd043437a258581e387f93dc1a5": "The `{{app}}` is powered by an incompatible {{loopback}} version {0}. Supported versions: {1}",
"e8d29edfb313cfe64f5c96cc7d3d5b4b": "When using {{loopback-boot}} with {{loopback}} <1.9, the {{loopback}} module must be available for `{{require('loopback')}}`.",
"f48405e7c61c3d665b601c9ba41da424": "{{app.port}} must be a {{string}} or {{number}}",
"fa2a7d5137c8891693f9515d48f5b7d7": "{{app.restApiRoot}} must be a {{string}}"
}

View File

@ -1,17 +0,0 @@
{
"1e5fea50eef843cbffd1d438494912c8": "Nie można rozstrzygnąć ścieżki \"{0}\"",
"34319676975b1abf107da7a056abb434": "Niepoprawny format normalizacji — \"{0}\"",
"3a7049e42006e8bc19e0f4fc8df63b6b": "Aplikacja jest obsługiwana przez niezgodną wersję aplikacji LoopBack {0}. Obsługiwane wersje: {1}",
"3f93b626dd9a1c33d67490f6e71018b5": "OSTRZEŻENIE: Brak głównego pliku konfiguracyjnego \"{0}{{.json}}\"",
"4d052d84c8620730afd4a30832f11724": "Nie można skonfigurować nieznanego modelu {0}",
"4ed668e9187650d898acf97707df445a": "Faza {{phase}} \"{0}\" nie została zdefiniowana w konfiguracji głównej.",
"6447e6b342a2c51ab0bc53b3cbdf3742": "Konflikt porządkowania: nie można dodać elementu \"{0}\" po elemencie \"{1}\", ponieważ została już określona odwrotna kolejność",
"70654dc6eb565613a33344efed3de998": "Nie powiodło się ładowanie skryptu startowego: {0}\n{1}",
"7f7bdcadb75abfef1bd8a126d547dd6d": "{0} nie umożliwia rozstrzygnięcia na poprawną wartość, zwrócono jako {1}. \"{2}\" musi umożliwiać rozstrzygnięcie w zmiennej środowiskowej lub przez metodę {{app.get()}}.",
"91a742b7c3568cf6b6755741a70b3c52": "Warstwa pośrednia {{middleware}} \"{0}\" w fazie {{phase}} \"{1}\" nie została zdefiniowana w konfiguracji głównej.",
"a3aa22086ae4976cd013065c9a3ff81c": "Nie można zastosować {0}: ",
"be2cf2868ba54624fe38e9908dde5e9e": "Dane w pliku {{model-config.json}} mają nieobsługiwany format {{1.x}}.",
"ec551b6f2fafd8d40af801ebe5bb09f6": "Odrzucanie instrukcji warstwy pośredniej {{middleware}}, klient {{loopback}} nie obsługuje warstwy pośredniej {{middleware}}.",
"fdc23df1bd0fe55fe3faabcc89ff60f3": "Warstwa pośrednia \"{0}\" nie została znaleziona: {1}"
}

View File

@ -1,17 +0,0 @@
{
"1e5fea50eef843cbffd1d438494912c8": "Не удалось определить путь \"{0}\"",
"34319676975b1abf107da7a056abb434": "Недопустимый формат нормализации - \"{0}\"",
"3a7049e42006e8bc19e0f4fc8df63b6b": "Приложение `app` создано на основе несовместимой версии loopback {0}. Поддерживаемые версии: {1}",
"3f93b626dd9a1c33d67490f6e71018b5": "ПРЕДУПРЕЖДЕНИЕ: отсутствует главный файл конфигурации \"{0}{{.json}}\"",
"4d052d84c8620730afd4a30832f11724": "Не удается настроить неизвестную модель {0}",
"4ed668e9187650d898acf97707df445a": "Этап {{phase}} \"{0}\" не определен в главной конфигурации.",
"6447e6b342a2c51ab0bc53b3cbdf3742": "Конфликт упорядочения: не удается добавить \"{0}\" после \"{1}\", та как уже указан другой порядок",
"70654dc6eb565613a33344efed3de998": "Не удалось загрузить сценарий загрузки: {0}\n{1}",
"7f7bdcadb75abfef1bd8a126d547dd6d": "{0} не удается определить в допустимое значение, возвращено как {1}. \"{2}\" должен определяться как переменная среды или с помощью {{app.get()}}.",
"91a742b7c3568cf6b6755741a70b3c52": "{{middleware}} \"{0}\" на этапе {{phase}} \"{1}\"не определено в главной конфигурации.",
"a3aa22086ae4976cd013065c9a3ff81c": "Не удается применить {0}: ",
"be2cf2868ba54624fe38e9908dde5e9e": "Данные в {{model-config.json}} указаны в неподдерживаемом формате {{1.x}}.",
"ec551b6f2fafd8d40af801ebe5bb09f6": "Инструкции {{middleware}} отменяются, клиент {{loopback}} не поддерживает {{middleware}}.",
"fdc23df1bd0fe55fe3faabcc89ff60f3": "Не найдено промежуточное ПО \"{0}\": {1}"
}

222
lib/bootstrapper.js vendored
View File

@ -1,222 +0,0 @@
// Copyright IBM Corp. 2016,2019. All Rights Reserved.
// Node module: loopback-boot
// This file is licensed under the MIT License.
// License text available at https://opensource.org/licenses/MIT
'use strict';
const _ = require('lodash');
const assert = require('assert');
const async = require('async');
const utils = require('./utils');
const path = require('path');
const pluginLoader = require('./plugin-loader');
const debug = require('debug')('loopback:boot:bootstrapper');
const Promise = require('bluebird');
const arrayToObject = require('./utils').arrayToObject;
module.exports = Bootstrapper;
function createPromiseCallback() {
let cb;
const promise = new Promise(function(resolve, reject) {
cb = function(err, data) {
if (err) return reject(err);
return resolve(data);
};
});
cb.promise = promise;
return cb;
}
const builtinPlugins = [
'application', 'datasource', 'model', 'mixin',
'middleware', 'component', 'boot-script', 'swagger',
];
const builtinPhases = [
'load', 'compile', 'starting', 'start', 'started',
];
function loadAndRegisterPlugins(bootstrapper, options) {
const loader = pluginLoader(options);
const loaderContext = {};
loader.load(loaderContext);
loader.compile(loaderContext);
for (const i in loaderContext.instructions.pluginScripts) {
bootstrapper.use('/boot/' + i, loaderContext.instructions.pluginScripts[i]);
}
}
/**
* Create a new Bootstrapper with options
* @param options
* @constructor
*/
function Bootstrapper(options) {
this.plugins = [];
options = options || {};
if (typeof options === 'string') {
options = {appRootDir: options};
}
// For setting properties without modifying the original object
options = Object.create(options);
const appRootDir = options.appRootDir = options.appRootDir || process.cwd();
const env = options.env || process.env.NODE_ENV || 'development';
const scriptExtensions = options.scriptExtensions ?
arrayToObject(options.scriptExtensions) :
require.extensions;
const appConfigRootDir = options.appConfigRootDir || appRootDir;
options.rootDir = appConfigRootDir;
options.env = env;
options.scriptExtensions = scriptExtensions;
this.options = options;
this.phases = options.phases || builtinPhases;
this.builtinPlugins = options.plugins || builtinPlugins;
assert(Array.isArray(this.phases), 'Invalid phases: ' + this.phases);
assert(Array.isArray(this.plugins), 'Invalid plugins: ' +
this.builtinPlugins);
const self = this;
self.builtinPlugins.forEach(function(p) {
const factory = require('./plugins/' + p);
self.use('/boot/' + p, factory(options));
});
try {
loadAndRegisterPlugins(self, options);
} catch (err) {
debug('Cannot load & register plugins: %s', err.stack || err);
}
}
/**
* Register a handler to a given path
* @param {String} path
* @param {Function} handler
*/
Bootstrapper.prototype.use = function(path, handler) {
const plugin = {
path: path,
handler: handler,
};
this.plugins.push(plugin);
};
/**
* Get a list of plugins for the given path
* @param {String} path
* @returns {*}
*/
Bootstrapper.prototype.getPlugins = function(path) {
if (path[path.length - 1] !== '/') {
path = path + '/';
}
return this.plugins.filter(function(p) {
return p.path.indexOf(path) === 0;
});
};
/**
* Get a list of extensions for the given path
* @param {String} path
* @returns {*}
*/
Bootstrapper.prototype.getExtensions = function(path) {
if (path[path.length - 1] !== '/') {
path = path + '/';
}
return this.plugins.filter(function(p) {
if (p.path.indexOf(path) === -1) return false;
const name = p.path.substring(path.length);
return name && name.indexOf('/') === -1;
});
};
/**
* Add more phases. The order of phases is decided by the sequence of phase
* names
* @param {String[]} phases An array of phase names
* @returns {String[]} New list of phases
*/
Bootstrapper.prototype.addPhases = function(phases) {
this.phases = utils.mergePhaseNameLists(this.phases, phases || []);
return this.phases;
};
function pluginIteratorFactory(context, phase) {
return function executePluginPhase(plugin, done) {
let result;
if (typeof plugin.handler[phase] !== 'function') {
debug('Skipping %s.%s', plugin.handler.name, phase);
return done();
}
debug('Invoking %s.%s', plugin.handler.name, phase);
try {
if (plugin.handler[phase].length === 2) {
plugin.handler[phase](context, done);
} else {
result = plugin.handler[phase](context);
Promise.resolve(result)
.then(function onPluginPhaseResolved(value) {
done(null, value);
}, function onPluginPhaseRejected(err) {
debug('Unable to invoke %s.%s()', plugin.name, phase, err);
done(err);
});
}
} catch (err) {
debug('Unable to invoke %s.%s()', plugin.name, phase, err);
done(err);
}
};
}
/**
* Invoke the plugins phase by phase with the given context
* @param {Object} context Context object
* @param {Function} done Callback function. If not provided, a promise will be
* returned
* @returns {*}
*/
Bootstrapper.prototype.run = function(context, done) {
if (!done) {
done = createPromiseCallback();
}
const options = this.options;
const appRootDir = options.appRootDir = options.appRootDir || process.cwd();
const env = options.env || process.env.NODE_ENV || 'development';
const appConfigRootDir = options.appConfigRootDir || appRootDir;
options.rootDir = appConfigRootDir;
options.env = env;
context = context || {};
const phases = context.phases || this.phases;
if (phases.includes('starting') || phases.includes('start')) {
context.app.booting = true;
}
const bootPlugins = this.getExtensions('/boot');
async.eachSeries(phases, function(phase, done) {
debug('Phase %s', phase);
async.eachSeries(bootPlugins, pluginIteratorFactory(context, phase), done);
}, function(err) {
if (phases.includes('started')) {
context.app.booting = false;
}
return done(err, context);
});
return done.promise;
};

View File

@ -1,68 +1,52 @@
// Copyright IBM Corp. 2014,2019. All Rights Reserved.
// Copyright IBM Corp. 2014,2016. All Rights Reserved.
// Node module: loopback-boot
// This file is licensed under the MIT License.
// License text available at https://opensource.org/licenses/MIT
'use strict';
const fs = require('fs');
const path = require('path');
const commondir = require('commondir');
const cloneDeep = require('lodash').cloneDeep;
const g = require('./globalize');
var fs = require('fs');
var path = require('path');
var commondir = require('commondir');
var cloneDeep = require('lodash').cloneDeep;
var g = require('strong-globalize')();
/**
* Add boot instructions to a browserify bundler.
* @param {Object} instructions Boot instructions.
* @param {Object} bundler A browserify object created by `browserify()`.
*/
module.exports = function addInstructionsToBrowserify(context, bundler) {
addPlugins(bundler);
// bundlePluginScripts(context, bundler);
bundleModelScripts(context, bundler);
bundleMixinScripts(context, bundler);
bundleComponentScripts(context, bundler);
bundleOtherScripts(context, bundler);
bundleInstructions(context, bundler);
module.exports = function addInstructionsToBrowserify(instructions, bundler) {
bundleModelScripts(instructions, bundler);
bundleMixinScripts(instructions, bundler);
bundleComponentScripts(instructions, bundler);
bundleOtherScripts(instructions, bundler);
bundleInstructions(instructions, bundler);
};
function addPlugins(bundler) {
const dir = path.join(__dirname, './plugins');
const files = fs.readdirSync(dir);
files.forEach(function(f) {
bundler.require(path.join(dir, f),
{expose: './plugins/' + path.basename(f, '.js')});
});
function bundleOtherScripts(instructions, bundler) {
for (var key in instructions.files) {
addScriptsToBundle(key, instructions.files[key], bundler);
}
}
function bundleOtherScripts(context, bundler) {
const list = context.instructions.bootScripts;
addScriptsToBundle('boot', list, bundler);
function bundleModelScripts(instructions, bundler) {
bundleSourceFiles(instructions, 'models', bundler);
}
function bundlePluginScripts(context, bundler) {
const list = context.instructions.pluginScripts;
addScriptsToBundle('plugins', list, bundler);
function bundleMixinScripts(instructions, bundler) {
bundleSourceFiles(instructions, 'mixins', bundler);
}
function bundleModelScripts(context, bundler) {
bundleSourceFiles(context, 'models', bundler);
function bundleComponentScripts(instructions, bundler) {
bundleSourceFiles(instructions, 'components', bundler);
}
function bundleMixinScripts(context, bundler) {
bundleSourceFiles(context, 'mixins', bundler);
}
function bundleComponentScripts(context, bundler) {
bundleSourceFiles(context, 'components', bundler);
}
function bundleSourceFiles(context, type, bundler) {
const files = context.instructions[type]
function bundleSourceFiles(instructions, type, bundler) {
var files = instructions[type]
.map(function(m) { return m.sourceFile; })
.filter(function(f) { return !!f; });
const instructionToFileMapping = context.instructions[type]
var instructionToFileMapping = instructions[type]
.map(function(m) { return files.indexOf(m.sourceFile); });
addScriptsToBundle(type, files, bundler);
@ -70,47 +54,45 @@ function bundleSourceFiles(context, type, bundler) {
// Update `sourceFile` properties with the new paths
instructionToFileMapping.forEach(function(fileIx, sourceIx) {
if (fileIx === -1) return;
context.instructions[type][sourceIx].sourceFile = files[fileIx];
instructions[type][sourceIx].sourceFile = files[fileIx];
});
}
function addScriptsToBundle(name, list, bundler) {
if (!list.length) return;
const root = commondir(list.map(path.dirname));
var root = commondir(list.map(path.dirname));
for (const ix in list) {
const filepath = list[ix];
for (var ix in list) {
var filepath = list[ix];
// Build a short unique id that does not expose too much
// information about the file system, but still preserves
// useful information about where is the file coming from.
const fileid =
'loopback-boot#' + name + '#' + path.relative(root, filepath);
var fileid = 'loopback-boot#' + name + '#' + path.relative(root, filepath);
// Add the file to the bundle.
bundler.require(filepath, {expose: fileid});
bundler.require(filepath, { expose: fileid });
// Rewrite the context entry with the new id that will be
// Rewrite the instructions entry with the new id that will be
// used to load the file via `require(fileid)`.
list[ix] = fileid;
}
}
function bundleInstructions(context, bundler) {
const instructions = cloneDeep(context.instructions);
function bundleInstructions(instructions, bundler) {
instructions = cloneDeep(instructions);
const hasMiddleware = instructions.middleware.phases.length ||
var hasMiddleware = instructions.middleware.phases.length ||
instructions.middleware.middleware.length;
if (hasMiddleware) {
g.warn(
'Discarding {{middleware}} instructions,' +
' {{loopback}} client does not support {{middleware}}.',
);
' {{loopback}} client does not support {{middleware}}.');
}
delete instructions.middleware;
const instructionsString = JSON.stringify(instructions, null, 2);
var instructionsString = JSON.stringify(instructions, null, 2);
/* The following code does not work due to a bug in browserify
* https://github.com/substack/node-browserify/issues/771
@ -120,7 +102,7 @@ function bundleInstructions(context, bundler) {
b.require(instructionsStream, { expose: 'loopback-boot#instructions' });
*/
let instructionId = 'instructions';
var instructionId = 'instructions';
// Create an unique instruction identifier using the application ID.
// This is only useful when multiple loopback applications are being bundled
// together.
@ -129,10 +111,10 @@ function bundleInstructions(context, bundler) {
// Write the instructions to a file in our node_modules folder.
// The location should not really matter as long as it is .gitignore-ed
const instructionsFile = path.resolve(__dirname,
var instructionsFile = path.resolve(__dirname,
'..', 'generated-' + instructionId + '.json');
fs.writeFileSync(instructionsFile, instructionsString, 'utf-8');
const moduleName = 'loopback-boot#' + instructionId;
bundler.require(instructionsFile, {expose: moduleName});
var moduleName = 'loopback-boot#' + instructionId;
bundler.require(instructionsFile, { expose: moduleName });
}

849
lib/compiler.js Normal file
View File

@ -0,0 +1,849 @@
// Copyright IBM Corp. 2014,2016. All Rights Reserved.
// Node module: loopback-boot
// This file is licensed under the MIT License.
// License text available at https://opensource.org/licenses/MIT
var assert = require('assert');
var cloneDeep = require('lodash').cloneDeep;
var fs = require('fs');
var path = require('path');
var toposort = require('toposort');
var ConfigLoader = require('./config-loader');
var utils = require('./utils');
var debug = require('debug')('loopback:boot:compiler');
var Module = require('module');
var _ = require('lodash');
var g = require('strong-globalize')();
var requireNodeOrEsModule = require('./require');
var FILE_EXTENSION_JSON = '.json';
function arrayToObject(array) {
return array.reduce(function(obj, val) {
obj[val] = val;
return obj;
}, {});
}
/**
* Gather all bootstrap-related configuration data and compile it into
* a single object containing instruction for `boot.execute`.
*
* @options {String|Object} options Boot options; If String, this is
* the application root directory; if object, has the properties
* described in `bootLoopBackApp` options above.
* @return {Object}
*
* @header boot.compile(options)
*/
module.exports = function compile(options) {
options = options || {};
if (typeof options === 'string') {
options = { appRootDir: options };
}
// For setting properties without modifying the original object
options = Object.create(options);
var appRootDir = options.appRootDir = options.appRootDir || process.cwd();
var env = options.env || process.env.NODE_ENV || 'development';
var scriptExtensions = options.scriptExtensions ?
arrayToObject(options.scriptExtensions) :
require.extensions;
var appConfigRootDir = options.appConfigRootDir || appRootDir;
var appConfig = options.config ||
ConfigLoader.loadAppConfig(appConfigRootDir, env);
assertIsValidConfig('app', appConfig);
var modelsRootDir = options.modelsRootDir || appRootDir;
var modelsConfig = options.models ||
ConfigLoader.loadModels(modelsRootDir, env);
assertIsValidModelConfig(modelsConfig);
var dsRootDir = options.dsRootDir || appRootDir;
var dataSourcesConfig = options.dataSources ||
ConfigLoader.loadDataSources(dsRootDir, env);
assertIsValidConfig('data source', dataSourcesConfig);
var middlewareRootDir = options.middlewareRootDir || appRootDir;
var middlewareConfig = options.middleware ||
ConfigLoader.loadMiddleware(middlewareRootDir, env);
var middlewareInstructions =
buildMiddlewareInstructions(middlewareRootDir, middlewareConfig);
var componentRootDir = options.componentRootDir || appRootDir;
var componentConfig = options.components ||
ConfigLoader.loadComponents(componentRootDir, env);
var componentInstructions =
buildComponentInstructions(componentRootDir, componentConfig);
// require directories
var bootDirs = options.bootDirs || []; // precedence
bootDirs = bootDirs.concat(path.join(appRootDir, 'boot'));
resolveRelativePaths(bootDirs, appRootDir);
var bootScripts = options.bootScripts || [];
resolveRelativePaths(bootScripts, appRootDir);
bootDirs.forEach(function(dir) {
bootScripts = bootScripts.concat(findScripts(dir, scriptExtensions));
var envdir = dir + '/' + env;
bootScripts = bootScripts.concat(findScripts(envdir, scriptExtensions));
});
// de-dedup boot scripts -ERS
// https://github.com/strongloop/loopback-boot/issues/64
bootScripts = _.uniq(bootScripts);
var modelsMeta = modelsConfig._meta || {};
delete modelsConfig._meta;
var modelSources = options.modelSources || modelsMeta.sources || ['./models'];
var modelInstructions = buildAllModelInstructions(
modelsRootDir, modelsConfig, modelSources, options.modelDefinitions,
scriptExtensions);
var mixinSources = options.mixinSources || modelsMeta.mixins || ['./mixins'];
var mixinInstructions = buildAllMixinInstructions(
appRootDir, options, mixinSources, scriptExtensions, modelInstructions);
// When executor passes the instruction to loopback methods,
// loopback modifies the data. Since we are loading the data using `require`,
// such change affects also code that calls `require` for the same file.
var instructions = {
env: env,
config: appConfig,
dataSources: dataSourcesConfig,
models: modelInstructions,
middleware: middlewareInstructions,
components: componentInstructions,
mixins: mixinInstructions,
files: {
boot: bootScripts,
},
};
if (options.appId)
instructions.appId = options.appId;
return cloneDeep(instructions);
};
function assertIsValidConfig(name, config) {
if (config) {
assert(typeof config === 'object',
g.f('%s config must be a valid JSON object', name));
}
}
function assertIsValidModelConfig(config) {
assertIsValidConfig('model', config);
for (var name in config) {
var entry = config[name];
var options = entry.options || {};
var unsupported = entry.properties ||
entry.base || options.base ||
entry.plural || options.plural;
if (unsupported) {
throw new Error(
g.f('The data in {{model-config.json}}' +
' is in the unsupported 1.x format.'));
}
}
}
/**
* Find all javascript files (except for those prefixed with _)
* and all directories.
* @param {String} dir Full path of the directory to enumerate.
* @return {Array.<String>} A list of absolute paths to pass to `require()`.
* @private
*/
function findScripts(dir, scriptExtensions) {
assert(dir, g.f('cannot require directory contents without directory name'));
var files = tryReadDir(dir);
scriptExtensions = scriptExtensions || require.extensions;
// sort files in lowercase alpha for linux
files.sort(function(a, b) {
a = a.toLowerCase();
b = b.toLowerCase();
if (a < b) {
return -1;
} else if (b < a) {
return 1;
} else {
return 0;
}
});
var results = [];
files.forEach(function(filename) {
// ignore index.js and files prefixed with underscore
if (filename === 'index.js' || filename[0] === '_') {
return;
}
var filepath = path.resolve(path.join(dir, filename));
var stats = fs.statSync(filepath);
// only require files supported by specified extensions
if (stats.isFile()) {
if (scriptExtensions && isPreferredExtension(filename, scriptExtensions))
results.push(filepath);
else
debug('Skipping file %s - unknown extension', filepath);
} else {
debug('Skipping directory %s', filepath);
}
});
return results;
}
function tryReadDir() {
try {
return fs.readdirSync.apply(fs, arguments);
} catch (e) {
return [];
}
}
function buildAllModelInstructions(rootDir, modelsConfig, sources,
modelDefinitions, scriptExtensions) {
var registry = verifyModelDefinitions(rootDir, modelDefinitions,
scriptExtensions);
if (!registry) {
registry = findModelDefinitions(rootDir, sources, scriptExtensions);
}
var modelNamesToBuild = addAllBaseModels(registry, Object.keys(modelsConfig));
var instructions = modelNamesToBuild
.map(function createModelInstructions(name) {
var config = modelsConfig[name];
var definition = registry[name] || {};
debug('Using model "%s"\nConfiguration: %j\nDefinition %j',
name, config, definition.definition);
return {
name: name,
config: config,
definition: definition.definition,
sourceFile: definition.sourceFile,
};
});
return sortByInheritance(instructions);
}
function addAllBaseModels(registry, modelNames) {
var result = [];
var visited = {};
while (modelNames.length) {
var name = modelNames.shift();
if (visited[name]) continue;
visited[name] = true;
result.push(name);
var definition = registry[name] && registry[name].definition;
if (!definition) continue;
var base = getBaseModelName(definition);
// ignore built-in models like User
if (!registry[base]) continue;
modelNames.push(base);
}
return result;
}
function getBaseModelName(modelDefinition) {
if (!modelDefinition)
return undefined;
return modelDefinition.base ||
modelDefinition.options && modelDefinition.options.base;
}
function sortByInheritance(instructions) {
// create edges Base name -> Model name
var edges = instructions
.map(function(inst) {
return [getBaseModelName(inst.definition), inst.name];
});
var sortedNames = toposort(edges);
var instructionsByModelName = {};
instructions.forEach(function(inst) {
instructionsByModelName[inst.name] = inst;
});
return sortedNames
// convert to instructions
.map(function(name) {
return instructionsByModelName[name];
})
// remove built-in models
.filter(function(inst) {
return !!inst;
});
}
function verifyModelDefinitions(rootDir, modelDefinitions, scriptExtensions) {
if (!modelDefinitions || modelDefinitions.length < 1) {
return undefined;
}
var registry = {};
modelDefinitions.forEach(function(definition, idx) {
if (definition.sourceFile) {
var fullPath = path.resolve(rootDir, definition.sourceFile);
definition.sourceFile = fixFileExtension(
fullPath,
tryReadDir(path.dirname(fullPath)),
scriptExtensions);
if (!definition.sourceFile) {
debug('Model source code not found: %s - %s', definition.sourceFile);
}
}
debug('Found model "%s" - %s %s',
definition.definition.name,
'from options',
definition.sourceFile ?
path.relative(rootDir, definition.sourceFile) :
'(no source file)');
var modelName = definition.definition.name;
if (!modelName) {
debug('Skipping model definition without Model name ' +
'(from options.modelDefinitions @ index %s)',
idx);
return;
}
registry[modelName] = definition;
});
return registry;
}
function findModelDefinitions(rootDir, sources, scriptExtensions) {
var registry = {};
sources.forEach(function(src) {
var srcDir = tryResolveAppPath(rootDir, src, { strict: false });
if (!srcDir) {
debug('Skipping unknown module source dir %j', src);
return;
}
var files = tryReadDir(srcDir);
files
.filter(function(f) {
return f[0] !== '_' && path.extname(f) === '.json';
})
.forEach(function(f) {
var fullPath = path.resolve(srcDir, f);
var entry = loadModelDefinition(rootDir, fullPath, files,
scriptExtensions);
var modelName = entry.definition.name;
if (!modelName) {
debug('Skipping model definition without Model name: %s',
path.relative(srcDir, fullPath));
return;
}
registry[modelName] = entry;
});
});
return registry;
}
function resolveAppPath(rootDir, relativePath, resolveOptions) {
var resolvedPath = tryResolveAppPath(rootDir, relativePath, resolveOptions);
if (resolvedPath === undefined && !resolveOptions.optional) {
var err = new Error(g.f('Cannot resolve path "%s"', relativePath));
err.code = 'PATH_NOT_FOUND';
throw err;
}
return resolvedPath;
}
function tryResolveAppPath(rootDir, relativePath, resolveOptions) {
var fullPath;
var start = relativePath.substring(0, 2);
/* In order to retain backward compatibility, we need to support
* two ways how to treat values that are not relative nor absolute
* path (e.g. `relativePath = 'foobar'`)
* - `resolveOptions.strict = true` searches in `node_modules` only
* - `resolveOptions.strict = false` attempts to resolve the value
* as a relative path first before searching `node_modules`
*/
resolveOptions = resolveOptions || { strict: true };
var isModuleRelative = false;
// would love to use `path.isAbsolute(relativePath)` from node's core module `path`
// but unfortunately that is not available in node v0.10.x
// https://nodejs.org/dist/latest-v6.x/docs/api/path.html#path_path_isabsolute_path
if (relativePath[0] === '/' || /^[a-zA-Z]:[\\]{1,2}/.test(relativePath)) {
fullPath = relativePath;
} else if (start === './' || start === '..') {
fullPath = path.resolve(rootDir, relativePath);
} else if (!resolveOptions.strict) {
isModuleRelative = true;
fullPath = path.resolve(rootDir, relativePath);
}
if (fullPath) {
// This check is needed to support paths pointing to a directory
if (utils.fileExistsSync(fullPath)) {
return fullPath;
}
try {
fullPath = require.resolve(fullPath);
return fullPath;
} catch (err) {
if (!isModuleRelative) {
debug ('Skipping %s - %s', fullPath, err);
return undefined;
}
}
}
// Handle module-relative path, e.g. `loopback/common/models`
// Module.globalPaths is a list of globally configured paths like
// [ env.NODE_PATH values, $HOME/.node_modules, etc. ]
// Module._nodeModulePaths(rootDir) returns a list of paths like
// [ rootDir/node_modules, rootDir/../node_modules, etc. ]
var modulePaths = Module.globalPaths
.concat(Module._nodeModulePaths(rootDir));
fullPath = modulePaths
.map(function(candidateDir) {
var absPath = path.join(candidateDir, relativePath);
try {
// NOTE(bajtos) We need to create a proper String object here,
// otherwise we can't attach additional properties to it
var filePath = new String(require.resolve(absPath));
filePath.unresolvedPath = absPath;
return filePath;
} catch (err) {
return absPath;
}
})
.filter(function(candidate) {
return utils.fileExistsSync(candidate.toString());
})
[0];
if (fullPath) {
if (fullPath.unresolvedPath && resolveOptions.fullResolve === false)
return fullPath.unresolvedPath;
// Convert String object back to plain string primitive
return fullPath.toString();
}
debug ('Skipping %s - module not found', fullPath);
return undefined;
}
function loadModelDefinition(rootDir, jsonFile, allFiles, scriptExtensions) {
var definition = require(jsonFile);
var basename = path.basename(jsonFile, path.extname(jsonFile));
definition.name = definition.name || _.upperFirst(_.camelCase(basename));
// find a matching file with a supported extension like `.js` or `.coffee`
var sourceFile = fixFileExtension(jsonFile, allFiles, scriptExtensions);
if (sourceFile === undefined) {
debug('Model source code not found: %s', sourceFile);
}
debug('Found model "%s" - %s %s', definition.name,
path.relative(rootDir, jsonFile),
sourceFile ? path.relative(rootDir, sourceFile) : '(no source file)');
return {
definition: definition,
sourceFile: sourceFile,
};
}
function buildMiddlewareInstructions(rootDir, config) {
var phasesNames = Object.keys(config);
var middlewareList = [];
phasesNames.forEach(function(phase) {
var phaseConfig = config[phase];
Object.keys(phaseConfig).forEach(function(middleware) {
var allConfigs = phaseConfig[middleware];
if (!Array.isArray(allConfigs))
allConfigs = [allConfigs];
allConfigs.forEach(function(config) {
var resolved = resolveMiddlewarePath(rootDir, middleware, config);
// resolved.sourceFile will be false-y if an optional middleware
// is not resolvable.
// if a non-optional middleware is not resolvable, it will throw
// at resolveAppPath() and not reach here
if (!resolved.sourceFile) {
return g.log('{{Middleware}} "%s" not found: %s',
middleware,
resolved.optional
);
}
var middlewareConfig = cloneDeep(config);
middlewareConfig.phase = phase;
if (middlewareConfig.params) {
middlewareConfig.params = resolveMiddlewareParams(
rootDir, middlewareConfig.params);
}
var item = {
sourceFile: resolved.sourceFile,
config: middlewareConfig,
};
if (resolved.fragment) {
item.fragment = resolved.fragment;
}
middlewareList.push(item);
});
});
});
var flattenedPhaseNames = phasesNames
.map(function getBaseName(name) {
return name.replace(/:[^:]+$/, '');
})
.filter(function differsFromPreviousItem(value, ix, source) {
// Skip duplicate entries. That happens when
// `name:before` and `name:after` are both translated to `name`
return ix === 0 || value !== source[ix - 1];
});
return {
phases: flattenedPhaseNames,
middleware: middlewareList,
};
}
function resolveMiddlewarePath(rootDir, middleware, config) {
var resolved = {
optional: !!config.optional,
};
var segments = middleware.split('#');
var pathName = segments[0];
var fragment = segments[1];
var middlewarePath = pathName;
var opts = {
strict: true,
optional: !!config.optional,
};
if (fragment) {
resolved.fragment = fragment;
}
if (pathName.indexOf('./') === 0 || pathName.indexOf('../') === 0) {
// Relative path
pathName = path.resolve(rootDir, pathName);
}
var resolveOpts = _.extend(opts, {
// Workaround for strong-agent to allow probes to detect that
// strong-express-middleware was loaded: exclude the path to the
// module main file from the source file path.
// For example, return
// node_modules/strong-express-metrics
// instead of
// node_modules/strong-express-metrics/index.js
fullResolve: false,
});
var sourceFile = resolveAppScriptPath(rootDir, middlewarePath, resolveOpts);
if (!fragment) {
resolved.sourceFile = sourceFile;
return resolved;
}
// Try to require the module and check if <module>.<fragment> is a valid
// function
var m = requireNodeOrEsModule(sourceFile);
if (typeof m[fragment] === 'function') {
resolved.sourceFile = sourceFile;
return resolved;
}
/*
* module/server/middleware/fragment
* module/middleware/fragment
*/
var candidates = [
pathName + '/server/middleware/' + fragment,
pathName + '/middleware/' + fragment,
// TODO: [rfeng] Should we support the following flavors?
// pathName + '/lib/' + fragment,
// pathName + '/' + fragment
];
var err = undefined; // see https://github.com/eslint/eslint/issues/5744
for (var ix in candidates) {
try {
resolved.sourceFile = resolveAppScriptPath(rootDir, candidates[ix], opts);
delete resolved.fragment;
return resolved;
} catch (e) {
// Report the error for the first candidate when no candidate matches
if (!err) err = e;
}
}
throw err;
}
// Match values starting with `$!./` or `$!../`
var MIDDLEWARE_PATH_PARAM_REGEX = /^\$!(\.\/|\.\.\/)/;
function resolveMiddlewareParams(rootDir, params) {
return _.cloneDeepWith(params, function resolvePathParam(value) {
if (typeof value === 'string' && MIDDLEWARE_PATH_PARAM_REGEX.test(value)) {
return path.resolve(rootDir, value.slice(2));
} else {
return undefined; // no change
}
});
}
function buildComponentInstructions(rootDir, componentConfig) {
return Object.keys(componentConfig)
.filter(function(name) { return !!componentConfig[name]; })
.map(function(name) {
return {
sourceFile: resolveAppScriptPath(rootDir, name, { strict: true }),
config: componentConfig[name],
};
});
}
function resolveRelativePaths(relativePaths, appRootDir) {
var resolveOpts = { strict: false };
relativePaths.forEach(function(relativePath, k) {
var resolvedPath = tryResolveAppPath(appRootDir, relativePath, resolveOpts);
if (resolvedPath !== undefined) {
relativePaths[k] = resolvedPath;
} else {
debug ('skipping boot script %s - unknown file', relativePath);
}
});
}
function getExcludedExtensions() {
return {
'.json': '.json',
/**
* This is a temporary workaround for #246
* See discussion here for full description of the underlying issue
* https://github.com/strongloop/loopback-boot/pull/245#issuecomment-311052798
*/
'.map': '.map',
'.node': 'node',
};
}
function isPreferredExtension(filename, includeExtensions) {
assert(!!includeExtensions, '"includeExtensions" argument is required');
var ext = path.extname(filename);
return (ext in includeExtensions) && !(ext in getExcludedExtensions());
}
function fixFileExtension(filepath, files, scriptExtensions) {
var results = [];
var otherFile;
/* Prefer coffee scripts over json */
if (scriptExtensions && isPreferredExtension(filepath, scriptExtensions)) {
return filepath;
}
var basename = path.basename(filepath, FILE_EXTENSION_JSON);
var sourceDir = path.dirname(filepath);
files.forEach(function(f) {
otherFile = path.resolve(sourceDir, f);
var stats = fs.statSync(otherFile);
if (stats.isFile()) {
var otherFileExtension = path.extname(f);
if (!(otherFileExtension in getExcludedExtensions()) &&
path.basename(f, otherFileExtension) == basename) {
if (!scriptExtensions || otherFileExtension in scriptExtensions) {
results.push(otherFile);
}
}
}
});
return (results.length > 0 ? results[0] : undefined);
}
function resolveAppScriptPath(rootDir, relativePath, resolveOptions) {
var resolvedPath = resolveAppPath(rootDir, relativePath, resolveOptions);
if (!resolvedPath) {
return false;
}
var sourceDir = path.dirname(resolvedPath);
var files = tryReadDir(sourceDir);
var fixedFile = fixFileExtension(resolvedPath, files);
return (fixedFile === undefined ? resolvedPath : fixedFile);
}
function buildAllMixinInstructions(appRootDir, options, mixinSources,
scriptExtensions, modelInstructions) {
// load mixins from `options.mixins`
var sourceFiles = options.mixins || [];
var mixinDirs = options.mixinDirs || [];
var instructionsFromMixins = loadMixins(sourceFiles, options.normalization);
// load mixins from `options.mixinDirs`
sourceFiles = findMixinDefinitions(appRootDir, mixinDirs, scriptExtensions);
if (sourceFiles === undefined) return;
var instructionsFromMixinDirs = loadMixins(sourceFiles,
options.normalization);
/* If `mixinDirs` and `mixinSources` have any directories in common,
* then remove the common directories from `mixinSources` */
mixinSources = _.difference(mixinSources, mixinDirs);
// load mixins from `options.mixinSources`
sourceFiles = findMixinDefinitions(appRootDir, mixinSources,
scriptExtensions);
if (sourceFiles === undefined) return;
var instructionsFromMixinSources = loadMixins(sourceFiles,
options.normalization);
// Fetch unique list of mixin names, used in models
var modelMixins = fetchMixinNamesUsedInModelInstructions(modelInstructions);
modelMixins = _.uniq(modelMixins);
// Filter-in only mixins, that are used in models
instructionsFromMixinSources = filterMixinInstructionsUsingWhitelist(
instructionsFromMixinSources, modelMixins);
var mixins = _.assign(
instructionsFromMixins,
instructionsFromMixinDirs,
instructionsFromMixinSources);
return _.values(mixins);
}
function findMixinDefinitions(appRootDir, sourceDirs, scriptExtensions) {
var files = [];
sourceDirs.forEach(function(dir) {
var path = tryResolveAppPath(appRootDir, dir);
if (!path) {
debug('Skipping unknown module source dir %j', dir);
return;
}
files = files.concat(findScripts(path, scriptExtensions));
});
return files;
}
function loadMixins(sourceFiles, normalization) {
var mixinInstructions = {};
sourceFiles.forEach(function(filepath) {
var dir = path.dirname(filepath);
var ext = path.extname(filepath);
var name = path.basename(filepath, ext);
var metafile = path.join(dir, name + FILE_EXTENSION_JSON);
name = normalizeMixinName(name, normalization);
var meta = {};
meta.name = name;
if (utils.fileExistsSync(metafile)) {
// May overwrite name, not sourceFile
_.extend(meta, requireNodeOrEsModule(metafile));
}
meta.sourceFile = filepath;
mixinInstructions[meta.name] = meta;
});
return mixinInstructions;
}
function fetchMixinNamesUsedInModelInstructions(modelInstructions) {
return _.flatten(modelInstructions
.map(function(model) {
return model.definition && model.definition.mixins ?
Object.keys(model.definition.mixins) : [];
}));
}
function filterMixinInstructionsUsingWhitelist(instructions, includeMixins) {
var instructionKeys = Object.keys(instructions);
includeMixins = _.intersection(instructionKeys, includeMixins);
var filteredInstructions = {};
instructionKeys.forEach(function(mixinName) {
if (includeMixins.indexOf(mixinName) !== -1) {
filteredInstructions[mixinName] = instructions[mixinName];
}
});
return filteredInstructions;
}
function normalizeMixinName(str, normalization) {
switch (normalization) {
case false:
case 'none': return str;
case undefined:
case 'classify':
str = String(str).replace(/([A-Z]+)/g, ' $1').trim();
str = String(str).replace(/[\W_]/g, ' ').toLowerCase();
str = str.replace(/(?:^|\s|-)\S/g, function(c) {
return c.toUpperCase();
});
str = str.replace(/\s+/g, '');
return str;
case 'dasherize':
str = String(str).replace(/([A-Z]+)/g, ' $1').trim();
str = String(str).replace(/[\W_]/g, ' ').toLowerCase();
str = str.replace(/\s+/g, '-');
return str;
default:
if (typeof normalization === 'function') {
return normalization(str);
}
var err = new Error(g.f('Invalid normalization format - "%s"',
normalization));
err.code = 'INVALID_NORMALIZATION_FORMAT';
throw err;
}
}

313
lib/config-loader.js Normal file
View File

@ -0,0 +1,313 @@
// Copyright IBM Corp. 2014,2016. All Rights Reserved.
// Node module: loopback-boot
// This file is licensed under the MIT License.
// License text available at https://opensource.org/licenses/MIT
var cloneDeep = require('lodash').cloneDeep;
var path = require('path');
var utils = require('./utils.js');
var debug = require('debug')('loopback:boot:config-loader');
var assert = require('assert');
var g = require('strong-globalize')();
var ConfigLoader = exports;
/**
* Load application config from `config.json` and friends.
* @param {String} rootDir Directory where to look for files.
* @param {String} env Environment, usually `process.env.NODE_ENV`
* @returns {Object}
*/
ConfigLoader.loadAppConfig = function(rootDir, env) {
return loadNamed(rootDir, env, 'config', mergeAppConfig);
};
/**
* Load data-sources config from `datasources.json` and friends.
* @param {String} rootDir Directory where to look for files.
* @param {String} env Environment, usually `process.env.NODE_ENV`
* @returns {Object}
*/
ConfigLoader.loadDataSources = function(rootDir, env) {
return loadNamed(rootDir, env, 'datasources', mergeDataSourceConfig);
};
/**
* Load model config from `model-config.json` and friends.
* @param {String} rootDir Directory where to look for files.
* @param {String} env Environment, usually `process.env.NODE_ENV`
* @returns {Object}
*/
ConfigLoader.loadModels = function(rootDir, env) {
return loadNamed(rootDir, env, 'model-config', mergeModelConfig);
};
/**
* Load middleware config from `middleware.json` and friends.
* @param {String} rootDir Directory where to look for files.
* @param {String} env Environment, usually `process.env.NODE_ENV`
* @returns {Object}
*/
ConfigLoader.loadMiddleware = function(rootDir, env) {
return loadNamed(rootDir, env, 'middleware', mergeMiddlewareConfig);
};
/**
* Load component config from `component-config.json` and friends.
* @param {String} rootDir Directory where to look for files.
* @param {String} env Environment, usually `process.env.NODE_ENV`
* @returns {Object}
*/
ConfigLoader.loadComponents = function(rootDir, env) {
return loadNamed(rootDir, env, 'component-config', mergeComponentConfig);
};
/*-- Implementation --*/
/**
* Load named configuration.
* @param {String} rootDir Directory where to look for files.
* @param {String} env Environment, usually `process.env.NODE_ENV`
* @param {String} name
* @param {function(target:Object, config:Object, filename:String)} mergeFn
* @returns {Object}
*/
function loadNamed(rootDir, env, name, mergeFn) {
var files = findConfigFiles(rootDir, env, name);
if (files.length) {
debug('found %s %s files', env, name);
files.forEach(function(f) { debug(' %s', f); });
}
var configs = loadConfigFiles(files);
var merged = mergeConfigurations(configs, mergeFn);
debug('merged %s %s configuration %j', env, name, merged);
return merged;
}
/**
* Search `appRootDir` for all files containing configuration for `name`.
* @param {String} appRootDir
* @param {String} env Environment, usually `process.env.NODE_ENV`
* @param {String} name
* @returns {Array.<String>} Array of absolute file paths.
*/
function findConfigFiles(appRootDir, env, name) {
var master = ifExists(name + '.json');
if (!master && (ifExistsWithAnyExt(name + '.local') ||
ifExistsWithAnyExt(name + '.' + env))) {
g.warn('WARNING: Main {{config}} file "%s.json" is missing', name);
}
if (!master) return [];
var candidates = [
master,
ifExistsWithAnyExt(name + '.local'),
ifExistsWithAnyExt(name + '.' + env),
];
return candidates.filter(function(c) { return c !== undefined; });
function ifExists(fileName) {
var filepath = path.resolve(appRootDir, fileName);
return utils.fileExistsSync(filepath) ? filepath : undefined;
}
function ifExistsWithAnyExt(fileName) {
return ifExists(fileName + '.js') || ifExists(fileName + '.json');
}
}
/**
* Load configuration files into an array of objects.
* Attach non-enumerable `_filename` property to each object.
* @param {Array.<String>} files
* @returns {Array.<Object>}
*/
function loadConfigFiles(files) {
return files.map(function(f) {
var config = cloneDeep(require(f));
Object.defineProperty(config, '_filename', {
enumerable: false,
value: f,
});
debug('loaded config file %s: %j', f, config);
return config;
});
}
/**
* Merge multiple configuration objects into a single one.
* @param {Array.<Object>} configObjects
* @param {function(target:Object, config:Object, filename:String)} mergeFn
*/
function mergeConfigurations(configObjects, mergeFn) {
var result = configObjects.shift() || {};
while (configObjects.length) {
var next = configObjects.shift();
mergeFn(result, next, next._filename);
}
return result;
}
function mergeDataSourceConfig(target, config, fileName) {
var err = mergeObjects(target, config);
if (err) {
throw new Error(g.f('Cannot apply %s: %s', fileName, err));
}
}
function mergeModelConfig(target, config, fileName) {
var err = mergeObjects(target, config);
if (err) {
throw new Error(g.f('Cannot apply %s: %s', fileName, err));
}
}
function mergeAppConfig(target, config, fileName) {
var err = mergeObjects(target, config);
if (err) {
throw new Error(g.f('Cannot apply %s: %s', fileName, err));
}
}
function mergeMiddlewareConfig(target, config, fileName) {
var err = undefined; // see https://github.com/eslint/eslint/issues/5744
for (var phase in config) {
if (phase in target) {
err = mergePhaseConfig(target[phase], config[phase], phase);
} else {
err = g.f('The {{phase}} "%s" is not defined in the main config.', phase);
}
if (err)
throw new Error(g.f('Cannot apply %s: %s', fileName, err));
}
}
function mergeNamedItems(arr1, arr2, key) {
assert(Array.isArray(arr1), g.f('invalid array: %s', arr1));
assert(Array.isArray(arr2), g.f('invalid array: %s', arr2));
key = key || 'name';
var result = [].concat(arr1);
for (var i = 0, n = arr2.length; i < n; i++) {
var item = arr2[i];
var found = false;
if (item[key]) {
for (var j = 0, k = result.length; j < k; j++) {
if (result[j][key] === item[key]) {
mergeObjects(result[j], item);
found = true;
break;
}
}
}
if (!found) {
result.push(item);
}
}
return result;
}
function mergePhaseConfig(target, config, phase) {
var err = undefined; // see https://github.com/eslint/eslint/issues/5744
for (var mw in config) {
if (mw in target) {
var targetMiddleware = target[mw];
var configMiddleware = config[mw];
if (Array.isArray(targetMiddleware) && Array.isArray(configMiddleware)) {
// Both are arrays, combine them
target[mw] = mergeNamedItems(targetMiddleware, configMiddleware);
} else if (Array.isArray(targetMiddleware)) {
if (typeof configMiddleware === 'object' &&
Object.keys(configMiddleware).length) {
// Config side is an non-empty object
target[mw] = mergeNamedItems(targetMiddleware, [configMiddleware]);
}
} else if (Array.isArray(configMiddleware)) {
if (typeof targetMiddleware === 'object' &&
Object.keys(targetMiddleware).length) {
// Target side is an non-empty object
target[mw] = mergeNamedItems([targetMiddleware], configMiddleware);
} else {
// Target side is empty
target[mw] = configMiddleware;
}
} else {
err = mergeObjects(targetMiddleware, configMiddleware);
}
} else {
err = g.f('The {{middleware}} "%s" in phase "%s"' +
'is not defined in the main config.', mw, phase);
}
if (err) return err;
}
}
function mergeComponentConfig(target, config, fileName) {
var err = mergeObjects(target, config);
if (err) {
throw new Error(g.f('Cannot apply %s: %s', fileName, err));
}
}
function mergeObjects(target, config, keyPrefix) {
for (var key in config) {
var fullKey = keyPrefix ? keyPrefix + '.' + key : key;
var err = mergeSingleItemOrProperty(target, config, key, fullKey);
if (err) return err;
}
return null; // no error
}
function mergeSingleItemOrProperty(target, config, key, fullKey) {
var origValue = target[key];
var newValue = config[key];
if (!hasCompatibleType(origValue, newValue)) {
return 'Cannot merge values of incompatible types for the option `' +
fullKey + '`.';
}
if (Array.isArray(origValue)) {
return mergeArrays(origValue, newValue, fullKey);
}
if (newValue !== null && typeof origValue === 'object') {
return mergeObjects(origValue, newValue, fullKey);
}
target[key] = newValue;
return null; // no error
}
function mergeArrays(target, config, keyPrefix) {
if (target.length !== config.length) {
return 'Cannot merge array values of different length' +
' for the option `' + keyPrefix + '`.';
}
// Use for(;;) to iterate over undefined items, for(in) would skip them.
for (var ix = 0; ix < target.length; ix++) {
var fullKey = keyPrefix + '[' + ix + ']';
var err = mergeSingleItemOrProperty(target, config, ix, fullKey);
if (err) return err;
}
return null; // no error
}
function hasCompatibleType(origValue, newValue) {
if (origValue === null || origValue === undefined)
return true;
if (Array.isArray(origValue))
return Array.isArray(newValue);
if (typeof origValue === 'object')
return typeof newValue === 'object';
// Note: typeof Array() is 'object' too,
// we don't need to explicitly check array types
return typeof newValue !== 'object';
}

462
lib/executor.js Normal file
View File

@ -0,0 +1,462 @@
// Copyright IBM Corp. 2014,2016. All Rights Reserved.
// Node module: loopback-boot
// This file is licensed under the MIT License.
// License text available at https://opensource.org/licenses/MIT
var assert = require('assert');
var semver = require('semver');
var debug = require('debug')('loopback:boot:executor');
var async = require('async');
var path = require('path');
var format = require('util').format;
var g = require('strong-globalize')();
var requireNodeOrEsModule = require('./require');
/**
* Execute bootstrap instructions gathered by `boot.compile`.
*
* @param {Object} app The loopback app to boot.
* @options {Object} instructions Boot instructions.
* @param {Function} [callback] Callback function.
*
* @header boot.execute(instructions)
*/
module.exports = function execute(app, instructions, callback) {
callback = callback || function() {};
app.booting = true;
patchAppLoopback(app);
assertLoopBackVersion(app);
setEnv(app, instructions);
setHost(app, instructions);
setPort(app, instructions);
setApiRoot(app, instructions);
applyAppConfig(app, instructions);
setupDataSources(app, instructions);
setupModels(app, instructions);
setupMiddleware(app, instructions);
setupComponents(app, instructions);
// Run the boot scripts in series synchronously or asynchronously
// Please note async supports both styles
async.series([
function(done) {
runBootScripts(app, instructions, done);
},
function(done) {
enableAnonymousSwagger(app, instructions);
done();
},
// Ensure both the "booted" event and the callback are always called
// in the next tick of the even loop.
// See http://blog.izs.me/post/59142742143/designing-apis-for-asynchrony
process.nextTick,
], function(err) {
app.booting = false;
if (err) return callback(err);
app.emit('booted');
callback();
});
};
function patchAppLoopback(app) {
if (app.loopback) return;
// app.loopback was introduced in 1.9.0
// patch the app object to make loopback-boot work with older versions too
try {
app.loopback = require('loopback');
} catch (err) {
if (err.code === 'MODULE_NOT_FOUND') {
g.error(
'When using {{loopback-boot}} with {{loopback}} <1.9, ' +
'the {{loopback}} module must be available ' +
'for `{{require(\'loopback\')}}`.');
}
throw err;
}
}
function assertLoopBackVersion(app) {
var RANGE = '1.x || 2.x || ^3.0.0-alpha';
var loopback = app.loopback;
// remove any pre-release tag from the version string,
// because semver has special treatment of pre-release versions,
// while loopback-boot treats pre-releases the same way as regular versions
var version = (loopback.version || '1.0.0').replace(/-.*$/, '');
if (!semver.satisfies(version, RANGE)) {
var msg = g.f(
'The `{{app}}` is powered by an incompatible {{loopback}} version %s. ' +
'Supported versions: %s',
loopback.version || '(unknown)',
RANGE);
throw new Error(msg);
}
}
function setEnv(app, instructions) {
var env = instructions.env;
if (env !== undefined)
app.set('env', env);
}
function setHost(app, instructions) {
// jscs:disable requireCamelCaseOrUpperCaseIdentifiers
var host =
process.env.npm_config_host ||
process.env.OPENSHIFT_SLS_IP ||
process.env.OPENSHIFT_NODEJS_IP ||
process.env.HOST ||
process.env.VCAP_APP_HOST ||
instructions.config.host ||
process.env.npm_package_config_host ||
app.get('host');
if (host !== undefined) {
assert(typeof host === 'string', g.f('{{app.host}} must be a {{string}}'));
app.set('host', host);
}
}
function setPort(app, instructions) {
// jscs:disable requireCamelCaseOrUpperCaseIdentifiers
var port = find([
process.env.npm_config_port,
process.env.OPENSHIFT_SLS_PORT,
process.env.OPENSHIFT_NODEJS_PORT,
process.env.PORT,
process.env.VCAP_APP_PORT,
instructions.config.port,
process.env.npm_package_config_port,
app.get('port'),
3000,
], function(p) {
return p != null;
});
if (port !== undefined) {
var portType = typeof port;
assert(portType === 'string' || portType === 'number',
g.f('{{app.port}} must be a {{string}} or {{number}}'));
app.set('port', port);
}
}
function find(array, predicate) {
return array.filter(predicate)[0];
}
function setApiRoot(app, instructions) {
var restApiRoot =
instructions.config.restApiRoot ||
app.get('restApiRoot') ||
'/api';
assert(restApiRoot !== undefined, g.f('{{app.restBasePath}} is required'));
assert(typeof restApiRoot === 'string',
g.f('{{app.restApiRoot}} must be a {{string}}'));
assert(/^\//.test(restApiRoot),
g.f('{{app.restApiRoot}} must start with "/"'));
app.set('restApiRoot', restApiRoot);
}
function applyAppConfig(app, instructions) {
var appConfig = instructions.config;
for (var configKey in appConfig) {
var cur = app.get(configKey);
if (cur === undefined || cur === null) {
app.set(configKey, appConfig[configKey]);
}
}
}
function setupDataSources(app, instructions) {
forEachKeyedObject(instructions.dataSources, function(key, obj) {
var opts = {
useEnvVars: true,
};
obj = getUpdatedConfigObject(app, obj, opts);
var lazyConnect = process.env.LB_LAZYCONNECT_DATASOURCES;
if (lazyConnect) {
obj.lazyConnect =
lazyConnect === 'false' || lazyConnect === '0' ? false : true;
}
app.dataSource(key, obj);
});
}
function setupModels(app, instructions) {
defineMixins(app, instructions);
defineModels(app, instructions);
instructions.models.forEach(function(data) {
// Skip base models that are not exported to the app
if (!data.config) return;
app.model(data._model, data.config);
});
}
function defineMixins(app, instructions) {
var modelBuilder = (app.registry || app.loopback).modelBuilder;
var BaseClass = app.loopback.Model;
var mixins = instructions.mixins || [];
if (!modelBuilder.mixins || !mixins.length) return;
mixins.forEach(function(obj) {
var mixin = requireNodeOrEsModule(obj.sourceFile);
if (typeof mixin === 'function' || mixin.prototype instanceof BaseClass) {
debug('Defining mixin %s', obj.name);
modelBuilder.mixins.define(obj.name, mixin); // TODO (name, mixin, meta)
} else {
debug('Skipping mixin file %s - `module.exports` is not a function' +
' or Loopback model', obj);
}
});
}
function defineModels(app, instructions) {
var registry = app.registry || app.loopback;
instructions.models.forEach(function(data) {
var name = data.name;
var model;
if (!data.definition) {
model = registry.getModel(name);
if (!model) {
throw new Error(g.f('Cannot configure unknown model %s', name));
}
debug('Configuring existing model %s', name);
} else if (isBuiltinLoopBackModel(app, data)) {
model = registry.getModel(name);
assert(model, g.f('Built-in model %s should have been defined', name));
debug('Configuring built-in LoopBack model %s', name);
} else {
debug('Creating new model %s %j', name, data.definition);
model = registry.createModel(data.definition);
if (data.sourceFile) {
debug('Loading customization script %s', data.sourceFile);
var code = requireNodeOrEsModule(data.sourceFile);
if (typeof code === 'function') {
debug('Customizing model %s', name);
code(model);
} else {
debug('Skipping model file %s - `module.exports` is not a function',
data.sourceFile);
}
}
}
data._model = model;
});
}
// Regular expression to match built-in loopback models
var LOOPBACK_MODEL_REGEXP = new RegExp(
['', 'node_modules', 'loopback', '[^\\/\\\\]+', 'models', '[^\\/\\\\]+\\.js$']
.join('\\' + path.sep));
function isBuiltinLoopBackModel(app, data) {
// 1. Built-in models are exposed on the loopback object
if (!app.loopback[data.name]) return false;
// 2. Built-in models have a script file `loopback/{facet}/models/{name}.js`
var srcFile = data.sourceFile;
return srcFile &&
LOOPBACK_MODEL_REGEXP.test(srcFile);
}
function forEachKeyedObject(obj, fn) {
if (typeof obj !== 'object') return;
Object.keys(obj).forEach(function(key) {
fn(key, obj[key]);
});
}
function runScripts(app, list, callback) {
list = list || [];
var functions = [];
list.forEach(function(filepath) {
debug('Requiring script %s', filepath);
try {
var bootFn = requireNodeOrEsModule(filepath);
if (typeof bootFn === 'function') {
debug('Exported function detected %s', filepath);
functions.push({
path: filepath,
func: bootFn,
});
}
} catch (err) {
g.error('Failed loading boot script: %s\n%s', filepath, err.stack);
throw err;
}
});
async.eachSeries(functions, function(f, done) {
debug('Running script %s', f.path);
var cb = function(err) {
debug('Async function %s %s', err ? 'failed' : 'finished', f.path);
done(err);
// Make sure done() isn't called twice, e.g. if a script returns a
// thenable object and also calls the passed callback.
cb = function() {};
};
try {
var result = f.func(app, cb);
if (result && typeof result.then === 'function') {
result.then(function() { cb(); }, cb);
} else if (f.func.length < 2) {
debug('Sync function finished %s', f.path);
done();
}
} catch (err) {
debug('Sync function failed %s', f.path, err);
done(err);
}
}, callback);
}
function setupMiddleware(app, instructions) {
if (!instructions.middleware) {
// the browserified client does not support middleware
return;
}
// Phases can be empty
var phases = instructions.middleware.phases || [];
assert(Array.isArray(phases),
g.f('{{instructions.middleware.phases}} must be an {{array}}'));
var middleware = instructions.middleware.middleware;
assert(Array.isArray(middleware),
'instructions.middleware.middleware must be an object');
debug('Defining middleware phases %j', phases);
app.defineMiddlewarePhases(phases);
middleware.forEach(function(data) {
debug('Configuring middleware %j%s', data.sourceFile,
data.fragment ? ('#' + data.fragment) : '');
var factory = requireNodeOrEsModule(data.sourceFile);
if (data.fragment) {
factory = factory[data.fragment].bind(factory);
}
assert(typeof factory === 'function',
'Middleware factory must be a function');
var opts = {
useEnvVars: true,
};
data.config = getUpdatedConfigObject(app, data.config, opts);
app.middlewareFromConfig(factory, data.config);
});
}
function getUpdatedConfigObject(app, config, opts) {
var DYNAMIC_CONFIG_PARAM = /\$\{(\w+)\}$/;
var useEnvVars = opts && opts.useEnvVars;
function getConfigVariable(param) {
var configVariable = param;
var match = configVariable.match(DYNAMIC_CONFIG_PARAM);
if (match) {
var varName = match[1];
if (useEnvVars && process.env[varName] !== undefined) {
debug('Dynamic Configuration: Resolved via process.env: %s as %s',
process.env[varName], param);
configVariable = process.env[varName];
} else if (app.get(varName) !== undefined) {
debug('Dynamic Configuration: Resolved via app.get(): %s as %s',
app.get(varName), param);
var appValue = app.get(varName);
configVariable = appValue;
} else {
// previously it returns the original string such as "${restApiRoot}"
// it will now return `undefined`, for the use case of
// dynamic datasources url:`undefined` to fallback to other parameters
configVariable = undefined;
g.warn('%s does not resolve to a valid value, returned as %s. ' +
'"%s" must be resolvable in Environment variable or by app.get().',
param, configVariable, varName);
debug('Dynamic Configuration: Cannot resolve variable for `%s`, ' +
'returned as %s', varName, configVariable);
}
}
return configVariable;
}
function interpolateVariables(config) {
// config is a string and contains a config variable ('${var}')
if (typeof config === 'string')
return getConfigVariable(config);
// anything but an array or object
if (typeof config !== 'object' || config == null)
return config;
// recurse into array elements
if (Array.isArray(config))
return config.map(interpolateVariables);
// Not a plain object. Examples: RegExp, Date,
if (!config.constructor || config.constructor !== Object)
return config;
// recurse into object props
var interpolated = {};
Object.keys(config).forEach(function(configKey) {
var value = config[configKey];
if (Array.isArray(value)) {
interpolated[configKey] = value.map(interpolateVariables);
} else if (typeof value === 'string') {
interpolated[configKey] = getConfigVariable(value);
} else if (value === null) {
interpolated[configKey] = value;
} else if (typeof value === 'object' && Object.keys(value).length) {
interpolated[configKey] = interpolateVariables(value);
} else {
interpolated[configKey] = value;
}
});
return interpolated;
}
return interpolateVariables(config);
}
function setupComponents(app, instructions) {
instructions.components.forEach(function(data) {
debug('Configuring component %j', data.sourceFile);
var configFn = requireNodeOrEsModule(data.sourceFile);
var opts = {
useEnvVars: true,
};
data.config = getUpdatedConfigObject(app, data.config, opts);
configFn(app, data.config);
});
}
function runBootScripts(app, instructions, callback) {
runScripts(app, instructions.files.boot, callback);
}
function enableAnonymousSwagger(app, instructions) {
// disable token requirement for swagger, if available
var swagger = app.remotes().exports.swagger;
if (!swagger) return;
var appConfig = instructions.config;
var requireTokenForSwagger = appConfig.swagger &&
appConfig.swagger.requireToken;
swagger.requireToken = requireTokenForSwagger || false;
}

View File

@ -1,11 +0,0 @@
// Copyright IBM Corp. 2016,2019. All Rights Reserved.
// Node module: loopback-boot
// This file is licensed under the MIT License.
// License text available at https://opensource.org/licenses/MIT
'use strict';
const path = require('path');
const SG = require('strong-globalize');
SG.SetRootDir(path.join(__dirname, '..'), {autonomousMsgLoading: 'all'});
module.exports = SG();

View File

@ -1,338 +0,0 @@
// Copyright IBM Corp. 2016,2019. All Rights Reserved.
// Node module: loopback-boot
// This file is licensed under the MIT License.
// License text available at https://opensource.org/licenses/MIT
'use strict';
const fs = require('fs');
const path = require('path');
const debug = require('debug')('loopback:boot:plugin');
const assert = require('assert');
const _ = require('lodash');
const util = require('./utils');
const g = require('./globalize');
module.exports = PluginBase;
function PluginBase(options, name, artifact) {
this.options = options || {};
this.name = name || options.name;
this.artifact = artifact || options.artifact;
}
PluginBase.prototype.getRootDir = function() {
return this.options.rootDir;
};
PluginBase.prototype.load = function(context) {
const rootDir = this.getRootDir() || this.options.rootDir;
const env = this.options.env;
assert(this.name, 'Plugin name must to be set');
debug('Root dir: %s, env: %s, artifact: %s', rootDir, env, this.artifact);
let config = {};
if (this.options[this.name]) {
// First check if options have the corresponding config object
debug('Artifact: %s is using provided config obj instead' +
' of config file');
config = this.options[this.name];
} else {
if (this.artifact) {
config = this.loadNamed(rootDir, env, this.artifact);
}
}
// Register as context.configurations.<plugin-name>
return this.configure(context, config);
};
PluginBase.prototype.configure = function(context, config) {
config = config || {};
// Register as context.configurations.<plugin-name>
if (!context.configurations) {
context.configurations = {};
}
context.configurations[this.name] = config;
return config;
};
PluginBase.prototype.merge = function(target, config, keyPrefix) {
return this._mergeObjects(target, config, keyPrefix);
};
/**
* Load named configuration.
* @param {String} rootDir Directory where to look for files.
* @param {String} env Environment, usually `process.env.NODE_ENV`
* @param {String} name
* @returns {Object}
*/
PluginBase.prototype.loadNamed = function(rootDir, env, name) {
const files = this.findConfigFiles(rootDir, env, name);
debug('Looking in dir %s for %s configs', rootDir, this.name);
if (files.length) {
debug('found %s %s files: %j', env, name, files);
files.forEach(function(f) {
debug(' %s', f);
});
}
const configs = this._loadConfigFiles(files);
const merged = this._mergeConfigurations(configs);
debug('merged %s %s configuration %j', env, name, merged);
return merged;
};
/**
* Search `rootDir` for all files containing configuration for `name`.
* @param {String} rootDir Root directory
* @param {String} env Environment, usually `process.env.NODE_ENV`
* @param {String} name Name
* @param {Array.<String>} exts An array of extension names
* @returns {Array.<String>} Array of absolute file paths.
*/
PluginBase.prototype.findConfigFiles = function(rootDir, env, name, exts) {
const master = ifExists(name + '.json');
if (!master && (ifExistsWithAnyExt(name + '.local') ||
ifExistsWithAnyExt(name + '.' + env))) {
g.warn('WARNING: Main config file "%s{{.json}}" is missing', name);
}
if (!master) return [];
const candidates = [
master,
ifExistsWithAnyExt(name + '.local'),
ifExistsWithAnyExt(name + '.' + env),
];
return candidates.filter(function(c) {
return c !== undefined;
});
function ifExists(fileName) {
const filePath = path.resolve(rootDir, fileName);
return util.fileExistsSync(filePath) ? filePath : undefined;
}
function ifExistsWithAnyExt(fileName) {
const extensions = exts || ['js', 'json'];
let file;
for (let i = 0, n = extensions.length; i < n; i++) {
file = ifExists(fileName + '.' + extensions[i]);
if (file) {
return file;
}
}
}
};
/**
* Load configuration files into an array of objects.
* Attach non-enumerable `_filename` property to each object.
* @param {Array.<String>} files
* @returns {Array.<Object>}
*/
PluginBase.prototype._loadConfigFiles = function(files) {
return files.map(function(f) {
let config = require(f);
config = _.cloneDeep(config);
Object.defineProperty(config, '_filename', {
enumerable: false,
value: f,
});
return config;
});
};
/**
* Merge multiple configuration objects into a single one.
* @param {Array.<Object>} configObjects
*/
PluginBase.prototype._mergeConfigurations = function(configObjects) {
const result = configObjects.shift() || {};
while (configObjects.length) {
const next = configObjects.shift();
this.merge(result, next, next._filename);
}
return result;
};
PluginBase.prototype._mergeObjects = function(target, config, keyPrefix) {
for (const key in config) {
const fullKey = keyPrefix ? keyPrefix + '.' + key : key;
const err = this._mergeSingleItemOrProperty(target, config, key, fullKey);
if (err) throw err;
}
return null; // no error
};
PluginBase.prototype._mergeNamedItems = function(arr1, arr2, key) {
assert(Array.isArray(arr1), 'invalid array: ' + arr1);
assert(Array.isArray(arr2), 'invalid array: ' + arr2);
key = key || 'name';
const result = [].concat(arr1);
for (let i = 0, n = arr2.length; i < n; i++) {
const item = arr2[i];
let found = false;
if (item[key]) {
for (let j = 0, k = result.length; j < k; j++) {
if (result[j][key] === item[key]) {
this._mergeObjects(result[j], item);
found = true;
break;
}
}
}
if (!found) {
result.push(item);
}
}
return result;
};
PluginBase.prototype._mergeSingleItemOrProperty =
function(target, config, key, fullKey) {
const origValue = target[key];
const newValue = config[key];
if (!hasCompatibleType(origValue, newValue)) {
return 'Cannot merge values of incompatible types for the option `' +
fullKey + '`.';
}
if (Array.isArray(origValue)) {
return this._mergeArrays(origValue, newValue, fullKey);
}
if (newValue !== null && typeof origValue === 'object') {
return this._mergeObjects(origValue, newValue, fullKey);
}
target[key] = newValue;
return null; // no error
};
PluginBase.prototype._mergeArrays = function(target, config, keyPrefix) {
if (target.length !== config.length) {
return 'Cannot merge array values of different length' +
' for the option `' + keyPrefix + '`.';
}
// Use for(;;) to iterate over undefined items, for(in) would skip them.
for (let ix = 0; ix < target.length; ix++) {
const fullKey = keyPrefix + '[' + ix + ']';
const err = this._mergeSingleItemOrProperty(target, config, ix, fullKey);
if (err) return err;
}
return null; // no error
};
function hasCompatibleType(origValue, newValue) {
if (origValue === null || origValue === undefined)
return true;
if (Array.isArray(origValue))
return Array.isArray(newValue);
if (typeof origValue === 'object')
return typeof newValue === 'object';
// Note: typeof Array() is 'object' too,
// we don't need to explicitly check array types
return typeof newValue !== 'object';
}
PluginBase.prototype.compile = function(context) {
let instructions;
if (typeof this.buildInstructions === 'function') {
const rootDir = this.options.rootDir;
const config = context.configurations[this.name] || {};
instructions = this.buildInstructions(context, rootDir, config);
} else {
instructions = context.configurations[this.name];
}
// Register as context.instructions.<plugin-name>
if (!context.instructions) {
context.instructions = {};
if (this.options.appId) {
context.instructions.appId = this.options.appId;
}
}
context.instructions[this.name] = instructions;
return undefined;
};
const DYNAMIC_CONFIG_PARAM = /\$\{(\w+)\}$/;
function getConfigVariable(app, param, useEnvVars) {
let configVariable = param;
const match = configVariable.match(DYNAMIC_CONFIG_PARAM);
if (match) {
const varName = match[1];
if (useEnvVars && process.env[varName] !== undefined) {
debug('Dynamic Configuration: Resolved via process.env: %s as %s',
process.env[varName], param);
configVariable = process.env[varName];
} else if (app.get(varName) !== undefined) {
debug('Dynamic Configuration: Resolved via app.get(): %s as %s',
app.get(varName), param);
const appValue = app.get(varName);
configVariable = appValue;
} else {
// previously it returns the original string such as "${restApiRoot}"
// it will now return `undefined`, for the use case of
// dynamic datasources url:`undefined` to fallback to other parameters
configVariable = undefined;
g.warn('%s does not resolve to a valid value, returned as %s. ' +
'"%s" must be resolvable in Environment variable or by {{app.get()}}.',
param, configVariable, varName);
debug('Dynamic Configuration: Cannot resolve variable for `%s`, ' +
'returned as %s', varName, configVariable);
}
}
return configVariable;
}
PluginBase.prototype.getUpdatedConfigObject = function(context, config, opts) {
const app = context.app;
const useEnvVars = opts && opts.useEnvVars;
function interpolateVariables(config) {
// config is a string and contains a config variable ('${var}')
if (typeof config === 'string')
return getConfigVariable(app, config, useEnvVars);
// anything but an array or object
if (typeof config !== 'object' || config == null)
return config;
// recurse into array elements
if (Array.isArray(config))
return config.map(interpolateVariables);
// Not a plain object. Examples: RegExp, Date,
if (!config.constructor || config.constructor !== Object)
return config;
// recurse into object props
const interpolated = {};
Object.keys(config).forEach(function(configKey) {
const value = config[configKey];
if (Array.isArray(value)) {
interpolated[configKey] = value.map(interpolateVariables);
} else if (typeof value === 'string') {
interpolated[configKey] = getConfigVariable(app, value, useEnvVars);
} else if (value === null) {
interpolated[configKey] = value;
} else if (typeof value === 'object' && Object.keys(value).length) {
interpolated[configKey] = interpolateVariables(value);
} else {
interpolated[configKey] = value;
}
});
return interpolated;
}
return interpolateVariables(config);
};

View File

@ -1,66 +0,0 @@
// Copyright IBM Corp. 2016,2019. All Rights Reserved.
// Node module: loopback-boot
// This file is licensed under the MIT License.
// License text available at https://opensource.org/licenses/MIT
'use strict';
const util = require('util');
const utils = require('./utils');
const path = require('path');
const async = require('async');
const debug = require('debug')('loopback:boot:plugin-loader');
const PluginBase = require('./plugin-base');
const _ = require('lodash');
module.exports = function(options) {
return new PluginScript(options);
};
function PluginScript(options) {
PluginBase.call(this, options, 'pluginScripts', null);
}
util.inherits(PluginScript, PluginBase);
PluginScript.prototype.load = function(context) {
const options = this.options;
const appRootDir = options.rootDir;
// require directories
let pluginDirs = options.pluginDirs || []; // precedence
pluginDirs = pluginDirs.concat(path.join(appRootDir, 'plugins'));
utils.resolveRelativePaths(pluginDirs, appRootDir);
let pluginScripts = options.pluginScripts || [];
utils.resolveRelativePaths(pluginScripts, appRootDir);
pluginDirs.forEach(function(dir) {
pluginScripts = pluginScripts.concat(
utils.findScripts(dir, options.scriptExtensions),
);
const envdir = dir + '/' + options.env;
pluginScripts = pluginScripts.concat(
utils.findScripts(envdir, options.scriptExtensions),
);
});
pluginScripts = _.uniq(pluginScripts);
debug('Plugin scripts: %j', pluginScripts);
this.configure(context, pluginScripts);
return pluginScripts;
};
PluginScript.prototype.compile = function(context) {
const pluginScripts = context.configurations.pluginScripts;
context.instructions = context.instructions || {};
const plugins = context.instructions.pluginScripts = {};
const self = this;
pluginScripts.forEach(function(ps) {
debug('Loading %s', ps);
const factory = require(ps);
const handler = factory(self.options);
const name = handler.name || path.basename(ps, '.js');
debug('Loaded plugin name: %s', name);
plugins[name] = handler;
});
};

View File

@ -1,135 +0,0 @@
// Copyright IBM Corp. 2016,2019. All Rights Reserved.
// Node module: loopback-boot
// This file is licensed under the MIT License.
// License text available at https://opensource.org/licenses/MIT
'use strict';
const util = require('util');
const assert = require('assert');
const semver = require('semver');
const PluginBase = require('../plugin-base');
const g = require('../globalize');
module.exports = function(options) {
return new Application(options);
};
function Application(options) {
PluginBase.call(this, options, 'application', 'config');
}
util.inherits(Application, PluginBase);
function assertLoopBackVersion(app) {
const RANGE = '2.x || 3.x';
const loopback = app.loopback;
// remove any pre-release tag from the version string,
// because semver has special treatment of pre-release versions,
// while loopback-boot treats pre-releases the same way as regular versions
const version = (loopback.version || '1.0.0').replace(/-.*$/, '');
if (!semver.satisfies(version, RANGE)) {
const msg = g.f(
'The `app` is powered by an incompatible loopback version %s. ' +
'Supported versions: %s',
loopback.version || '(unknown)',
RANGE,
);
throw new Error(msg);
}
}
function setEnv(app, env) {
if (env !== undefined)
app.set('env', env);
}
function setHost(app, appConfig) {
// jscs:disable requireCamelCaseOrUpperCaseIdentifiers
const host =
process.env.npm_config_host ||
process.env.OPENSHIFT_SLS_IP ||
process.env.OPENSHIFT_NODEJS_IP ||
process.env.HOST ||
process.env.VCAP_APP_HOST ||
appConfig.host ||
process.env.npm_package_config_host ||
app.get('host');
if (host !== undefined) {
assert(typeof host === 'string', 'app.host must be a string');
app.set('host', host);
}
}
function setPort(app, appConfig) {
// jscs:disable requireCamelCaseOrUpperCaseIdentifiers
const port = find([
process.env.npm_config_port,
process.env.OPENSHIFT_SLS_PORT,
process.env.OPENSHIFT_NODEJS_PORT,
process.env.PORT,
process.env.VCAP_APP_PORT,
appConfig.port,
process.env.npm_package_config_port,
app.get('port'),
3000,
], function(p) {
return p != null;
});
if (port !== undefined) {
const portType = typeof port;
assert(portType === 'string' || portType === 'number',
'app.port must be a string or number');
app.set('port', port);
}
}
function find(array, predicate) {
return array.filter(predicate)[0];
}
function setApiRoot(app, appConfig) {
const restApiRoot =
appConfig.restApiRoot ||
app.get('restApiRoot') ||
'/api';
assert(restApiRoot !== undefined, 'app.restBasePath is required');
assert(typeof restApiRoot === 'string',
'app.restApiRoot must be a string');
assert(/^\//.test(restApiRoot),
'app.restApiRoot must start with "/"');
app.set('restApiRoot', restApiRoot);
}
function applyAppConfig(app, appConfig) {
for (const configKey in appConfig) {
const cur = app.get(configKey);
if (cur === undefined || cur === null) {
app.set(configKey, appConfig[configKey]);
}
}
}
Application.prototype.starting = function(context) {
const app = context.app;
assertLoopBackVersion(app);
const appConfig = context.instructions.application;
setEnv(app, context.instructions.env || this.options.env);
setHost(app, appConfig);
setPort(app, appConfig);
setApiRoot(app, appConfig);
applyAppConfig(app, appConfig);
};
Application.prototype.started = function(context, done) {
const app = context.app;
process.nextTick(function() {
app.emit('booted');
done();
});
};

View File

@ -1,105 +0,0 @@
// Copyright IBM Corp. 2016,2019. All Rights Reserved.
// Node module: loopback-boot
// This file is licensed under the MIT License.
// License text available at https://opensource.org/licenses/MIT
'use strict';
const util = require('util');
const utils = require('../utils');
const path = require('path');
const async = require('async');
const debug = require('debug')('loopback:boot:script');
const PluginBase = require('../plugin-base');
const _ = require('lodash');
const g = require('../globalize');
module.exports = function(options) {
return new Script(options);
};
function Script(options) {
PluginBase.call(this, options, 'bootScripts', null);
}
util.inherits(Script, PluginBase);
Script.prototype.load = function(context) {
const options = this.options;
const appRootDir = options.rootDir;
// require directories
let bootDirs = options.bootDirs || []; // precedence
bootDirs = bootDirs.concat(path.join(appRootDir, 'boot'));
utils.resolveRelativePaths(bootDirs, appRootDir);
let bootScripts = options.bootScripts || [];
utils.resolveRelativePaths(bootScripts, appRootDir);
bootDirs.forEach(function(dir) {
bootScripts = bootScripts.concat(
utils.findScripts(dir, options.scriptExtensions),
);
const envdir = dir + '/' + options.env;
bootScripts = bootScripts.concat(
utils.findScripts(envdir, options.scriptExtensions),
);
});
// de-dedup boot scripts -ERS
// https://github.com/strongloop/loopback-boot/issues/64
bootScripts = _.uniq(bootScripts);
debug('Boot scripts: %j', bootScripts);
this.configure(context, bootScripts);
return bootScripts;
};
Script.prototype.start = function(context, done) {
const app = context.app;
const instructions = context.instructions[this.name];
runScripts(app, instructions, done);
};
function runScripts(app, list, callback) {
list = list || [];
const functions = [];
list.forEach(function(filepath) {
debug('Requiring script %s', filepath);
try {
let exports = require(filepath);
if (exports.__esModule) exports = exports.default;
if (typeof exports === 'function') {
debug('Exported function detected %s', filepath);
functions.push({
path: filepath,
func: exports,
});
}
} catch (err) {
g.error('Failed loading boot script: %s\n%s', filepath, err.stack);
throw err;
}
});
async.eachSeries(functions, function(f, done) {
debug('Running script %s', f.path);
let cb = function(err) {
debug('Async function %s %s', err ? 'failed' : 'finished', f.path);
done(err);
// Make sure done() isn't called twice, e.g. if a script returns a
// thenable object and also calls the passed callback.
cb = function() {};
};
try {
const result = f.func(app, cb);
if (result && typeof result.then === 'function') {
result.then(function() { cb(); }, cb);
} else if (f.func.length < 2) {
debug('Sync function finished %s', f.path);
done();
}
} catch (err) {
debug('Sync function failed %s', f.path, err);
done(err);
}
}, callback);
}

View File

@ -1,52 +0,0 @@
// Copyright IBM Corp. 2016,2019. All Rights Reserved.
// Node module: loopback-boot
// This file is licensed under the MIT License.
// License text available at https://opensource.org/licenses/MIT
'use strict';
const util = require('util');
const debug = require('debug')('loopback:boot:component');
const PluginBase = require('../plugin-base');
const utils = require('../utils');
const resolveAppScriptPath = utils.resolveAppScriptPath;
module.exports = function(options) {
return new Component(options);
};
function Component(options) {
PluginBase.call(this, options, 'components', 'component-config');
}
util.inherits(Component, PluginBase);
Component.prototype.getRootDir = function() {
return this.options.componentRootDir || this.options.rootDir;
};
Component.prototype.buildInstructions = function(context, rootDir, config) {
return Object.keys(config)
.filter(function(name) {
return !!config[name];
}).map(function(name) {
return {
sourceFile: resolveAppScriptPath(rootDir, name, {strict: true}),
config: config[name],
};
});
};
Component.prototype.start = function(context) {
const app = context.app;
const self = this;
context.instructions[this.name].forEach(function(data) {
debug('Configuring component %j', data.sourceFile);
const configFn = require(data.sourceFile);
data.config = self.getUpdatedConfigObject(context, data.config,
{useEnvVars: true});
configFn(app, data.config);
});
};

View File

@ -1,41 +0,0 @@
// Copyright IBM Corp. 2016,2019. All Rights Reserved.
// Node module: loopback-boot
// This file is licensed under the MIT License.
// License text available at https://opensource.org/licenses/MIT
'use strict';
const util = require('util');
const utils = require('../utils');
const PluginBase = require('../plugin-base');
const debug = require('debug')('loopback:boot:datasource');
module.exports = function(options) {
return new DataSource(options);
};
function DataSource(options) {
PluginBase.call(this, options, 'dataSources', 'datasources');
}
util.inherits(DataSource, PluginBase);
DataSource.prototype.getRootDir = function() {
return this.options.dsRootDir;
};
DataSource.prototype.start = function(context) {
const app = context.app;
const self = this;
const lazyConnect = process.env.LB_LAZYCONNECT_DATASOURCES;
utils.forEachKeyedObject(context.instructions[this.name], function(key, obj) {
obj = self.getUpdatedConfigObject(context, obj, {useEnvVars: true});
debug('Registering data source %s %j', key, obj);
if (lazyConnect) {
obj.lazyConnect =
lazyConnect === 'false' || lazyConnect === '0' ? false : true;
}
app.dataSource(key, obj);
});
};

View File

@ -1,266 +0,0 @@
// Copyright IBM Corp. 2016,2019. All Rights Reserved.
// Node module: loopback-boot
// This file is licensed under the MIT License.
// License text available at https://opensource.org/licenses/MIT
'use strict';
const util = require('util');
const assert = require('assert');
const path = require('path');
const _ = require('lodash');
const cloneDeepWith = _.cloneDeepWith;
const cloneDeep = _.cloneDeep;
const debug = require('debug')('loopback:boot:middleware');
const PluginBase = require('../plugin-base');
const utils = require('../utils');
const g = require('../globalize');
const resolveAppScriptPath = utils.resolveAppScriptPath;
module.exports = function(options) {
return new Middleware(options);
};
function Middleware(options) {
PluginBase.call(this, options, 'middleware', 'middleware');
}
util.inherits(Middleware, PluginBase);
Middleware.prototype.getRootDir = function() {
return this.options.middlewareRootDir || this.options.rootDir;
};
Middleware.prototype.merge = function(target, config, fileName) {
let err, phase;
for (phase in config) {
if (phase in target) {
err = this.mergePhaseConfig(target[phase], config[phase], phase);
} else {
err = g.f('The {{phase}} "%s" is not defined in the main config.', phase);
}
if (err)
throw new Error(g.f('Cannot apply %s: ', fileName) + err);
}
};
Middleware.prototype.mergePhaseConfig = function(target, config, phase) {
let err, mw;
for (mw in config) {
if (mw in target) {
const targetMiddleware = target[mw];
const configMiddleware = config[mw];
if (Array.isArray(targetMiddleware) && Array.isArray(configMiddleware)) {
// Both are arrays, combine them
target[mw] = this._mergeNamedItems(targetMiddleware, configMiddleware);
} else if (Array.isArray(targetMiddleware)) {
if (typeof configMiddleware === 'object' &&
Object.keys(configMiddleware).length) {
// Config side is an non-empty object
target[mw] = this._mergeNamedItems(targetMiddleware,
[configMiddleware]);
}
} else if (Array.isArray(configMiddleware)) {
if (typeof targetMiddleware === 'object' &&
Object.keys(targetMiddleware).length) {
// Target side is an non-empty object
target[mw] = this._mergeNamedItems([targetMiddleware],
configMiddleware);
} else {
// Target side is empty
target[mw] = configMiddleware;
}
} else {
err = this._mergeObjects(targetMiddleware, configMiddleware);
}
} else {
err = g.f('The {{middleware}} "%s" in {{phase}} "%s"' +
'is not defined in the main config.', mw, phase);
}
if (err) return err;
}
};
Middleware.prototype.buildInstructions = function(context, rootDir, config) {
const phasesNames = Object.keys(config);
const middlewareList = [];
phasesNames.forEach(function(phase) {
const phaseConfig = config[phase];
Object.keys(phaseConfig).forEach(function(middleware) {
let allConfigs = phaseConfig[middleware];
if (!Array.isArray(allConfigs))
allConfigs = [allConfigs];
allConfigs.forEach(function(config) {
const resolved = resolveMiddlewarePath(rootDir, middleware, config);
// resolved.sourceFile will be false-y if an optional middleware
// is not resolvable.
// if a non-optional middleware is not resolvable, it will throw
// at resolveAppPath() and not reach here
if (!resolved.sourceFile) {
return g.log('Middleware "%s" not found: %s',
middleware,
resolved.optional);
}
const middlewareConfig = cloneDeep(config);
middlewareConfig.phase = phase;
if (middlewareConfig.params) {
middlewareConfig.params = resolveMiddlewareParams(
rootDir, middlewareConfig.params,
);
}
const item = {
sourceFile: resolved.sourceFile,
config: middlewareConfig,
};
if (resolved.fragment) {
item.fragment = resolved.fragment;
}
middlewareList.push(item);
});
});
});
const flattenedPhaseNames = phasesNames
.map(function getBaseName(name) {
return name.replace(/:[^:]+$/, '');
})
.filter(function differsFromPreviousItem(value, ix, source) {
// Skip duplicate entries. That happens when
// `name:before` and `name:after` are both translated to `name`
return ix === 0 || value !== source[ix - 1];
});
return {
phases: flattenedPhaseNames,
middleware: middlewareList,
};
};
function resolveMiddlewarePath(rootDir, middleware, config) {
const resolved = {
optional: !!config.optional,
};
const segments = middleware.split('#');
let pathName = segments[0];
const fragment = segments[1];
const middlewarePath = pathName;
const opts = {
strict: true,
optional: !!config.optional,
};
if (fragment) {
resolved.fragment = fragment;
}
if (pathName.indexOf('./') === 0 || pathName.indexOf('../') === 0) {
// Relative path
pathName = path.resolve(rootDir, pathName);
}
const resolveOpts = _.extend(opts, {
// Workaround for strong-agent to allow probes to detect that
// strong-express-middleware was loaded: exclude the path to the
// module main file from the source file path.
// For example, return
// node_modules/strong-express-metrics
// instead of
// node_modules/strong-express-metrics/index.js
fullResolve: false,
});
const sourceFile = resolveAppScriptPath(rootDir, middlewarePath, resolveOpts);
if (!fragment) {
resolved.sourceFile = sourceFile;
return resolved;
}
// Try to require the module and check if <module>.<fragment> is a valid
// function
const m = require(sourceFile);
if (typeof m[fragment] === 'function') {
resolved.sourceFile = sourceFile;
return resolved;
}
/*
* module/server/middleware/fragment
* module/middleware/fragment
*/
const candidates = [
pathName + '/server/middleware/' + fragment,
pathName + '/middleware/' + fragment,
// TODO: [rfeng] Should we support the following flavors?
// pathName + '/lib/' + fragment,
// pathName + '/' + fragment
];
let err, ix;
for (ix in candidates) {
try {
resolved.sourceFile = resolveAppScriptPath(rootDir, candidates[ix], opts);
delete resolved.fragment;
return resolved;
} catch (e) {
// Report the error for the first candidate when no candidate matches
if (!err) err = e;
}
}
throw err;
}
// Match values starting with `$!./` or `$!../`
const MIDDLEWARE_PATH_PARAM_REGEX = /^\$!(\.\/|\.\.\/)/;
function resolveMiddlewareParams(rootDir, params) {
return cloneDeepWith(params, function resolvePathParam(value) {
if (typeof value === 'string' && MIDDLEWARE_PATH_PARAM_REGEX.test(value)) {
return path.resolve(rootDir, value.slice(2));
} else {
return undefined; // no change
}
});
}
Middleware.prototype.start = function(context) {
const self = this;
const app = context.app;
const instructions = context.instructions.middleware;
if (!instructions) {
// the browserified client does not support middleware
return;
}
// Phases can be empty
const phases = instructions.phases || [];
assert(Array.isArray(phases),
'Middleware phases must be an array');
const middleware = instructions.middleware;
assert(Array.isArray(middleware),
'Middleware must be an array');
debug('Defining middleware phases %j', phases);
app.defineMiddlewarePhases(phases);
middleware.forEach(function(data) {
debug('Configuring middleware %j%s', data.sourceFile,
data.fragment ? ('#' + data.fragment) : '');
let factory = require(data.sourceFile);
if (data.fragment) {
factory = factory[data.fragment].bind(factory);
}
assert(typeof factory === 'function',
'Middleware factory must be a function');
data.config = self.getUpdatedConfigObject(context, data.config,
{useEnvVars: true});
app.middlewareFromConfig(factory, data.config);
});
};

View File

@ -1,200 +0,0 @@
// Copyright IBM Corp. 2016,2019. All Rights Reserved.
// Node module: loopback-boot
// This file is licensed under the MIT License.
// License text available at https://opensource.org/licenses/MIT
'use strict';
const util = require('util');
const fs = require('fs');
const path = require('path');
const PluginBase = require('../plugin-base');
const _ = require('lodash');
const debug = require('debug')('loopback:boot:mixin');
const utils = require('../utils');
const g = require('../globalize');
const tryResolveAppPath = utils.tryResolveAppPath;
const getExcludedExtensions = utils.getExcludedExtensions;
const findScripts = utils.findScripts;
const FILE_EXTENSION_JSON = utils.FILE_EXTENSION_JSON;
module.exports = function(options) {
return new Mixin(options);
};
function Mixin(options) {
PluginBase.call(this, options, 'mixins', null);
}
util.inherits(Mixin, PluginBase);
Mixin.prototype.buildInstructions = function(context, rootDir, config) {
const modelsMeta = context.configurations.mixins._meta || {};
const modelInstructions = context.instructions.models;
const mixinSources = this.options.mixinSources || modelsMeta.mixins ||
['./mixins'];
const scriptExtensions = this.options.scriptExtensions || require.extensions;
const mixinInstructions = buildAllMixinInstructions(
rootDir, this.options, mixinSources, scriptExtensions, modelInstructions,
);
return mixinInstructions;
};
function buildAllMixinInstructions(appRootDir, options, mixinSources,
scriptExtensions, modelInstructions) {
// load mixins from `options.mixins`
let sourceFiles = options.mixins || [];
const mixinDirs = options.mixinDirs || [];
const instructionsFromMixins = loadMixins(sourceFiles, options.normalization);
// load mixins from `options.mixinDirs`
sourceFiles = findMixinDefinitions(appRootDir, mixinDirs, scriptExtensions);
if (sourceFiles === undefined) return;
const instructionsFromMixinDirs = loadMixins(sourceFiles,
options.normalization);
/* If `mixinDirs` and `mixinSources` have any directories in common,
* then remove the common directories from `mixinSources` */
mixinSources = _.difference(mixinSources, mixinDirs);
// load mixins from `options.mixinSources`
sourceFiles = findMixinDefinitions(appRootDir, mixinSources,
scriptExtensions);
if (sourceFiles === undefined) return;
let instructionsFromMixinSources = loadMixins(sourceFiles,
options.normalization);
// Fetch unique list of mixin names, used in models
let modelMixins = fetchMixinNamesUsedInModelInstructions(modelInstructions);
modelMixins = _.uniq(modelMixins);
// Filter-in only mixins, that are used in models
instructionsFromMixinSources = filterMixinInstructionsUsingWhitelist(
instructionsFromMixinSources, modelMixins,
);
const mixins = _.assign(
instructionsFromMixins,
instructionsFromMixinDirs,
instructionsFromMixinSources,
);
return _.values(mixins);
}
function findMixinDefinitions(appRootDir, sourceDirs, scriptExtensions) {
let files = [];
sourceDirs.forEach(function(dir) {
const path = tryResolveAppPath(appRootDir, dir);
if (!path) {
debug('Skipping unknown module source dir %j', dir);
return;
}
files = files.concat(findScripts(path, scriptExtensions));
});
return files;
}
function loadMixins(sourceFiles, normalization) {
const mixinInstructions = {};
sourceFiles.forEach(function(filepath) {
const dir = path.dirname(filepath);
const ext = path.extname(filepath);
let name = path.basename(filepath, ext);
const metafile = path.join(dir, name + FILE_EXTENSION_JSON);
name = normalizeMixinName(name, normalization);
const meta = {};
meta.name = name;
if (utils.fileExistsSync(metafile)) {
// May overwrite name, not sourceFile
_.extend(meta, require(metafile));
}
meta.sourceFile = filepath;
mixinInstructions[meta.name] = meta;
});
return mixinInstructions;
}
function fetchMixinNamesUsedInModelInstructions(modelInstructions) {
return _.flatten(modelInstructions
.map(function(model) {
return model.definition && model.definition.mixins ?
Object.keys(model.definition.mixins) : [];
}));
}
function filterMixinInstructionsUsingWhitelist(instructions, includeMixins) {
const instructionKeys = Object.keys(instructions);
includeMixins = _.intersection(instructionKeys, includeMixins);
const filteredInstructions = {};
instructionKeys.forEach(function(mixinName) {
if (includeMixins.indexOf(mixinName) !== -1) {
filteredInstructions[mixinName] = instructions[mixinName];
}
});
return filteredInstructions;
}
function normalizeMixinName(str, normalization) {
switch (normalization) {
case false:
case 'none':
return str;
case undefined:
case 'classify':
str = String(str).replace(/([A-Z]+)/g, ' $1').trim();
str = String(str).replace(/[\W_]/g, ' ').toLowerCase();
str = str.replace(/(?:^|\s|-)\S/g, function(c) {
return c.toUpperCase();
});
str = str.replace(/\s+/g, '');
return str;
case 'dasherize':
str = String(str).replace(/([A-Z]+)/g, ' $1').trim();
str = String(str).replace(/[\W_]/g, ' ').toLowerCase();
str = str.replace(/\s+/g, '-');
return str;
default:
if (typeof normalization === 'function') {
return normalization(str);
}
const err = new Error(g.f('Invalid normalization format - "%s"',
normalization));
err.code = 'INVALID_NORMALIZATION_FORMAT';
throw err;
}
}
Mixin.prototype.starting = function(context) {
const app = context.app;
const instructions = context.instructions.mixins;
const modelBuilder = (app.registry || app.loopback).modelBuilder;
const BaseClass = app.loopback.Model;
const mixins = instructions || [];
if (!modelBuilder.mixins || !mixins.length) return;
mixins.forEach(function(obj) {
debug('Requiring mixin %s', obj.sourceFile);
const mixin = require(obj.sourceFile);
if (typeof mixin === 'function' || mixin.prototype instanceof BaseClass) {
debug('Defining mixin %s', obj.name);
modelBuilder.mixins.define(obj.name, mixin); // TODO (name, mixin, meta)
} else {
debug('Skipping mixin file %s - `module.exports` is not a function' +
' or Loopback model', obj);
}
});
};

View File

@ -1,319 +0,0 @@
// Copyright IBM Corp. 2016,2019. All Rights Reserved.
// Node module: loopback-boot
// This file is licensed under the MIT License.
// License text available at https://opensource.org/licenses/MIT
'use strict';
const assert = require('assert');
const util = require('util');
const PluginBase = require('../plugin-base');
const path = require('path');
const debug = require('debug')('loopback:boot:model');
const _ = require('lodash');
const toposort = require('toposort');
const utils = require('../utils');
const tryReadDir = utils.tryReadDir;
const assertIsValidConfig = utils.assertIsValidConfig;
const tryResolveAppPath = utils.tryResolveAppPath;
const fixFileExtension = utils.fixFileExtension;
const g = require('../globalize');
module.exports = function(options) {
return new Model(options);
};
function Model(options) {
PluginBase.call(this, options, 'models', 'model-config');
}
util.inherits(Model, PluginBase);
Model.prototype.getRootDir = function() {
return this.options.modelsRootDir;
};
Model.prototype.load = function(context) {
const config = PluginBase.prototype.load.apply(this, arguments);
assertIsValidModelConfig(config);
return config;
};
Model.prototype.buildInstructions = function(context, rootDir, modelsConfig) {
const modelsMeta = modelsConfig._meta || {};
delete modelsConfig._meta;
context.configurations.mixins._meta = modelsMeta;
const modelSources = this.options.modelSources || modelsMeta.sources ||
['./models'];
const modelInstructions = buildAllModelInstructions(
rootDir, modelsConfig, modelSources, this.options.modelDefinitions,
this.options.scriptExtensions,
);
return modelInstructions;
};
function buildAllModelInstructions(rootDir, modelsConfig, sources,
modelDefinitions, scriptExtensions) {
let registry = verifyModelDefinitions(rootDir, modelDefinitions,
scriptExtensions);
if (!registry) {
registry = findModelDefinitions(rootDir, sources, scriptExtensions);
}
const modelNamesToBuild = addAllBaseModels(
registry,
Object.keys(modelsConfig),
);
const instructions = modelNamesToBuild
.map(function createModelInstructions(name) {
const config = modelsConfig[name];
const definition = registry[name] || {};
debug('Using model "%s"\nConfiguration: %j\nDefinition %j',
name, config, definition.definition);
return {
name: name,
config: config,
definition: definition.definition,
sourceFile: definition.sourceFile,
};
});
return sortByInheritance(instructions);
}
function addAllBaseModels(registry, modelNames) {
const result = [];
const visited = {};
while (modelNames.length) {
const name = modelNames.shift();
if (visited[name]) continue;
visited[name] = true;
result.push(name);
const definition = registry[name] && registry[name].definition;
if (!definition) continue;
const base = getBaseModelName(definition);
// ignore built-in models like User
if (!registry[base]) continue;
modelNames.push(base);
}
return result;
}
function getBaseModelName(modelDefinition) {
if (!modelDefinition)
return undefined;
return modelDefinition.base ||
modelDefinition.options && modelDefinition.options.base;
}
function sortByInheritance(instructions) {
// create edges Base name -> Model name
const edges = instructions
.map(function(inst) {
return [getBaseModelName(inst.definition), inst.name];
});
const sortedNames = toposort(edges);
const instructionsByModelName = {};
instructions.forEach(function(inst) {
instructionsByModelName[inst.name] = inst;
});
return sortedNames
// convert to instructions
.map(function(name) {
return instructionsByModelName[name];
})
// remove built-in models
.filter(function(inst) {
return !!inst;
});
}
function verifyModelDefinitions(rootDir, modelDefinitions, scriptExtensions) {
if (!modelDefinitions || modelDefinitions.length < 1) {
return undefined;
}
const registry = {};
modelDefinitions.forEach(function(definition, idx) {
if (definition.sourceFile) {
const fullPath = path.resolve(rootDir, definition.sourceFile);
definition.sourceFile = fixFileExtension(
fullPath,
tryReadDir(path.dirname(fullPath)),
scriptExtensions,
);
if (!definition.sourceFile) {
debug('Model source code not found: %s - %s', definition.sourceFile);
}
}
debug('Found model "%s" - %s %s',
definition.definition.name,
'from options',
definition.sourceFile ?
path.relative(rootDir, definition.sourceFile) :
'(no source file)');
const modelName = definition.definition.name;
if (!modelName) {
debug('Skipping model definition without Model name ' +
'(from options.modelDefinitions @ index %s)',
idx);
return;
}
registry[modelName] = definition;
});
return registry;
}
function findModelDefinitions(rootDir, sources, scriptExtensions) {
const registry = {};
sources.forEach(function(src) {
const srcDir = tryResolveAppPath(rootDir, src, {strict: false});
if (!srcDir) {
debug('Skipping unknown module source dir %j', src);
return;
}
const files = tryReadDir(srcDir);
files
.filter(function(f) {
return f[0] !== '_' && path.extname(f) === '.json';
})
.forEach(function(f) {
const fullPath = path.resolve(srcDir, f);
const entry = loadModelDefinition(rootDir, fullPath, files,
scriptExtensions);
const modelName = entry.definition.name;
if (!modelName) {
debug('Skipping model definition without Model name: %s',
path.relative(srcDir, fullPath));
return;
}
registry[modelName] = entry;
});
});
return registry;
}
function loadModelDefinition(rootDir, jsonFile, allFiles, scriptExtensions) {
const definition = require(jsonFile);
const basename = path.basename(jsonFile, path.extname(jsonFile));
definition.name = definition.name || _.upperFirst(_.camelCase(basename));
// find a matching file with a supported extension like `.js` or `.coffee`
const sourceFile = fixFileExtension(jsonFile, allFiles, scriptExtensions);
if (sourceFile === undefined) {
debug('Model source code not found: %s', sourceFile);
}
debug('Found model "%s" - %s %s', definition.name,
path.relative(rootDir, jsonFile),
sourceFile ? path.relative(rootDir, sourceFile) : '(no source file)');
return {
definition: definition,
sourceFile: sourceFile,
};
}
function assertIsValidModelConfig(config) {
assertIsValidConfig('model', config);
for (const name in config) {
const entry = config[name];
const options = entry.options || {};
const unsupported = entry.properties ||
entry.base || options.base ||
entry.plural || options.plural;
if (unsupported) {
throw new Error(g.f(
'The data in {{model-config.json}} ' +
'is in the unsupported {{1.x}} format.',
));
}
}
}
// Regular expression to match built-in loopback models
const LOOPBACK_MODEL_REGEXP = new RegExp(
['', 'node_modules', 'loopback', '[^\\/\\\\]+', 'models', '[^\\/\\\\]+\\.js$']
.join('\\' + path.sep),
);
function isBuiltinLoopBackModel(app, data) {
// 1. Built-in models are exposed on the loopback object
if (!app.loopback[data.name]) return false;
// 2. Built-in models have a script file `loopback/{facet}/models/{name}.js`
const srcFile = data.sourceFile;
return srcFile &&
LOOPBACK_MODEL_REGEXP.test(srcFile);
}
Model.prototype.start = function(context) {
const app = context.app;
const instructions = context.instructions[this.name];
const registry = app.registry || app.loopback;
instructions.forEach(function(data) {
const name = data.name;
let model;
if (!data.definition) {
model = registry.getModel(name);
if (!model) {
throw new Error(g.f('Cannot configure unknown model %s', name));
}
debug('Configuring existing model %s', name);
} else if (isBuiltinLoopBackModel(app, data)) {
model = registry.getModel(name);
assert(model, 'Built-in model ' + name + ' should have been defined');
debug('Configuring built-in LoopBack model %s', name);
} else {
debug('Creating new model %s %j', name, data.definition);
model = registry.createModel(data.definition);
if (data.sourceFile) {
debug('Loading customization script %s', data.sourceFile);
const code = require(data.sourceFile);
if (typeof code === 'function') {
debug('Customizing model %s', name);
code(model);
} else {
debug('Skipping model file %s - `module.exports` is not a function',
data.sourceFile);
}
}
}
data._model = model;
});
instructions.forEach(function(data) {
// Skip base models that are not exported to the app
if (!data.config) return;
app.model(data._model, data.config);
});
};

View File

@ -1,31 +0,0 @@
// Copyright IBM Corp. 2016,2019. All Rights Reserved.
// Node module: loopback-boot
// This file is licensed under the MIT License.
// License text available at https://opensource.org/licenses/MIT
'use strict';
const util = require('util');
const PluginBase = require('../plugin-base');
module.exports = function(options) {
return new Swagger(options);
};
function Swagger(options) {
PluginBase.call(this, options, 'apis', null);
}
util.inherits(Swagger, PluginBase);
Swagger.prototype.start = function(context) {
const app = context.app;
const appConfig = context.instructions.application;
// disable token requirement for swagger, if available
const swagger = app.remotes().exports.swagger;
if (!swagger) return;
const requireTokenForSwagger = appConfig.swagger &&
appConfig.swagger.requireToken;
swagger.requireToken = requireTokenForSwagger || false;
};

9
lib/require.js Normal file
View File

@ -0,0 +1,9 @@
// Copyright IBM Corp. 2015,2017. All Rights Reserved.
// Node module: loopback-boot
// This file is licensed under the MIT License.
// License text available at https://opensource.org/licenses/MIT
module.exports = function requireNodeOrEsModule(sourceFile) {
var exports = require(sourceFile);
return exports && exports.__esModule ? exports.default : exports;
};

View File

@ -1,354 +1,11 @@
// Copyright IBM Corp. 2016,2019. All Rights Reserved.
// Copyright IBM Corp. 2014,2016. All Rights Reserved.
// Node module: loopback-boot
// This file is licensed under the MIT License.
// License text available at https://opensource.org/licenses/MIT
'use strict';
var fs = require('fs');
const debug = require('debug')('loopback:boot');
const path = require('path');
const Module = require('module');
const fs = require('fs');
const assert = require('assert');
const _ = require('lodash');
const g = require('./globalize');
exports.arrayToObject = arrayToObject;
exports.tryReadDir = tryReadDir;
exports.resolveRelativePaths = resolveRelativePaths;
exports.assertIsValidConfig = assertIsValidConfig;
exports.fileExistsSync = fileExistsSync;
exports.fixFileExtension = fixFileExtension;
exports.findScripts = findScripts;
exports.resolveAppScriptPath = resolveAppScriptPath;
exports.getExcludedExtensions = getExcludedExtensions;
exports.tryResolveAppPath = tryResolveAppPath;
exports.forEachKeyedObject = forEachKeyedObject;
exports.mergePhaseNameLists = mergePhaseNameLists;
const FILE_EXTENSION_JSON = exports.FILE_EXTENSION_JSON = '.json';
/**
* Find all javascript files (except for those prefixed with _)
* and all directories.
* @param {String} dir Full path of the directory to enumerate.
* @return {Array.<String>} A list of absolute paths to pass to `require()`.
*/
function findScripts(dir, scriptExtensions) {
assert(dir, 'cannot require directory contents without directory name');
const files = tryReadDir(dir);
scriptExtensions = scriptExtensions || require.extensions;
// sort files in lowercase alpha for linux
files.sort(function(a, b) {
a = a.toLowerCase();
b = b.toLowerCase();
if (a < b) {
return -1;
} else if (b < a) {
return 1;
} else {
return 0;
}
});
const results = [];
files.forEach(function(filename) {
// ignore index.js and files prefixed with underscore
if (filename === 'index.js' || filename[0] === '_') {
return;
}
const filepath = path.resolve(path.join(dir, filename));
const stats = fs.statSync(filepath);
// only require files supported by specified extensions
if (stats.isFile()) {
if (scriptExtensions && isPreferredExtension(filename, scriptExtensions))
results.push(filepath);
else
debug('Skipping file %s - unknown extension', filepath);
} else {
debug('Skipping directory %s', filepath);
}
});
return results;
}
function tryReadDir() {
try {
return fs.readdirSync.apply(fs, arguments);
} catch (e) {
return [];
}
}
function resolveRelativePaths(relativePaths, appRootDir) {
const resolveOpts = {strict: false};
relativePaths.forEach(function(relativePath, k) {
const resolvedPath = tryResolveAppPath(
appRootDir,
relativePath,
resolveOpts,
);
if (resolvedPath !== undefined) {
relativePaths[k] = resolvedPath;
} else {
debug('skipping boot script %s - unknown file', relativePath);
}
});
}
function getExcludedExtensions() {
return {
'.json': '.json',
'.node': 'node',
/**
* This is a temporary workaround for #246
* See discussion here for full description of the underlying issue
* https://github.com/strongloop/loopback-boot/pull/245#issuecomment-311052798
*/
'.map': 'map',
};
}
function arrayToObject(array) {
return array.reduce(function(obj, val) {
obj[val] = val;
return obj;
}, {});
}
function isPreferredExtension(filename, includeExtensions) {
assert(!!includeExtensions, '"includeExtensions" argument is required');
const ext = path.extname(filename);
return (ext in includeExtensions) && !(ext in getExcludedExtensions());
}
function fixFileExtension(filepath, files, scriptExtensions) {
const results = [];
let otherFile;
/* Prefer coffee scripts over json */
if (scriptExtensions && isPreferredExtension(filepath, scriptExtensions)) {
return filepath;
}
const basename = path.basename(filepath, FILE_EXTENSION_JSON);
const sourceDir = path.dirname(filepath);
files.forEach(function(f) {
otherFile = path.resolve(sourceDir, f);
const stats = fs.statSync(otherFile);
if (stats.isFile()) {
const otherFileExtension = path.extname(f);
if (!(otherFileExtension in getExcludedExtensions()) &&
path.basename(f, otherFileExtension) == basename) {
if (!scriptExtensions || otherFileExtension in scriptExtensions) {
results.push(otherFile);
}
}
}
});
return results.length > 0 ? results[0] : undefined;
}
function resolveAppPath(rootDir, relativePath, resolveOptions) {
const resolvedPath = tryResolveAppPath(rootDir, relativePath, resolveOptions);
if (resolvedPath === undefined && !resolveOptions.optional) {
const err = new Error(g.f('Cannot resolve path "%s"', relativePath));
err.code = 'PATH_NOT_FOUND';
throw err;
}
return resolvedPath;
}
function resolveAppScriptPath(rootDir, relativePath, resolveOptions) {
const resolvedPath = resolveAppPath(rootDir, relativePath, resolveOptions);
if (!resolvedPath) {
return false;
}
const sourceDir = path.dirname(resolvedPath);
const files = tryReadDir(sourceDir);
const fixedFile = fixFileExtension(resolvedPath, files);
return (fixedFile === undefined ? resolvedPath : fixedFile);
}
function tryResolveAppPath(rootDir, relativePath, resolveOptions) {
let fullPath;
const start = relativePath.substring(0, 2);
/* In order to retain backward compatibility, we need to support
* two ways how to treat values that are not relative nor absolute
* path (e.g. `relativePath = 'foobar'`)
* - `resolveOptions.strict = true` searches in `node_modules` only
* - `resolveOptions.strict = false` attempts to resolve the value
* as a relative path first before searching `node_modules`
*/
resolveOptions = resolveOptions || {strict: true};
let isModuleRelative = false;
if (relativePath[0] === '/') {
fullPath = relativePath;
} else if (start === './' || start === '..') {
fullPath = path.resolve(rootDir, relativePath);
} else if (!resolveOptions.strict) {
isModuleRelative = true;
fullPath = path.resolve(rootDir, relativePath);
}
if (fullPath) {
// This check is needed to support paths pointing to a directory
if (fileExistsSync(fullPath)) {
return fullPath;
}
try {
fullPath = require.resolve(fullPath);
return fullPath;
} catch (err) {
if (!isModuleRelative) {
debug('Skipping %s - %s', fullPath, err);
return undefined;
}
}
}
// Handle module-relative path, e.g. `loopback/common/models`
// Module.globalPaths is a list of globally configured paths like
// [ env.NODE_PATH values, $HOME/.node_modules, etc. ]
// Module._nodeModulePaths(rootDir) returns a list of paths like
// [ rootDir/node_modules, rootDir/../node_modules, etc. ]
const modulePaths = Module.globalPaths
.concat(Module._nodeModulePaths(rootDir));
fullPath = modulePaths
.map(function(candidateDir) {
const absPath = path.join(candidateDir, relativePath);
try {
// NOTE(bajtos) We need to create a proper String object here,
// otherwise we can't attach additional properties to it
/* jshint -W053 */
const filePath = new String(require.resolve(absPath));
filePath.unresolvedPath = absPath;
return filePath;
} catch (err) {
return absPath;
}
})
.filter(function(candidate) {
return fileExistsSync(candidate.toString());
})
[0];
if (fullPath) {
if (fullPath.unresolvedPath && resolveOptions.fullResolve === false)
return fullPath.unresolvedPath;
// Convert String object back to plain string primitive
return fullPath.toString();
}
debug('Skipping %s - module not found', fullPath);
return undefined;
}
function assertIsValidConfig(name, config) {
if (config) {
assert(typeof config === 'object',
name + ' config must be a valid JSON object');
}
}
function forEachKeyedObject(obj, fn) {
if (typeof obj !== 'object') return;
Object.keys(obj).forEach(function(key) {
fn(key, obj[key]);
});
}
/**
* Extend the list of builtin phases by merging in an array of phases
* requested by a user while preserving the relative order of phases
* as specified by both arrays.
*
* If the first new name does not match any existing phase, it is inserted
* as the first phase in the new list. The same applies for the second phase,
* and so on, until an existing phase is found.
*
* Any new names in the middle of the array are inserted immediatelly after
* the last common phase. For example, extending
* `["initial", "session", "auth"]` with `["initial", "preauth", "auth"]`
* results in `["initial", "preauth", "session", "auth"]`.
*
*
* **Example**
*
* ```js
* var result = mergePhaseNameLists(
* ['initial', 'session', 'auth', 'routes', 'files', 'final'],
* ['initial', 'postinit', 'preauth', 'auth',
* 'routes', 'subapps', 'final', 'last']
* );
*
* // result: [
* // 'initial', 'postinit', 'preauth', 'session', 'auth',
* // 'routes', 'subapps', 'files', 'final', 'last'
* // ]
* ```
*
* @param {Array} currentNames The current list of phase names.
* @param {Array} namesToMerge The items to add (zip merge) into the target
* array.
* @returns {Array} A new array containing combined items from both arrays.
*
* @header mergePhaseNameLists
*/
function mergePhaseNameLists(currentNames, namesToMerge) {
if (!namesToMerge.length) return currentNames.slice();
const targetArray = currentNames.slice();
let targetIx = targetArray.indexOf(namesToMerge[0]);
if (targetIx === -1) {
// the first new item does not match any existing one
// start adding the new items at the start of the list
targetArray.splice(0, 0, namesToMerge[0]);
targetIx = 0;
}
// merge (zip) two arrays
for (let sourceIx = 1; sourceIx < namesToMerge.length; sourceIx++) {
const valueToAdd = namesToMerge[sourceIx];
const previousValue = namesToMerge[sourceIx - 1];
const existingIx = targetArray.indexOf(valueToAdd, targetIx);
if (existingIx === -1) {
// A new phase - try to add it after the last one,
// unless it was already registered
if (targetArray.indexOf(valueToAdd) !== -1) {
const errMsg = g.f('Ordering conflict: cannot add "%s' +
'" after "%s", because the opposite order was ' +
' already specified', valueToAdd, previousValue);
throw new Error(errMsg);
}
const previousIx = targetArray.indexOf(previousValue);
targetArray.splice(previousIx + 1, 0, valueToAdd);
} else {
// An existing phase - move the pointer
targetIx = existingIx;
}
}
return targetArray;
}
/**
* Check synchronously if a filepath points to an existing file.

View File

@ -1,6 +1,6 @@
{
"name": "loopback-boot",
"version": "3.3.1",
"version": "2.27.1",
"description": "Convention-based bootstrapper for LoopBack applications",
"keywords": [
"StrongLoop",
@ -12,40 +12,37 @@
"type": "git",
"url": "https://github.com/strongloop/loopback-boot"
},
"engines": {
"node": ">=8"
},
"main": "index.js",
"browser": "browser.js",
"scripts": {
"test": "mocha",
"posttest": "npm run lint",
"lint": "eslint .",
"lint:fix": "eslint . --fix"
"lint": "eslint ."
},
"engines": {
"node": ">=4"
},
"license": "MIT",
"dependencies": {
"async": "^2.4.0",
"bluebird": "^3.5.3",
"commondir": "^1.0.1",
"debug": "^4.1.1",
"lodash": "^4.17.11",
"semver": "^5.1.0",
"strong-globalize": "^4.1.1",
"toposort": "^2.0.2"
"async": "~0.9.0",
"commondir": "0.0.1",
"debug": "^2.0.0",
"lodash": "^4.17.5",
"semver": "^4.1.0",
"strong-globalize": "^2.6.2",
"toposort": "^0.2.10"
},
"devDependencies": {
"browserify": "^16.2.3",
"chai": "^4.2.0",
"coffeeify": "^3.0.1",
"coffeescript": "^2.3.1",
"dirty-chai": "^2.0.1",
"eslint": "^6.6.0",
"eslint-config-loopback": "^13.1.0",
"fs-extra": "^7.0.1",
"loopback": "^3.0.0",
"mocha": "^5.2.0",
"supertest": "^4.0.2"
},
"author": "IBM Corp."
"browserify": "^4.1.8",
"bluebird": "^3.1.1",
"chai": "^1.10.0",
"coffee-script": "^1.8.0",
"coffeeify": "^0.7.0",
"eslint": "^2.5.3",
"eslint-config-loopback": "^1.0.0",
"fs-extra": "^0.12.0",
"loopback": "^2.16.3",
"mocha": "^1.19.0",
"supertest": "^0.14.0"
}
}

View File

@ -1,40 +0,0 @@
// Copyright IBM Corp. 2019. All Rights Reserved.
// Node module: loopback-boot
// This file is licensed under the MIT License.
// License text available at https://opensource.org/licenses/MIT
'use strict';
const path = require('path');
const loopback = require('loopback');
const chai = require('chai');
const dirtyChai = require('dirty-chai');
const expect = chai.expect;
chai.use(dirtyChai);
const bootLoopBackApp = require('..');
describe('bootLoopBackApp', function() {
let app;
beforeEach(function() {
app = loopback();
});
it('sets app.booting immediately', function() {
const appDir = path.join(__dirname, './fixtures/empty-app');
// Start the bootstrapper
const promise = bootLoopBackApp(app, appDir);
// Still in the original turn of the event loop,
// verify that the app is signalling "boot in progress"
expect(app.booting).to.equal(true);
// Wait for bootstrapper to finish
return promise.then(() => {
// Verify that app is signalling "boot has finished"
expect(app.booting).to.equal(false);
});
});
});

View File

@ -1,122 +0,0 @@
// Copyright IBM Corp. 2016,2019. All Rights Reserved.
// Node module: loopback-boot
// This file is licensed under the MIT License.
// License text available at https://opensource.org/licenses/MIT
'use strict';
const path = require('path');
const loopback = require('loopback');
const chai = require('chai');
const dirtyChai = require('dirty-chai');
const expect = chai.expect;
chai.use(dirtyChai);
const Bootstrapper = require('../lib/bootstrapper');
describe('Bootstrapper', function() {
let app;
beforeEach(function() {
app = loopback();
process.bootFlags = [];
});
it('should honor options.phases', function(done) {
const options = {
app: app,
appRootDir: path.join(__dirname, './fixtures/simple-app'),
phases: ['load'],
};
const bootstrapper = new Bootstrapper(options);
const context = {
app: app,
};
bootstrapper.run(context, function(err) {
if (err) return done(err);
const configs = context.configurations;
expect(configs.application, 'application').to.be.an('object');
expect(configs.bootScripts, 'bootScripts').to.be.an('array');
expect(configs.middleware, 'middleware').to.be.an('object');
expect(configs.models, 'models').to.be.an('object');
expect(configs.tracker, 'tracker').to.eql('load');
expect(context.instructions, 'instructions').to.be.undefined();
expect(process.bootFlags.length).to.eql(0);
done();
});
});
it('should honor options.plugins', function(done) {
const options = {
app: app,
appRootDir: path.join(__dirname, './fixtures/simple-app'),
plugins: ['application', 'boot-script'],
};
const bootstrapper = new Bootstrapper(options);
const context = {
app: app,
};
bootstrapper.run(context, function(err) {
if (err) return done(err);
const configs = context.configurations;
const instructions = context.instructions;
expect(configs.application, 'application').to.be.an('object');
expect(configs.middleware, 'middleware').to.be.undefined();
expect(configs.models, 'models').to.be.undefined();
expect(configs.bootScripts, 'bootScripts').to.be.an('array');
expect(instructions.application, 'application').to.be.an('object');
expect(instructions.tracker, 'instruction: tracker').to.eql('compile');
expect(context.executions.tracker, 'execution: tracker').to.eql('start');
expect(process.bootFlags, 'process: bootFlags').to.eql([
'barLoaded',
'barSyncLoaded',
'fooLoaded',
'promiseLoaded',
'thenableLoaded',
'barStarted',
'barFinished',
'barSyncExecuted',
'promiseStarted',
'promiseFinished',
'thenableStarted',
'thenableFinished',
'umdLoaded',
]);
done();
});
});
it('searches boot file extensions specified in options.scriptExtensions',
function(done) {
const options = {
app: app,
appRootDir: path.join(__dirname, './fixtures/simple-app'),
scriptExtensions: ['.customjs', '.customjs2'],
};
const bootstrapper = new Bootstrapper(options);
const context = {
app: app,
};
bootstrapper.run(context, function(err) {
if (err) return done(err);
expect(process.bootFlags, 'process: bootFlags').to.eql([
'customjs',
'customjs2',
]);
done();
});
});
afterEach(function() {
delete process.bootFlags;
});
});

View File

@ -1,22 +1,18 @@
// Copyright IBM Corp. 2015,2019. All Rights Reserved.
// Copyright IBM Corp. 2015,2016. All Rights Reserved.
// Node module: loopback-boot
// This file is licensed under the MIT License.
// License text available at https://opensource.org/licenses/MIT
'use strict';
const boot = require('../');
const async = require('async');
const exportBrowserifyToFile = require('./helpers/browserify').exportToSandbox;
const packageFilter = require('./helpers/browserify').packageFilter;
const fs = require('fs');
const path = require('path');
const expect = require('chai').expect;
const browserify = require('browserify');
const sandbox = require('./helpers/sandbox');
const vm = require('vm');
const createBrowserLikeContext = require('./helpers/browser').createContext;
const printContextLogs = require('./helpers/browser').printContextLogs;
var boot = require('../');
var exportBrowserifyToFile = require('./helpers/browserify').exportToSandbox;
var fs = require('fs');
var path = require('path');
var expect = require('chai').expect;
var browserify = require('browserify');
var sandbox = require('./helpers/sandbox');
var vm = require('vm');
var createBrowserLikeContext = require('./helpers/browser').createContext;
var printContextLogs = require('./helpers/browser').printContextLogs;
describe('browser support for multiple apps', function() {
this.timeout(60000); // 60s to give browserify enough time to finish
@ -24,10 +20,10 @@ describe('browser support for multiple apps', function() {
beforeEach(sandbox.reset);
it('has API for bundling and booting multiple apps', function(done) {
const app1Dir = path.resolve(__dirname, './fixtures/browser-app');
const app2Dir = path.resolve(__dirname, './fixtures/browser-app-2');
var app1Dir = path.resolve(__dirname, './fixtures/browser-app');
var app2Dir = path.resolve(__dirname, './fixtures/browser-app-2');
const apps = [
var apps = [
{
appDir: app1Dir,
appFile: './app.js',
@ -44,82 +40,67 @@ describe('browser support for multiple apps', function() {
browserifyTestApps(apps, function(err, bundlePath) {
if (err) return done(err);
const bundledApps = executeBundledApps(bundlePath, apps, function(err) {
const app1 = bundledApps.defaultApp;
const app2 = bundledApps.browserApp2;
var bundledApps = executeBundledApps(bundlePath, apps);
var app1 = bundledApps.defaultApp;
var app2 = bundledApps.browserApp2;
expect(app1.settings).to.have.property('custom-key', 'custom-value');
expect(Object.keys(app1.models)).to.include('Customer');
expect(Object.keys(app1.models)).to.not.include('Robot');
expect(app1.models.Customer.settings).to.have.property('_customized',
'Customer');
expect(app1.settings).to.have.property('custom-key', 'custom-value');
expect(Object.keys(app1.models)).to.include('Customer');
expect(Object.keys(app1.models)).to.not.include('Robot');
expect(app1.models.Customer.settings).to.have.property('_customized',
'Customer');
expect(Object.keys(app2.models)).to.include('Robot');
expect(Object.keys(app2.models)).to.not.include('Customer');
expect(Object.keys(app2.models)).to.include('Robot');
expect(Object.keys(app2.models)).to.not.include('Customer');
done();
});
done();
});
});
});
function browserifyTestApps(apps, next) {
const b = browserify({
var b = browserify({
debug: true,
basedir: path.resolve(__dirname, './fixtures'),
packageFilter,
});
const bundles = [];
for (const i in apps) {
const appDir = apps[i].appDir;
let appFile = apps[i].appFile;
const moduleName = apps[i].moduleName;
const appId = apps[i].appId;
for (var i in apps) {
var appDir = apps[i].appDir;
var appFile = apps[i].appFile;
var moduleName = apps[i].moduleName;
var appId = apps[i].appId;
appFile = path.join(appDir, appFile);
b.require(appFile, {expose: moduleName});
b.require(appFile, { expose: moduleName });
let opts = appDir;
var opts = appDir;
if (appId) {
opts = {
appId: appId,
appRootDir: appDir,
};
}
bundles.push(opts);
boot.compileToBrowserify(opts, b);
}
async.eachSeries(bundles, function(opts, done) {
boot.compileToBrowserify(opts, b, done);
}, function(err) {
exportBrowserifyToFile(b, 'browser-app-bundle.js', next);
});
exportBrowserifyToFile(b, 'browser-app-bundle.js', next);
}
function executeBundledApps(bundlePath, apps, done) {
const code = fs.readFileSync(bundlePath);
const context = createBrowserLikeContext();
function executeBundledApps(bundlePath, apps) {
var code = fs.readFileSync(bundlePath);
var context = createBrowserLikeContext();
vm.runInContext(code, context, bundlePath);
const ids = [];
let script = 'var apps = {};\n';
for (const i in apps) {
const moduleName = apps[i].moduleName;
const id = apps[i].appId || 'defaultApp';
ids.push(id);
var script = 'var apps = {};\n';
for (var i in apps) {
var moduleName = apps[i].moduleName;
var id = apps[i].appId || 'defaultApp';
script += 'apps.' + id + ' = require("' + moduleName + '");\n';
}
script += 'apps;\n';
const appsInContext = vm.runInContext(script, context);
async.each(ids, function(id, done) {
appsInContext[id].once('booted', function() {
done();
});
}, function(err) {
printContextLogs(context);
done(err, appsInContext);
});
var appsInContext = vm.runInContext(script, context);
printContextLogs(context);
return appsInContext;
}

View File

@ -1,43 +1,40 @@
// Copyright IBM Corp. 2014,2019. All Rights Reserved.
// Copyright IBM Corp. 2014,2016. All Rights Reserved.
// Node module: loopback-boot
// This file is licensed under the MIT License.
// License text available at https://opensource.org/licenses/MIT
'use strict';
var boot = require('../');
var exportBrowserifyToFile = require('./helpers/browserify').exportToSandbox;
var fs = require('fs');
var path = require('path');
var expect = require('chai').expect;
var browserify = require('browserify');
var sandbox = require('./helpers/sandbox');
var vm = require('vm');
var createBrowserLikeContext = require('./helpers/browser').createContext;
var printContextLogs = require('./helpers/browser').printContextLogs;
const boot = require('../');
const exportBrowserifyToFile = require('./helpers/browserify').exportToSandbox;
const packageFilter = require('./helpers/browserify').packageFilter;
const fs = require('fs');
const path = require('path');
const expect = require('chai').expect;
const browserify = require('browserify');
const sandbox = require('./helpers/sandbox');
const vm = require('vm');
const createBrowserLikeContext = require('./helpers/browser').createContext;
const printContextLogs = require('./helpers/browser').printContextLogs;
const compileStrategies = {
default: function(appDir) {
const b = browserify({
var compileStrategies = {
'default': function(appDir) {
var b = browserify({
basedir: appDir,
debug: true,
packageFilter,
});
b.require('./app.js', {expose: 'browser-app'});
b.require('./app.js', { expose: 'browser-app' });
return b;
},
coffee: function(appDir) {
const b = browserify({
'coffee': function(appDir) {
var b = browserify({
basedir: appDir,
extensions: ['.coffee'],
debug: true,
packageFilter,
});
b.transform('coffeeify');
b.require('./app.coffee', {expose: 'browser-app'});
b.require('./app.coffee', { expose: 'browser-app' });
return b;
},
};
@ -48,96 +45,98 @@ describe('browser support', function() {
beforeEach(sandbox.reset);
it('has API for bundling and executing boot instructions', function(done) {
const appDir = path.resolve(__dirname, './fixtures/browser-app');
var appDir = path.resolve(__dirname, './fixtures/browser-app');
browserifyTestApp(appDir, function(err, bundlePath) {
if (err) return done(err);
const app = executeBundledApp(bundlePath, function(err) {
if (err) return done(err);
// configured in fixtures/browser-app/boot/configure.js
expect(app.settings).to.have.property('custom-key', 'custom-value');
expect(Object.keys(app.models)).to.include('Customer');
expect(app.models.Customer.settings).to.have.property(
'_customized',
'Customer',
);
var app = executeBundledApp(bundlePath);
// configured in fixtures/browser-app/component-config.json
// and fixtures/browser-app/components/dummy-component.js
expect(app.dummyComponentOptions).to.eql({option: 'value'});
done();
});
// configured in fixtures/browser-app/boot/configure.js
expect(app.settings).to.have.property('custom-key', 'custom-value');
expect(Object.keys(app.models)).to.include('Customer');
expect(app.models.Customer.settings)
.to.have.property('_customized', 'Customer');
expect(Object.keys(app.models)).to.include('ProductUmd');
expect(app.models.ProductUmd.settings)
.to.have.property('_customized', 'UMD');
// configured in fixtures/browser-app/component-config.json,
// fixtures/browser-app/components/dummy-component.js and
// fixtures/browser-app/components/dummy-component-umd.js
expect(app.dummyComponentOptions).to.eql({ option: 'value' });
expect(app.dummyComponentUmdOptions).to.eql({ option: 'valueUmd' });
done();
});
});
it('loads mixins', function(done) {
const appDir = path.resolve(__dirname, './fixtures/browser-app');
const options = {
var appDir = path.resolve(__dirname, './fixtures/browser-app');
var options = {
appRootDir: appDir,
};
browserifyTestApp(options, function(err, bundlePath) {
if (err) return done(err);
const app = executeBundledApp(bundlePath, function(err) {
const modelBuilder = app.registry.modelBuilder;
const registry = modelBuilder.mixins.mixins;
expect(Object.keys(registry)).to.eql(['TimeStamps']);
expect(app.models.Customer.timeStampsMixin).to.eql(true);
var app = executeBundledApp(bundlePath);
done();
});
var modelBuilder = app.registry.modelBuilder;
var registry = modelBuilder.mixins.mixins;
expect(Object.keys(registry)).to.eql(['AuditedUmd', 'TimeStamps']);
expect(app.models.Customer.timeStampsMixin).to.eql(true);
expect(app.models.ProductUmd.auditedMixin).to.eql(true);
done();
});
});
it('supports coffee-script files', function(done) {
// add coffee-script to require.extensions
require('coffeescript/register');
require('coffee-script/register');
const appDir = path.resolve(__dirname, './fixtures/coffee-app');
var appDir = path.resolve(__dirname, './fixtures/coffee-app');
browserifyTestApp(appDir, 'coffee', function(err, bundlePath) {
if (err) return done(err);
const app = executeBundledApp(bundlePath, function(err) {
// configured in fixtures/browser-app/boot/configure.coffee
expect(app.settings).to.have.property('custom-key', 'custom-value');
expect(Object.keys(app.models)).to.include('Customer');
expect(app.models.Customer.settings).to.have.property(
'_customized',
'Customer',
);
done();
});
var app = executeBundledApp(bundlePath);
// configured in fixtures/browser-app/boot/configure.coffee
expect(app.settings).to.have.property('custom-key', 'custom-value');
expect(Object.keys(app.models)).to.include('Customer');
expect(app.models.Customer.settings)
.to.have.property('_customized', 'Customer');
done();
});
});
});
function browserifyTestApp(options, strategy, next) {
// set default args
if (typeof strategy === 'function' && !next) {
if (((typeof strategy) === 'function') && !next) {
next = strategy;
strategy = undefined;
}
if (!strategy) strategy = 'default';
if (!strategy)
strategy = 'default';
const appDir = typeof options === 'object' ? options.appRootDir : options;
const b = compileStrategies[strategy](appDir);
var appDir = typeof(options) === 'object' ? options.appRootDir : options;
var b = compileStrategies[strategy](appDir);
boot.compileToBrowserify(options, b, function(err) {
exportBrowserifyToFile(b, 'browser-app-bundle.js', next);
});
boot.compileToBrowserify(options, b);
exportBrowserifyToFile(b, 'browser-app-bundle.js', next);
}
function executeBundledApp(bundlePath, done) {
const code = fs.readFileSync(bundlePath);
const context = createBrowserLikeContext();
function executeBundledApp(bundlePath) {
var code = fs.readFileSync(bundlePath);
var context = createBrowserLikeContext();
vm.runInContext(code, context, bundlePath);
const app = vm.runInContext('require("browser-app")', context);
app.once('booted', function(err) {
printContextLogs(context);
done(err, app);
});
var app = vm.runInContext('require("browser-app")', context);
printContextLogs(context);
return app;
}

File diff suppressed because it is too large Load Diff

View File

@ -0,0 +1,37 @@
// Copyright IBM Corp. 2014,2016. All Rights Reserved.
// Node module: loopback-boot
// This file is licensed under the MIT License.
// License text available at https://opensource.org/licenses/MIT
var configLoader = require('../lib/config-loader');
var fs = require('fs-extra');
var path = require('path');
var expect = require('chai').expect;
var sandbox = require('./helpers/sandbox');
var appdir = require('./helpers/appdir');
describe('config-loader', function() {
beforeEach(sandbox.reset);
beforeEach(appdir.init);
it('does not cache loaded values', function() {
appdir.createConfigFilesSync();
appdir.writeConfigFileSync('middleware.json', {
'strong-error-handler': { params: { debug: false }},
});
appdir.writeConfigFileSync('middleware.development.json', {
'strong-error-handler': { params: { debug: true }},
});
// Here we load main config and merge it with DEV overrides
var config = configLoader.loadMiddleware(appdir.PATH, 'development');
expect(config['strong-error-handler'].params.debug, 'debug in development')
.to.equal(true);
// When we load the config file again in different environment,
// only the main file is loaded and no overrides are applied.
config = configLoader.loadMiddleware(appdir.PATH, 'production');
expect(config['strong-error-handler'].params.debug, 'debug in production')
.to.equal(false);
});
});

File diff suppressed because it is too large Load Diff

View File

@ -1,14 +1,13 @@
// Copyright IBM Corp. 2015,2019. All Rights Reserved.
// Copyright IBM Corp. 2015,2016. All Rights Reserved.
// Node module: loopback-boot
// This file is licensed under the MIT License.
// License text available at https://opensource.org/licenses/MIT
'use strict';
var loopback = require('loopback');
var boot = require('../../../');
const loopback = require('loopback');
const boot = require('../../../');
var app = module.exports = loopback();
const app = module.exports = loopback();
boot(app, {
appId: 'browserApp2',
appRootDir: __dirname,

View File

@ -1,10 +1,8 @@
// Copyright IBM Corp. 2015,2019. All Rights Reserved.
// Copyright IBM Corp. 2015. All Rights Reserved.
// Node module: loopback-boot
// This file is licensed under the MIT License.
// License text available at https://opensource.org/licenses/MIT
'use strict';
module.exports = function(Robot) {
Robot.settings._customized = 'Robot';
Robot.base.settings._customized = 'Robot';

View File

@ -1,12 +1,10 @@
// Copyright IBM Corp. 2014,2019. All Rights Reserved.
// Copyright IBM Corp. 2014,2016. All Rights Reserved.
// Node module: loopback-boot
// This file is licensed under the MIT License.
// License text available at https://opensource.org/licenses/MIT
'use strict';
var loopback = require('loopback');
var boot = require('../../../');
const loopback = require('loopback');
const boot = require('../../../');
const app = module.exports = loopback();
boot(app, __dirname);
var app = module.exports = loopback();
boot(app);

View File

@ -1,10 +1,8 @@
// Copyright IBM Corp. 2014,2019. All Rights Reserved.
// Copyright IBM Corp. 2014. All Rights Reserved.
// Node module: loopback-boot
// This file is licensed under the MIT License.
// License text available at https://opensource.org/licenses/MIT
'use strict';
module.exports = function(app) {
app.set('custom-key', 'custom-value');
};

View File

@ -1,5 +1,8 @@
{
"./components/dummy-component": {
"option": "value"
},
"./components/dummy-component-umd": {
"option": "valueUmd"
}
}

View File

@ -0,0 +1,11 @@
// Copyright IBM Corp. 2015,2017. All Rights Reserved.
// Node module: loopback-boot
// This file is licensed under the MIT License.
// License text available at https://opensource.org/licenses/MIT
module.exports = {
default: function(app, options) {
app.dummyComponentUmdOptions = options;
},
};
Object.defineProperty(module.exports, '__esModule', { value: true });

View File

@ -1,10 +1,8 @@
// Copyright IBM Corp. 2015,2019. All Rights Reserved.
// Copyright IBM Corp. 2015. All Rights Reserved.
// Node module: loopback-boot
// This file is licensed under the MIT License.
// License text available at https://opensource.org/licenses/MIT
'use strict';
module.exports = function(app, options) {
app.dummyComponentOptions = options;
};

View File

@ -0,0 +1,11 @@
// Copyright IBM Corp. 2014,2017. All Rights Reserved.
// Node module: loopback-boot
// This file is licensed under the MIT License.
// License text available at https://opensource.org/licenses/MIT
module.exports = {
default: function(Model, options) {
Model.auditedMixin = true;
},
};
Object.defineProperty(module.exports, '__esModule', { value: true });

View File

@ -1,10 +1,8 @@
// Copyright IBM Corp. 2015,2019. All Rights Reserved.
// Copyright IBM Corp. 2015,2016. All Rights Reserved.
// Node module: loopback-boot
// This file is licensed under the MIT License.
// License text available at https://opensource.org/licenses/MIT
'use strict';
module.exports = function(Model, options) {
Model.timeStampsMixin = true;
};

View File

@ -7,5 +7,8 @@
},
"Customer": {
"dataSource": "db"
},
"ProductUmd": {
"dataSource": "db"
}
}

View File

@ -1,10 +1,8 @@
// Copyright IBM Corp. 2014,2019. All Rights Reserved.
// Copyright IBM Corp. 2014. All Rights Reserved.
// Node module: loopback-boot
// This file is licensed under the MIT License.
// License text available at https://opensource.org/licenses/MIT
'use strict';
module.exports = function(Customer) {
Customer.settings._customized = 'Customer';
Customer.base.settings._customized = 'Base';

View File

@ -0,0 +1,11 @@
// Copyright IBM Corp. 2014,2017. All Rights Reserved.
// Node module: loopback-boot
// This file is licensed under the MIT License.
// License text available at https://opensource.org/licenses/MIT
module.exports = {
default: function(ProductUmd) {
ProductUmd.settings._customized = 'UMD';
},
};
Object.defineProperty(module.exports, '__esModule', { value: true });

View File

@ -0,0 +1,5 @@
{
"name": "ProductUmd",
"base": "User",
"mixins": {"AuditedUmd": {} }
}

View File

@ -2,4 +2,4 @@ loopback = require 'loopback'
boot = require '../../../'
module.exports = client = loopback()
boot(client, __dirname)
boot(client)

View File

@ -1,2 +0,0 @@
{
}

View File

@ -1,10 +1,8 @@
// Copyright IBM Corp. 2015,2019. All Rights Reserved.
// Copyright IBM Corp. 2015. All Rights Reserved.
// Node module: loopback-boot
// This file is licensed under the MIT License.
// License text available at https://opensource.org/licenses/MIT
'use strict';
process.bootFlags.push('barLoadedInTest');
module.exports = function(app, callback) {
callback();

View File

@ -1,11 +1,9 @@
// Copyright IBM Corp. 2015,2019. All Rights Reserved.
// Copyright IBM Corp. 2015,2016. All Rights Reserved.
// Node module: loopback-boot
// This file is licensed under the MIT License.
// License text available at https://opensource.org/licenses/MIT
'use strict';
const framework = {
var framework = {
initialize: function(passport) {
return function(req, res, next) {
req._passport = passport;
@ -15,7 +13,7 @@ const framework = {
},
};
const Passport = function() {
var Passport = function() {
this._framework = framework;
};

View File

@ -1,10 +1,8 @@
// Copyright IBM Corp. 2014,2019. All Rights Reserved.
// Copyright IBM Corp. 2014. All Rights Reserved.
// Node module: loopback-boot
// This file is licensed under the MIT License.
// License text available at https://opensource.org/licenses/MIT
'use strict';
process.bootFlags.push('barLoaded');
module.exports = function(app, callback) {
process.bootFlags.push('barStarted');

View File

@ -1,10 +1,8 @@
// Copyright IBM Corp. 2014,2019. All Rights Reserved.
// Copyright IBM Corp. 2014. All Rights Reserved.
// Node module: loopback-boot
// This file is licensed under the MIT License.
// License text available at https://opensource.org/licenses/MIT
'use strict';
process.bootFlags.push('barSyncLoaded');
module.exports = function(app) {
process.bootFlags.push('barSyncExecuted');

View File

@ -1,10 +1,8 @@
// Copyright IBM Corp. 2015,2019. All Rights Reserved.
// Copyright IBM Corp. 2015. All Rights Reserved.
// Node module: loopback-boot
// This file is licensed under the MIT License.
// License text available at https://opensource.org/licenses/MIT
'use strict';
module.exports = function(app, cb) {
if (app.booting)
process.bootingFlagSet = true;

View File

@ -1,8 +1,6 @@
// Copyright IBM Corp. 2014,2019. All Rights Reserved.
// Copyright IBM Corp. 2014. All Rights Reserved.
// Node module: loopback-boot
// This file is licensed under the MIT License.
// License text available at https://opensource.org/licenses/MIT
'use strict';
process.bootFlags.push('fooLoaded');

View File

@ -1,11 +1,9 @@
// Copyright IBM Corp. 2017,2019. All Rights Reserved.
// Copyright IBM Corp. 2017. All Rights Reserved.
// Node module: loopback-boot
// This file is licensed under the MIT License.
// License text available at https://opensource.org/licenses/MIT
'use strict';
const Promise = require('bluebird');
var Promise = require('bluebird');
module.exports = function(app, callback) {
callback();

View File

@ -1,11 +1,9 @@
// Copyright IBM Corp. 2017,2019. All Rights Reserved.
// Copyright IBM Corp. 2017. All Rights Reserved.
// Node module: loopback-boot
// This file is licensed under the MIT License.
// License text available at https://opensource.org/licenses/MIT
'use strict';
const Promise = require('bluebird');
var Promise = require('bluebird');
process.bootFlags.push('promiseLoaded');
module.exports = function(app) {

View File

@ -1,11 +1,9 @@
// Copyright IBM Corp. 2017,2019. All Rights Reserved.
// Copyright IBM Corp. 2017. All Rights Reserved.
// Node module: loopback-boot
// This file is licensed under the MIT License.
// License text available at https://opensource.org/licenses/MIT
'use strict';
const Promise = require('bluebird');
var Promise = require('bluebird');
module.exports = function(app) {
if (process.rejectPromise) {

View File

@ -3,8 +3,6 @@
// This file is licensed under the MIT License.
// License text available at https://opensource.org/licenses/MIT
'use strict';
process.bootFlags.push('thenableLoaded');
module.exports = function(app) {
process.bootFlags.push('thenableStarted');

View File

@ -3,8 +3,6 @@
// This file is licensed under the MIT License.
// License text available at https://opensource.org/licenses/MIT
'use strict';
module.exports = function(app) {
if (process.throwError) {
throw new Error('throw');

View File

@ -1,13 +1,11 @@
// Copyright IBM Corp. 2018,2019. All Rights Reserved.
// Copyright IBM Corp. 2014,2017. All Rights Reserved.
// Node module: loopback-boot
// This file is licensed under the MIT License.
// License text available at https://opensource.org/licenses/MIT
'use strict';
module.exports = {
default: function() {
default: function(app) {
process.bootFlags.push('umdLoaded');
},
};
Object.defineProperty(module.exports, '__esModule', {value: true});
Object.defineProperty(module.exports, '__esModule', { value: true });

View File

@ -2,6 +2,9 @@
"initial": {
"../../helpers/push-name-middleware": {
"params": "custom-middleware"
}
},
"../../helpers/set-umd-middleware": {
"params": "success"
}
}
}

View File

@ -1,10 +1,8 @@
// Copyright IBM Corp. 2014,2019. All Rights Reserved.
// Copyright IBM Corp. 2014. All Rights Reserved.
// Node module: loopback-boot
// This file is licensed under the MIT License.
// License text available at https://opensource.org/licenses/MIT
'use strict';
exports.myMiddleware = function(name) {
return function(req, res, next) {
req._names = req._names || [];

View File

@ -1,10 +1,8 @@
// Copyright IBM Corp. 2014,2019. All Rights Reserved.
// Copyright IBM Corp. 2014. All Rights Reserved.
// Node module: loopback-boot
// This file is licensed under the MIT License.
// License text available at https://opensource.org/licenses/MIT
'use strict';
/**
* Exporting a middleware as a property of the main module
*/

View File

@ -1,30 +0,0 @@
// Copyright IBM Corp. 2016,2019. All Rights Reserved.
// Node module: loopback-boot
// This file is licensed under the MIT License.
// License text available at https://opensource.org/licenses/MIT
'use strict';
module.exports = function(opitions) {
return new Tracker(opitions);
};
function Tracker(options) {
this.name = 'tracker';
this.options = options || {};
}
Tracker.prototype.load = function(context) {
context.configurations.tracker = 'load';
};
Tracker.prototype.compile = function(context, done) {
context.instructions.tracker = 'compile';
process.nextTick(done);
};
Tracker.prototype.start = function(context, done) {
context.executions = context.executions || {};
context.executions.tracker = 'start';
process.nextTick(done);
};

View File

@ -1,10 +1,8 @@
// Copyright IBM Corp. 2015,2019. All Rights Reserved.
// Copyright IBM Corp. 2015,2016. All Rights Reserved.
// Node module: loopback-boot
// This file is licensed under the MIT License.
// License text available at https://opensource.org/licenses/MIT
'use strict';
module.exports = function(loopbackApp, params) {
loopbackApp.use('/component', function(req, res, next) {
res.send(params);

View File

@ -1,10 +1,8 @@
// Copyright IBM Corp. 2015,2019. All Rights Reserved.
// Copyright IBM Corp. 2015. All Rights Reserved.
// Node module: loopback-boot
// This file is licensed under the MIT License.
// License text available at https://opensource.org/licenses/MIT
'use strict';
module.exports = function(params) {
return function(req, res, next) {
res.send(params);

View File

@ -1,18 +1,16 @@
// Copyright IBM Corp. 2014,2019. All Rights Reserved.
// Copyright IBM Corp. 2014,2016. All Rights Reserved.
// Node module: loopback-boot
// This file is licensed under the MIT License.
// License text available at https://opensource.org/licenses/MIT
'use strict';
var path = require('path');
var fs = require('fs-extra');
var extend = require('util')._extend;
var sandbox = require('./sandbox');
const path = require('path');
const fs = require('fs-extra');
const extend = require('util')._extend;
const sandbox = require('./sandbox');
var appdir = exports;
const appdir = exports;
let PATH = appdir.PATH = null;
var PATH = appdir.PATH = null;
appdir.init = function(cb) {
// Node's module loader has a very aggressive caching, therefore
@ -20,7 +18,7 @@ appdir.init = function(cb) {
// The code here is used to generate a random string
require('crypto').randomBytes(5, function(err, buf) {
if (err) return cb(err);
const randomStr = buf.toString('hex');
var randomStr = buf.toString('hex');
PATH = appdir.PATH = sandbox.resolve(randomStr);
cb(null, appdir.PATH);
});
@ -29,7 +27,7 @@ appdir.init = function(cb) {
appdir.createConfigFilesSync = function(appConfig, dataSources, models) {
appConfig = extend({
}, appConfig);
appdir.writeConfigFileSync('config.json', appConfig);
appdir.writeConfigFileSync ('config.json', appConfig);
dataSources = extend({
db: {
@ -37,11 +35,11 @@ appdir.createConfigFilesSync = function(appConfig, dataSources, models) {
defaultForType: 'db',
},
}, dataSources);
appdir.writeConfigFileSync('datasources.json', dataSources);
appdir.writeConfigFileSync ('datasources.json', dataSources);
models = extend({
}, models);
appdir.writeConfigFileSync('model-config.json', models);
appdir.writeConfigFileSync ('model-config.json', models);
};
appdir.writeConfigFileSync = function(name, json) {
@ -49,7 +47,7 @@ appdir.writeConfigFileSync = function(name, json) {
};
appdir.writeFileSync = function(name, content) {
const filePath = this.resolve(name);
var filePath = this.resolve(name);
fs.mkdirsSync(path.dirname(filePath));
fs.writeFileSync(filePath, content, 'utf-8');
return filePath;

View File

@ -1,17 +1,14 @@
// Copyright IBM Corp. 2015,2019. All Rights Reserved.
// Copyright IBM Corp. 2015,2016. All Rights Reserved.
// Node module: loopback-boot
// This file is licensed under the MIT License.
// License text available at https://opensource.org/licenses/MIT
'use strict';
const vm = require('vm');
var vm = require('vm');
function createContext() {
const context = {
var context = {
// required by browserify
XMLHttpRequest: function() {},
clearTimeout: function() {},
XMLHttpRequest: function() { throw new Error('not implemented'); },
FormData: function() { throw new Error('not implemented'); },
localStorage: {
@ -23,17 +20,17 @@ function createContext() {
setTimeout: setTimeout,
// used by `debug` module
document: {documentElement: {style: {}}},
document: { documentElement: { style: {}}},
// used by `debug` module
navigator: {userAgent: 'sandbox'},
navigator: { userAgent: 'sandbox' },
// used by crypto-browserify & friends
Int32Array: Int32Array,
DataView: DataView,
crypto: {
getRandomValues: function(typedArray) {
const randomBuffer = require('crypto').randomBytes(typedArray.length);
var randomBuffer = require('crypto').randomBytes(typedArray.length);
// This implementation is not secure: we take random 8bit values
// and assign them to 8/16/32bit values, leaving high-order bits
// filled with zeroes.
@ -61,21 +58,25 @@ function createContext() {
error: [],
},
},
ArrayBuffer: ArrayBuffer,
};
// `window` is used by loopback to detect browser runtime
context.window = context;
// In Node.js 0.10, the Uint8Array is provided by Node.js glue,
// it's not available in the V8 runtime itself
if (/^v0\.10/.test(process.version)) {
context.Uint8Array = Uint8Array;
}
return vm.createContext(context);
}
exports.createContext = createContext;
function printContextLogs(context) {
let k, ix; // see https://github.com/eslint/eslint/issues/5744
var k, ix; // see https://github.com/eslint/eslint/issues/5744
for (k in context.console._logs) {
const items = context.console._logs[k];
var items = context.console._logs[k];
for (ix in items) {
console[k].apply(console, items[ix]);
}

View File

@ -1,16 +1,14 @@
// Copyright IBM Corp. 2015,2019. All Rights Reserved.
// Copyright IBM Corp. 2015. All Rights Reserved.
// Node module: loopback-boot
// This file is licensed under the MIT License.
// License text available at https://opensource.org/licenses/MIT
'use strict';
const fs = require('fs');
const sandbox = require('./sandbox');
var fs = require('fs');
var sandbox = require('./sandbox');
function exportToSandbox(b, fileName, callback) {
const bundlePath = sandbox.resolve(fileName);
const out = fs.createWriteStream(bundlePath);
var bundlePath = sandbox.resolve(fileName);
var out = fs.createWriteStream(bundlePath);
b.bundle().pipe(out);
out.on('error', function(err) {
@ -21,16 +19,3 @@ function exportToSandbox(b, fileName, callback) {
});
}
exports.exportToSandbox = exportToSandbox;
exports.packageFilter = function packageFilter(pkg, dir) {
// async@3 (used e.g. by loopback-connector) is specifying custom
// browserify config, in particular it wants to apply transformation
// `babelify`. We don't have `babelify` installed because we are
// testing using latest Chrome and thus don't need any transpilation.
// Let's remove the browserify config from the package and force
// browserify to use our config instead.
if (pkg.name === 'async') {
delete pkg.browserify;
}
return pkg;
};

View File

@ -1,10 +1,8 @@
// Copyright IBM Corp. 2014,2019. All Rights Reserved.
// Copyright IBM Corp. 2014. All Rights Reserved.
// Node module: loopback-boot
// This file is licensed under the MIT License.
// License text available at https://opensource.org/licenses/MIT
'use strict';
module.exports = function(name) {
return function(req, res, next) {
req._names = req._names || [];

View File

@ -1,14 +1,12 @@
// Copyright IBM Corp. 2014,2019. All Rights Reserved.
// Copyright IBM Corp. 2014. All Rights Reserved.
// Node module: loopback-boot
// This file is licensed under the MIT License.
// License text available at https://opensource.org/licenses/MIT
'use strict';
var fs = require('fs-extra');
var path = require('path');
const fs = require('fs-extra');
const path = require('path');
const sandbox = exports;
var sandbox = exports;
sandbox.PATH = path.join(__dirname, '..', 'sandbox');
sandbox.reset = function() {
@ -17,7 +15,7 @@ sandbox.reset = function() {
};
sandbox.resolve = function() {
const args = Array.prototype.slice.apply(arguments);
var args = Array.prototype.slice.apply(arguments);
args.unshift(sandbox.PATH);
return path.resolve.apply(path.resolve, args);
};

View File

@ -0,0 +1,14 @@
// Copyright IBM Corp. 2017. All Rights Reserved.
// Node module: loopback-boot
// This file is licensed under the MIT License.
// License text available at https://opensource.org/licenses/MIT
module.exports = {
default: function(value) {
return function(req, res, next) {
res.setHeader('umd', value);
next();
};
},
};
Object.defineProperty(module.exports, '__esModule', { value: true });

View File

@ -1,14 +1,12 @@
// Copyright IBM Corp. 2016,2019. All Rights Reserved.
// Copyright IBM Corp. 2014,2016. All Rights Reserved.
// Node module: loopback-boot
// This file is licensed under the MIT License.
// License text available at https://opensource.org/licenses/MIT
'use strict';
const utils = require('../lib/utils');
const expect = require('chai').expect;
const sandbox = require('./helpers/sandbox');
const appdir = require('./helpers/appdir');
var boot = require('../');
var expect = require('chai').expect;
var sandbox = require('./helpers/sandbox');
var appdir = require('./helpers/appdir');
describe('utils', function() {
beforeEach(sandbox.reset);
@ -17,16 +15,16 @@ describe('utils', function() {
});
describe('fileExistsSync', function() {
it('returns false when a file does not exist', function() {
const doesNotExist = sandbox.resolve('does-not-exist.json');
expect(utils.fileExistsSync(doesNotExist))
var doesNotExist = sandbox.resolve('does-not-exist.json');
expect(boot.utils.fileExistsSync(doesNotExist))
.to.equal(false);
});
it('returns true when a file does exist', function() {
const doesExist = appdir.writeConfigFileSync('does-exist.json', {
var doesExist = appdir.writeConfigFileSync('does-exist.json', {
exists: true,
});
expect(utils.fileExistsSync(doesExist))
expect(boot.utils.fileExistsSync(doesExist))
.to.equal(true);
});
});