Compare commits
99 Commits
Author | SHA1 | Date |
---|---|---|
|
c8241b2b35 | |
|
afe57b5b04 | |
|
56dcfaab6c | |
|
51ec554632 | |
|
f4313949c3 | |
|
98883a877f | |
|
7a6021dc77 | |
|
f9d6d147bc | |
|
298aec304a | |
|
0043b9e27d | |
|
3cbe0028a4 | |
|
e9b108f9c6 | |
|
0bbe8335a4 | |
|
c11ba18f73 | |
|
1fec4ea375 | |
|
974c63ccd5 | |
|
f6b2f24b29 | |
|
b4d1f96494 | |
|
6d8d9c92ce | |
|
14050c199f | |
|
59e46b3d91 | |
|
8bb4368ca8 | |
|
a1d2954626 | |
|
837b8185f2 | |
|
ef39abf6eb | |
|
8587dd6a19 | |
|
561cbd5bca | |
|
02da15cd3e | |
|
17ba74ed1c | |
|
e73b90ed97 | |
|
c10e8225bb | |
|
825411e6b4 | |
|
9b77d8159a | |
|
fd3a381289 | |
|
3e7031c82e | |
|
c278fe6705 | |
|
0ace54cd9e | |
|
bfa874de63 | |
|
b7a14e30e1 | |
|
2675771ec1 | |
|
14eeb4487d | |
|
e89e353ab0 | |
|
d598c3738a | |
|
c4ab39c4e0 | |
|
ded5bccb60 | |
|
473e685a69 | |
|
cdba5f0748 | |
|
47ba28898a | |
|
a035aaa3ae | |
|
c1cda7aefc | |
|
d7efabaebd | |
|
a9ae78ea54 | |
|
3277738937 | |
|
123f3da523 | |
|
e64adb92de | |
|
6e7809d3ef | |
|
a78a05f9b4 | |
|
42a7152474 | |
|
038bfeae43 | |
|
6e36f02005 | |
|
a054fb0064 | |
|
10ec2c5ed6 | |
|
3f42e66dea | |
|
2bf14c8cb4 | |
|
6e6ecb3ecc | |
|
0d216ac360 | |
|
96d59ce610 | |
|
756e3aedda | |
|
3ec9d3ab1c | |
|
92d6a1f91c | |
|
63a11502a6 | |
|
3bb519d5e9 | |
|
480380224d | |
|
d68ffc6f6f | |
|
aa321cf072 | |
|
f39fe30e47 | |
|
79d9ddb835 | |
|
ba688e0026 | |
|
f7c9cbc2c6 | |
|
ac1571ccf1 | |
|
314dff9f5f | |
|
ecc2d43957 | |
|
fbea19a002 | |
|
e96b08087d | |
|
a17c6c50e3 | |
|
6491cc8e71 | |
|
94aef17122 | |
|
295db6d873 | |
|
63cc0ecf7b | |
|
0d985bae0b | |
|
5da1420027 | |
|
57e5e64b4d | |
|
49ed10caaf | |
|
748a728a4f | |
|
58ef16993b | |
|
a6076ae8be | |
|
422fa6c11d | |
|
28d58ede8e | |
|
889a9fe275 |
|
@ -0,0 +1,50 @@
|
|||
---
|
||||
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_
|
|
@ -0,0 +1,25 @@
|
|||
---
|
||||
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.
|
|
@ -0,0 +1,27 @@
|
|||
---
|
||||
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/
|
||||
|
||||
-->
|
|
@ -0,0 +1,11 @@
|
|||
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.
|
|
@ -0,0 +1,18 @@
|
|||
<!--
|
||||
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)
|
|
@ -0,0 +1,24 @@
|
|||
# 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.
|
||||
|
|
@ -16,5 +16,4 @@ generated-instructions*.json
|
|||
checkstyle.xml
|
||||
loopback-boot-*.tgz
|
||||
/test/sandbox/
|
||||
intl/*
|
||||
!intl/en/
|
||||
intl/zz/
|
||||
|
|
|
@ -1,8 +1,6 @@
|
|||
sudo: false
|
||||
language: node_js
|
||||
node_js:
|
||||
- "0.10"
|
||||
- "0.12"
|
||||
- "4"
|
||||
- "6"
|
||||
- "8"
|
||||
- "10"
|
||||
|
||||
|
|
|
@ -0,0 +1,30 @@
|
|||
# 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.
|
108
CHANGES.md
108
CHANGES.md
|
@ -1,3 +1,111 @@
|
|||
2019-06-24, Version 3.3.1
|
||||
=========================
|
||||
|
||||
* chore: update LTS status (Diana Lau)
|
||||
|
||||
* chore: update copyrights years (Agnes Lin)
|
||||
|
||||
|
||||
2019-03-28, Version 3.3.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)
|
||||
|
||||
* 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)
|
||||
|
||||
|
||||
2017-06-22, Version 3.0.1
|
||||
=========================
|
||||
|
||||
* 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-05-22, Version 3.0.0
|
||||
=========================
|
||||
|
||||
* Upgrade deps and fix style issues (Raymond Feng)
|
||||
|
||||
* Provide scriptExtensions option (Supasate Choochaisri)
|
||||
|
||||
* Update paid support URL (Siddhi Pai)
|
||||
|
||||
* Refactor for modular and pluggable design (Raymond Feng)
|
||||
|
||||
* Add Node v7 to Travis CI platforms (Miroslav Bajtoš)
|
||||
|
||||
* Drop support for Node v0.10 and v0.12 (Miroslav Bajtoš)
|
||||
|
||||
* readme: update URL to new doc site (David Cheung)
|
||||
|
||||
* Update ja translation file (Candy)
|
||||
|
||||
* Update header-browser.md (Sequoia McDowell)
|
||||
|
||||
* Update translation files - round#2 (Candy)
|
||||
|
||||
* 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
|
||||
==========================
|
||||
|
||||
|
|
|
@ -0,0 +1,11 @@
|
|||
# Lines starting with '#' are comments.
|
||||
# Each line is a file pattern followed by one or more owners,
|
||||
# the last matching pattern has the most precendence.
|
||||
|
||||
# Current maintainers
|
||||
|
||||
* @raymondfeng @zbarbuto
|
||||
|
||||
# Alumni
|
||||
#
|
||||
# @lehni
|
2
LICENSE
2
LICENSE
|
@ -1,4 +1,4 @@
|
|||
Copyright (c) IBM Corp. 2014,2016. All Rights Reserved.
|
||||
Copyright (c) IBM Corp. 2014,2017. All Rights Reserved.
|
||||
Node module: loopback-boot
|
||||
This project is licensed under the MIT License, full text below.
|
||||
|
||||
|
|
31
README.md
31
README.md
|
@ -1,10 +1,20 @@
|
|||
# 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](http://docs.strongloop.com/display/LB/Defining+boot+scripts) and [Creating a LoopBack application](http://docs.strongloop.com/display/LB/Creating+an+application).
|
||||
|
||||
## Overview
|
||||
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).
|
||||
|
||||
The loopback-boot module initializes (bootstraps) a LoopBack application. Specifically, it:
|
||||
- Configures data-sources.
|
||||
|
@ -13,7 +23,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](http://docs.strongloop.com/display/LB/Defining+boot+scripts).
|
||||
For more information, see [Defining boot scripts](https://loopback.io/doc/en/lb2/Defining-boot-scripts).
|
||||
|
||||
### Version notes
|
||||
|
||||
|
@ -46,6 +56,19 @@ 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.
|
||||
|
|
27
browser.js
27
browser.js
|
@ -1,9 +1,11 @@
|
|||
// Copyright IBM Corp. 2014,2016. All Rights Reserved.
|
||||
// Copyright IBM Corp. 2014,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
|
||||
|
||||
var execute = require('./lib/executor');
|
||||
'use strict';
|
||||
|
||||
const Bootstrapper = require('./lib/bootstrapper');
|
||||
|
||||
/**
|
||||
* The browser version of `bootLoopBackApp`.
|
||||
|
@ -21,17 +23,24 @@ var execute = require('./lib/executor');
|
|||
* @header boot(app)
|
||||
*/
|
||||
|
||||
exports = module.exports = function bootBrowserApp(app, options) {
|
||||
exports = module.exports = function bootBrowserApp(app, options, callback) {
|
||||
// Only using options.id to identify the browserified bundle to load for
|
||||
// this application. If no Id was provided, load the default bundle.
|
||||
var moduleName = 'loopback-boot#instructions';
|
||||
if (options && typeof options === 'object' && options.appId)
|
||||
moduleName += '-' + options.appId;
|
||||
let moduleName = 'loopback-boot#instructions';
|
||||
const appId = options && typeof options === 'object' && options.appId;
|
||||
if (appId)
|
||||
moduleName += '-' + appId;
|
||||
|
||||
// The name of the module containing instructions
|
||||
// is hard-coded in lib/bundler
|
||||
var instructions = require(moduleName);
|
||||
execute(app, instructions);
|
||||
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);
|
||||
};
|
||||
|
||||
exports.execute = execute;
|
||||
|
|
|
@ -9,3 +9,7 @@ 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.
|
||||
|
|
71
index.js
71
index.js
|
@ -1,17 +1,16 @@
|
|||
// Copyright IBM Corp. 2014,2016. All Rights Reserved.
|
||||
// Copyright IBM Corp. 2014,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
|
||||
|
||||
// Strong globalize
|
||||
var SG = require('strong-globalize');
|
||||
SG.SetRootDir(__dirname);
|
||||
'use strict';
|
||||
|
||||
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');
|
||||
// Strong globalize
|
||||
const g = require('./lib/globalize');
|
||||
|
||||
const PluginBase = require('./lib/plugin-base');
|
||||
const Bootstrapper = require('./lib/bootstrapper');
|
||||
const addInstructionsToBrowserify = require('./lib/bundler');
|
||||
|
||||
/**
|
||||
* Initialize an application from an options object or
|
||||
|
@ -147,11 +146,33 @@ var utils = require('./lib/utils');
|
|||
*/
|
||||
|
||||
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');
|
||||
|
||||
var instructions = compile(options);
|
||||
execute(app, instructions, callback);
|
||||
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);
|
||||
};
|
||||
|
||||
/**
|
||||
|
@ -164,14 +185,26 @@ exports = module.exports = function bootLoopBackApp(app, options, callback) {
|
|||
*
|
||||
* @header boot.compileToBrowserify(options, bundler)
|
||||
*/
|
||||
exports.compileToBrowserify = function(options, bundler) {
|
||||
addInstructionsToBrowserify(compile(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();
|
||||
});
|
||||
};
|
||||
|
||||
/* -- 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);
|
||||
};
|
||||
|
|
|
@ -0,0 +1,17 @@
|
|||
{
|
||||
"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}"
|
||||
}
|
||||
|
|
@ -0,0 +1,17 @@
|
|||
{
|
||||
"1e5fea50eef843cbffd1d438494912c8": "Pfad \"{0}\" kann nicht aufgelöst werden",
|
||||
"34319676975b1abf107da7a056abb434": "Ungültiges Normalisierungsformat - \"{0}\"",
|
||||
"3a7049e42006e8bc19e0f4fc8df63b6b": "Die `app` wird von einer nicht kompatiblen Loopback-Version {0} betrieben. Unterstützte Versionen: {1}",
|
||||
"3f93b626dd9a1c33d67490f6e71018b5": "WARNUNG: Hauptkonfigurationsdatei \"{0}{{.json}}\" fehlt",
|
||||
"4d052d84c8620730afd4a30832f11724": "Unbekanntes Modell {0} kann nicht konfiguriert werden",
|
||||
"4ed668e9187650d898acf97707df445a": "Die {{phase}} \"{0}\" ist in der Hauptkonfiguration nicht definiert.",
|
||||
"6447e6b342a2c51ab0bc53b3cbdf3742": "Sortierungskonflikt: \"{0}\" kann nicht nach \"{1}\" hinzugefügt werden, da die entgegengesetzte Reihenfolge bereits angegeben wurde",
|
||||
"70654dc6eb565613a33344efed3de998": "Laden von Boot-Script fehlgeschlagen: {0}\n{1}",
|
||||
"7f7bdcadb75abfef1bd8a126d547dd6d": "{0} wird nicht in einen gültigen Wert aufgelöst; zurückgegeben als {1}. \"{2}\" muss in der Umgebungsvariable oder über {{app.get()}} auflösbar sein.",
|
||||
"91a742b7c3568cf6b6755741a70b3c52": "Die {{middleware}} \"{0}\" in {{phase}} \"{1}\" ist in der Hauptkonfiguration nicht definiert.",
|
||||
"a3aa22086ae4976cd013065c9a3ff81c": "{0} kann nicht angewendet werden: ",
|
||||
"be2cf2868ba54624fe38e9908dde5e9e": "Die Daten in {{model-config.json}} haben das nicht unterstützte {{1.x}}-Format.",
|
||||
"ec551b6f2fafd8d40af801ebe5bb09f6": "{{middleware}}-Anweisungen werden verworfen, {{loopback}}-Client unterstützt {{middleware}} nicht.",
|
||||
"fdc23df1bd0fe55fe3faabcc89ff60f3": "Middleware \"{0}\" nicht gefunden: {1}"
|
||||
}
|
||||
|
|
@ -1,25 +1,16 @@
|
|||
{
|
||||
"ec551b6f2fafd8d40af801ebe5bb09f6": "Discarding {{middleware}} instructions, {{loopback}} client does not support {{middleware}}.",
|
||||
"1e5fea50eef843cbffd1d438494912c8": "Cannot resolve path \"{0}\"",
|
||||
"34319676975b1abf107da7a056abb434": "Invalid normalization format - \"{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",
|
||||
"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}",
|
||||
"4ed668e9187650d898acf97707df445a": "The {{phase}} \"{0}\" is not defined in the main config.",
|
||||
"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().",
|
||||
"6447e6b342a2c51ab0bc53b3cbdf3742": "Ordering conflict: cannot add \"{0}\" after \"{1}\", because the opposite order was already specified",
|
||||
"70654dc6eb565613a33344efed3de998": "Failed loading boot script: {0}\n{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}}"
|
||||
"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}"
|
||||
}
|
||||
|
|
|
@ -0,0 +1,17 @@
|
|||
{
|
||||
"1e5fea50eef843cbffd1d438494912c8": "No se puede resolver la vía de acceso \"{0}\"",
|
||||
"34319676975b1abf107da7a056abb434": "Formato de normalización no válido - \"{0}\"",
|
||||
"3a7049e42006e8bc19e0f4fc8df63b6b": "La `app` está basada en una versión de loopback incompatible {0}. Versiones soportadas: {1}",
|
||||
"3f93b626dd9a1c33d67490f6e71018b5": "AVISO: falta el archivo de configuración principal \"{0}{{.json}}\"",
|
||||
"4d052d84c8620730afd4a30832f11724": "No se puede configurar el modelo desconocido {0}",
|
||||
"4ed668e9187650d898acf97707df445a": "La {{phase}} \"{0}\" no está definida en la configuración principal.",
|
||||
"6447e6b342a2c51ab0bc53b3cbdf3742": "Conflicto de orden: no se puede añadir \"{0}\" después de \"{1}\", porque ya se ha especificado el orden inverso.",
|
||||
"70654dc6eb565613a33344efed3de998": "No se ha podido cargar el script de arranque: {0}\n{1}",
|
||||
"7f7bdcadb75abfef1bd8a126d547dd6d": "{0} no se resuelve como un valor válido, se ha devuelto como {1}. \"{2}\" debe poder resolverse en la variable de entorno o por medio de {{app.get()}}.",
|
||||
"91a742b7c3568cf6b6755741a70b3c52": "El {{middleware}} \"{0}\" en la {{phase}} \"{1}\" no está definido en la configuración principal.",
|
||||
"a3aa22086ae4976cd013065c9a3ff81c": "No se puede aplicar {0}: ",
|
||||
"be2cf2868ba54624fe38e9908dde5e9e": "Los datos de {{model-config.json}} están en un formato {{1.x}} no soportado.",
|
||||
"ec551b6f2fafd8d40af801ebe5bb09f6": "Descartando instrucciones de {{middleware}}, el cliente de {{loopback}} no da soporte a {{middleware}}.",
|
||||
"fdc23df1bd0fe55fe3faabcc89ff60f3": "Middleware \"{0}\" no encontrado: {1}"
|
||||
}
|
||||
|
|
@ -0,0 +1,17 @@
|
|||
{
|
||||
"1e5fea50eef843cbffd1d438494912c8": "Impossible de résoudre le chemin \"{0}\"",
|
||||
"34319676975b1abf107da7a056abb434": "Format de normalisation non valide - \"{0}\"",
|
||||
"3a7049e42006e8bc19e0f4fc8df63b6b": "L'application `app` est basée sur une version loopback {0} incompatible. Versions prises en charge : {1}",
|
||||
"3f93b626dd9a1c33d67490f6e71018b5": "AVERTISSEMENT : le fichier de configuration principal \"{0}{{.json}}\" est manquant",
|
||||
"4d052d84c8620730afd4a30832f11724": "Impossible de configurer le modèle inconnu {0}",
|
||||
"4ed668e9187650d898acf97707df445a": "{{phase}} \"{0}\" n'est pas défini dans la configuration principale.",
|
||||
"6447e6b342a2c51ab0bc53b3cbdf3742": "Conflit concernant l'ordre : impossible d'ajouter \"{0}\" après \"{1}\" car l'ordre opposé à déjà été spécifié",
|
||||
"70654dc6eb565613a33344efed3de998": "Echec du chargement du script d'amorçage : {0}\n{1}",
|
||||
"7f7bdcadb75abfef1bd8a126d547dd6d": "{0} n'est pas résolu en une valeur valide, renvoyé sous forme de {1}. \"{2}\" doit pouvoir être résolu dans la variable d'environnement ou par {{app.get()}}.",
|
||||
"91a742b7c3568cf6b6755741a70b3c52": "Le {{middleware}} \"{0}\" dans {{phase}} \"{1}\" n'est pas défini dans la configuration principale.",
|
||||
"a3aa22086ae4976cd013065c9a3ff81c": "Impossible d'appliquer {0} : ",
|
||||
"be2cf2868ba54624fe38e9908dde5e9e": "Les données contenues dans {{model-config.json}} sont au format {{1.x}} qui n'est pas pris en charge.",
|
||||
"ec551b6f2fafd8d40af801ebe5bb09f6": "Les instructions {{middleware}} sont ignorées ; le client {{loopback}} ne prend pas en charge {{middleware}}.",
|
||||
"fdc23df1bd0fe55fe3faabcc89ff60f3": "Middleware \"{0}\" introuvable : {1}"
|
||||
}
|
||||
|
|
@ -0,0 +1,17 @@
|
|||
{
|
||||
"1e5fea50eef843cbffd1d438494912c8": "Impossibile risolvere il percorso \"{0}\"",
|
||||
"34319676975b1abf107da7a056abb434": "Formato di normalizzazione non valido - \"{0}\"",
|
||||
"3a7049e42006e8bc19e0f4fc8df63b6b": "La `app` si basa su una versione loopback non compatibile {0}. Versioni supportate: {1}",
|
||||
"3f93b626dd9a1c33d67490f6e71018b5": "AVVERTENZA: file di configurazione principale \"{0}{{.json}}\" mancante",
|
||||
"4d052d84c8620730afd4a30832f11724": "Impossibile configurare il modello {0} sconosciuto",
|
||||
"4ed668e9187650d898acf97707df445a": "{{phase}} \"{0}\" non definita nella configurazione principale.",
|
||||
"6447e6b342a2c51ab0bc53b3cbdf3742": "Conflitto di ordinamento: impossibile aggiungere \"{0}\" dopo \"{1}\", perché è già stato specificato l'ordine opposto",
|
||||
"70654dc6eb565613a33344efed3de998": "Caricamento dello script di boot non riuscito: {0}\n{1}",
|
||||
"7f7bdcadb75abfef1bd8a126d547dd6d": "{0} non viene risolto in un valore valido, restituito come {1}. \"{2}\" deve essere risolto in una variabile di ambiente o da {{app.get()}}.",
|
||||
"91a742b7c3568cf6b6755741a70b3c52": "{{middleware}} \"{0}\" in {{phase}} \"{1}\" non definito nella configurazione principale.",
|
||||
"a3aa22086ae4976cd013065c9a3ff81c": "Impossibile applicare {0}: ",
|
||||
"be2cf2868ba54624fe38e9908dde5e9e": "I dati in {{model-config.json}} sono nel formato {{1.x}} non supportato.",
|
||||
"ec551b6f2fafd8d40af801ebe5bb09f6": "Eliminazione delle istruzioni {{middleware}}, il client {{loopback}} non supporta {{middleware}}.",
|
||||
"fdc23df1bd0fe55fe3faabcc89ff60f3": "Middleware \"{0}\" non trovato: {1}"
|
||||
}
|
||||
|
|
@ -0,0 +1,17 @@
|
|||
{
|
||||
"1e5fea50eef843cbffd1d438494912c8": "パス \"{0}\" を解決できません",
|
||||
"34319676975b1abf107da7a056abb434": "無効な正規化形式 - \"{0}\"",
|
||||
"3a7049e42006e8bc19e0f4fc8df63b6b": "「アプリケーション」は、互換性のない 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": "{{phase}} \"{1}\" の {{middleware}} \"{0}\" がメイン構成内に定義されていません。",
|
||||
"a3aa22086ae4976cd013065c9a3ff81c": "{0} を適用できません: ",
|
||||
"be2cf2868ba54624fe38e9908dde5e9e": "{{model-config.json}} のデータが、サポートされていない {{1.x}} 形式になっています。",
|
||||
"ec551b6f2fafd8d40af801ebe5bb09f6": "{{middleware}} 命令を破棄します。{{loopback}} クライアントでは {{middleware}} はサポートされません。",
|
||||
"fdc23df1bd0fe55fe3faabcc89ff60f3": "ミドルウェア \"{0}\" が見つかりません: {1}"
|
||||
}
|
||||
|
|
@ -0,0 +1,17 @@
|
|||
{
|
||||
"1e5fea50eef843cbffd1d438494912c8": "\"{0}\" 경로를 해석할 수 없음",
|
||||
"34319676975b1abf107da7a056abb434": "올바르지 않은 정규화 형식 - \"{0}\"",
|
||||
"3a7049e42006e8bc19e0f4fc8df63b6b": "`앱`이 호환되지 않는 루프백 버전 {0}을(를) 기반으로 합니다. 지원되는 버전: {1}",
|
||||
"3f93b626dd9a1c33d67490f6e71018b5": "경고: 기본 구성 파일 \"{0}{{.json}}\"이(가) 누락됨",
|
||||
"4d052d84c8620730afd4a30832f11724": "알 수 없는 모델 {0}을(를) 구성할 수 없음",
|
||||
"4ed668e9187650d898acf97707df445a": "{{phase}} \"{0}\"이(가) 기본 구성에 정의되어 있지 않습니다.",
|
||||
"6447e6b342a2c51ab0bc53b3cbdf3742": "순서 지정 충돌: 반대 순서로 이미 지정되어서 \"{1}\" 뒤에 \"{0}\"을(를) 추가할 수 없음",
|
||||
"70654dc6eb565613a33344efed3de998": "부트 스크립트를 로드하는 데 실패함: {0}\n{1}",
|
||||
"7f7bdcadb75abfef1bd8a126d547dd6d": "{0}이(가) 올바른 값으로 해석되지 않아서 {1}(으)로 리턴되었습니다. \"{2}\"은(는) 환경 변수에서 또는 {{app.get()}}에 의해 해석 가능해야 합니다. ",
|
||||
"91a742b7c3568cf6b6755741a70b3c52": "{{phase}} \"{1}\"의 {{middleware}} \"{0}\"이(가) 기본 구성에 정의되어 있지 않습니다. ",
|
||||
"a3aa22086ae4976cd013065c9a3ff81c": "{0}을(를) 적용할 수 없음: ",
|
||||
"be2cf2868ba54624fe38e9908dde5e9e": "{{model-config.json}}의 데이터가 지원되지 않는 {{1.x}} 형식입니다. ",
|
||||
"ec551b6f2fafd8d40af801ebe5bb09f6": "{{middleware}} 지시사항을 버리십시오. {{loopback}} 클라이언트가 {{middleware}}을(를) 지원하지 않습니다.",
|
||||
"fdc23df1bd0fe55fe3faabcc89ff60f3": "미들웨어 \"{0}\"을(를) 찾을 수 없음: {1}"
|
||||
}
|
||||
|
|
@ -0,0 +1,17 @@
|
|||
{
|
||||
"1e5fea50eef843cbffd1d438494912c8": "Pad \"{0}\" kan niet worden omgezet.",
|
||||
"34319676975b1abf107da7a056abb434": "Ongeldige normalisatie-indeling - \"{0}\"",
|
||||
"3a7049e42006e8bc19e0f4fc8df63b6b": "De 'app' wordt aangestuurd door een incompatibele versie van loopback, {0}. Ondersteunde versies: {1}",
|
||||
"3f93b626dd9a1c33d67490f6e71018b5": "WAARSCHUWING: Hoofdconfiguratiebestand \"{0}{{.json}}\" ontbreekt.",
|
||||
"4d052d84c8620730afd4a30832f11724": "Configuratie van onbekend model {0} kan niet ongedaan worden gemaakt",
|
||||
"4ed668e9187650d898acf97707df445a": "De {{phase}} \"{0}\" is niet gedefinieerd in de hoofdconfiguratie.",
|
||||
"6447e6b342a2c51ab0bc53b3cbdf3742": "Volgordeconflict: \"{0}\" kan niet worden toegevoegd na \"{1}\", omdat de omgekeerde volgorde al is opgegeven.",
|
||||
"70654dc6eb565613a33344efed3de998": "Laden van opstartscript is mislukt: {0}\n{1}",
|
||||
"7f7bdcadb75abfef1bd8a126d547dd6d": "{0} wordt niet omgezet in een geldige waarde; wordt geretourneerd als {1}. \"{2}\" moet omgezet kunnen worden in een omgevingsvariabele of door {{app.get()}}.",
|
||||
"91a742b7c3568cf6b6755741a70b3c52": "De {{middleware}} \"{0}\" in {{phase}} \"{1}\" is niet gedefinieerd in de hoofdconfiguratie.",
|
||||
"a3aa22086ae4976cd013065c9a3ff81c": "{0} kan niet worden toegepast: ",
|
||||
"be2cf2868ba54624fe38e9908dde5e9e": "De gegevens in {{model-config.json}} hebben de niet ondersteunde indeling {{1.x}}.",
|
||||
"ec551b6f2fafd8d40af801ebe5bb09f6": "{{middleware}} instructies worden verwijderd, {{loopback}}-client ondersteunt geen {{middleware}}.",
|
||||
"fdc23df1bd0fe55fe3faabcc89ff60f3": "Middleware \"{0}\" is niet gevonden: {1}"
|
||||
}
|
||||
|
|
@ -0,0 +1,17 @@
|
|||
{
|
||||
"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}"
|
||||
}
|
||||
|
|
@ -0,0 +1,17 @@
|
|||
{
|
||||
"1e5fea50eef843cbffd1d438494912c8": "Não é possível resolver caminho \"{0}\"",
|
||||
"34319676975b1abf107da7a056abb434": "Formato de normalização inválido - \"{0}\"",
|
||||
"3a7049e42006e8bc19e0f4fc8df63b6b": "O `app` é desenvolvido com uma versão de loopback incompatível {0}. Versões suportadas: {1}",
|
||||
"3f93b626dd9a1c33d67490f6e71018b5": "AVISO: o arquivo de configuração principal \"{0}{{.json}}\" está ausente",
|
||||
"4d052d84c8620730afd4a30832f11724": "Não é possível configurar modelo desconhecido {0}",
|
||||
"4ed668e9187650d898acf97707df445a": "A {{phase}} \"{0}\" não foi definida na configuração principal.",
|
||||
"6447e6b342a2c51ab0bc53b3cbdf3742": "Conflito de ordem: não é possível incluir \"{0}\" após \"{1}\", porque a ordem oposta já foi especificada",
|
||||
"70654dc6eb565613a33344efed3de998": "Falha ao carregar script de inicialização: {0}\n{1}",
|
||||
"7f7bdcadb75abfef1bd8a126d547dd6d": "{0} não resolve para um valor válido, retornado como {1}. \"{2}\" deve ser resolvível na variável de ambiente ou pelo {{app.get()}}.",
|
||||
"91a742b7c3568cf6b6755741a70b3c52": "O {{middleware}} \"{0}\" em {{phase}} \"{1}\" não é definido na configuração principal.",
|
||||
"a3aa22086ae4976cd013065c9a3ff81c": "Não é possível aplicar {0}: ",
|
||||
"be2cf2868ba54624fe38e9908dde5e9e": "Os dados em {{model-config.json}} estão no formato não suportado {{1.x}}.",
|
||||
"ec551b6f2fafd8d40af801ebe5bb09f6": "Descartando instruções de {{middleware}}, cliente de {{loopback}} não suporta {{middleware}}.",
|
||||
"fdc23df1bd0fe55fe3faabcc89ff60f3": "Middleware \"{0}\" não localizado: {1}"
|
||||
}
|
||||
|
|
@ -0,0 +1,17 @@
|
|||
{
|
||||
"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}"
|
||||
}
|
||||
|
|
@ -0,0 +1,17 @@
|
|||
{
|
||||
"1e5fea50eef843cbffd1d438494912c8": "\"{0}\" yolu çözülemiyor",
|
||||
"34319676975b1abf107da7a056abb434": "Geçersiz normalleştirme biçimi - \"{0}\"",
|
||||
"3a7049e42006e8bc19e0f4fc8df63b6b": "`app` uyumsuz olan geri döngü {0} sürümüyle güçlendirilmiş. Desteklenen sürümler: {1}",
|
||||
"3f93b626dd9a1c33d67490f6e71018b5": "UYARI: Ana yapılandırma dosyası \"{0}{{.json}}\" eksik",
|
||||
"4d052d84c8620730afd4a30832f11724": "Bilinmeyen {0} modeli yapılandırılamıyor",
|
||||
"4ed668e9187650d898acf97707df445a": "{{phase}} \"{0}\", ana yapılandırmada tanımlı değil",
|
||||
"6447e6b342a2c51ab0bc53b3cbdf3742": "Sıralama çakışması: \"{0}\", \"{1}\" sonrasına eklenemez; karşıt sıra belirtilmiş",
|
||||
"70654dc6eb565613a33344efed3de998": "Önyükleme komut dosyasının yüklenmesi başarısız oldu: {0}\n{1}",
|
||||
"7f7bdcadb75abfef1bd8a126d547dd6d": "{0} geçerli bir değere çözülmüyor, {1} olarak döndürüldü. \"{2}\" ortam değişkeninde ya da {{app.get()}} ile çözülebilir olmalıdır.",
|
||||
"91a742b7c3568cf6b6755741a70b3c52": "{{phase}} \"{1}\" aşamasındaki {{middleware}} \"{0}\" ana yapılandırmada tanımlı değil.",
|
||||
"a3aa22086ae4976cd013065c9a3ff81c": "{0} uygulanamıyor: ",
|
||||
"be2cf2868ba54624fe38e9908dde5e9e": "{{model-config.json}} içindeki verileri desteklenmeyen {{1.x}} biçiminde.",
|
||||
"ec551b6f2fafd8d40af801ebe5bb09f6": "{{middleware}} yönergeleri atılıyor, {{middleware}}, {{loopback}} istemcisi tarafından desteklenmiyor.",
|
||||
"fdc23df1bd0fe55fe3faabcc89ff60f3": "Ara katman \"{0}\" bulunamadı: {1}"
|
||||
}
|
||||
|
|
@ -0,0 +1,17 @@
|
|||
{
|
||||
"1e5fea50eef843cbffd1d438494912c8": "无法解析路径“{0}”",
|
||||
"34319676975b1abf107da7a056abb434": "标准化格式无效 -“{0}”",
|
||||
"3a7049e42006e8bc19e0f4fc8df63b6b": "应用程序由不兼容的回环版本 {0} 支持。受支持的版本:{1}",
|
||||
"3f93b626dd9a1c33d67490f6e71018b5": "警告:缺失主要配置文件“{0}{{.json}}”",
|
||||
"4d052d84c8620730afd4a30832f11724": "无法配置未知的模型 {0}",
|
||||
"4ed668e9187650d898acf97707df445a": "未在主配置中定义 {{phase}}“{0}”。",
|
||||
"6447e6b342a2c51ab0bc53b3cbdf3742": "顺序冲突:不能在“{1}”之后添加“{0}”,因为已指定相反顺序",
|
||||
"70654dc6eb565613a33344efed3de998": "无法装入引导脚本:{0}\n{1}",
|
||||
"7f7bdcadb75abfef1bd8a126d547dd6d": "{0} 未解析为有效值,返回为 {1}。“{2}”必须可在环境变量中解析或由 {{app.get()}} 解析。",
|
||||
"91a742b7c3568cf6b6755741a70b3c52": "未在主配置中定义 {{phase}}“{1}”中的 {{middleware}}“{0}”。",
|
||||
"a3aa22086ae4976cd013065c9a3ff81c": "无法应用 {0}:",
|
||||
"be2cf2868ba54624fe38e9908dde5e9e": "{{model-config.json}} 中的数据不是受支持的 {{1.x}} 格式。",
|
||||
"ec551b6f2fafd8d40af801ebe5bb09f6": "正在丢弃 {{middleware}} 指示信息,{{loopback}} 客户机不支持 {{middleware}}。",
|
||||
"fdc23df1bd0fe55fe3faabcc89ff60f3": "找不到中间件“{0}”:{1}"
|
||||
}
|
||||
|
|
@ -0,0 +1,17 @@
|
|||
{
|
||||
"1e5fea50eef843cbffd1d438494912c8": "無法解析路徑 \"{0}\"",
|
||||
"34319676975b1abf107da7a056abb434": "無效的正規化格式 - \"{0}\"",
|
||||
"3a7049e42006e8bc19e0f4fc8df63b6b": "'app' 採用不相容的 LoopBack 版本 {0}。支援的版本:{1}",
|
||||
"3f93b626dd9a1c33d67490f6e71018b5": "警告:遺漏主要配置檔 \"{0}{{.json}}\"",
|
||||
"4d052d84c8620730afd4a30832f11724": "無法配置不明模型 {0}",
|
||||
"4ed668e9187650d898acf97707df445a": "主要配置中未定義 {{phase}} \"{0}\"。",
|
||||
"6447e6b342a2c51ab0bc53b3cbdf3742": "排序衝突:不能將 \"{0}\" 新增至 \"{1}\" 後面,因為已指定相反順序",
|
||||
"70654dc6eb565613a33344efed3de998": "載入啟動 Script 時失敗:{0}\n{1}",
|
||||
"7f7bdcadb75abfef1bd8a126d547dd6d": "{0} 未解析成有效的值,傳回 {1}。\"{2}\" 必須可在環境變數中解析或由 {{app.get()}} 解析。",
|
||||
"91a742b7c3568cf6b6755741a70b3c52": "主要配置中未定義 {{phase}} \"{1}\" 中的 {{middleware}} \"{0}\"。",
|
||||
"a3aa22086ae4976cd013065c9a3ff81c": "無法套用 {0}:",
|
||||
"be2cf2868ba54624fe38e9908dde5e9e": "{{model-config.json}} 中的資料採用不受支援的 {{1.x}} 格式。",
|
||||
"ec551b6f2fafd8d40af801ebe5bb09f6": "正在捨棄 {{middleware}} 指令,{{loopback}} 用戶端不支援 {{middleware}}。",
|
||||
"fdc23df1bd0fe55fe3faabcc89ff60f3": "找不到中介軟體 \"{0}\":{1}"
|
||||
}
|
||||
|
|
@ -0,0 +1,222 @@
|
|||
// 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;
|
||||
};
|
102
lib/bundler.js
102
lib/bundler.js
|
@ -1,52 +1,68 @@
|
|||
// Copyright IBM Corp. 2014,2016. All Rights Reserved.
|
||||
// Copyright IBM Corp. 2014,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
|
||||
|
||||
var fs = require('fs');
|
||||
var path = require('path');
|
||||
var commondir = require('commondir');
|
||||
var cloneDeep = require('lodash').cloneDeep;
|
||||
var g = require('strong-globalize')();
|
||||
'use strict';
|
||||
|
||||
const fs = require('fs');
|
||||
const path = require('path');
|
||||
const commondir = require('commondir');
|
||||
const cloneDeep = require('lodash').cloneDeep;
|
||||
const g = require('./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(instructions, bundler) {
|
||||
bundleModelScripts(instructions, bundler);
|
||||
bundleMixinScripts(instructions, bundler);
|
||||
bundleComponentScripts(instructions, bundler);
|
||||
bundleOtherScripts(instructions, bundler);
|
||||
bundleInstructions(instructions, bundler);
|
||||
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);
|
||||
};
|
||||
|
||||
function bundleOtherScripts(instructions, bundler) {
|
||||
for (var key in instructions.files) {
|
||||
addScriptsToBundle(key, instructions.files[key], 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 bundleModelScripts(instructions, bundler) {
|
||||
bundleSourceFiles(instructions, 'models', bundler);
|
||||
function bundleOtherScripts(context, bundler) {
|
||||
const list = context.instructions.bootScripts;
|
||||
addScriptsToBundle('boot', list, bundler);
|
||||
}
|
||||
|
||||
function bundleMixinScripts(instructions, bundler) {
|
||||
bundleSourceFiles(instructions, 'mixins', bundler);
|
||||
function bundlePluginScripts(context, bundler) {
|
||||
const list = context.instructions.pluginScripts;
|
||||
addScriptsToBundle('plugins', list, bundler);
|
||||
}
|
||||
|
||||
function bundleComponentScripts(instructions, bundler) {
|
||||
bundleSourceFiles(instructions, 'components', bundler);
|
||||
function bundleModelScripts(context, bundler) {
|
||||
bundleSourceFiles(context, 'models', bundler);
|
||||
}
|
||||
|
||||
function bundleSourceFiles(instructions, type, bundler) {
|
||||
var files = instructions[type]
|
||||
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]
|
||||
.map(function(m) { return m.sourceFile; })
|
||||
.filter(function(f) { return !!f; });
|
||||
|
||||
var instructionToFileMapping = instructions[type]
|
||||
const instructionToFileMapping = context.instructions[type]
|
||||
.map(function(m) { return files.indexOf(m.sourceFile); });
|
||||
|
||||
addScriptsToBundle(type, files, bundler);
|
||||
|
@ -54,45 +70,47 @@ function bundleSourceFiles(instructions, type, bundler) {
|
|||
// Update `sourceFile` properties with the new paths
|
||||
instructionToFileMapping.forEach(function(fileIx, sourceIx) {
|
||||
if (fileIx === -1) return;
|
||||
instructions[type][sourceIx].sourceFile = files[fileIx];
|
||||
context.instructions[type][sourceIx].sourceFile = files[fileIx];
|
||||
});
|
||||
}
|
||||
|
||||
function addScriptsToBundle(name, list, bundler) {
|
||||
if (!list.length) return;
|
||||
|
||||
var root = commondir(list.map(path.dirname));
|
||||
const root = commondir(list.map(path.dirname));
|
||||
|
||||
for (var ix in list) {
|
||||
var filepath = list[ix];
|
||||
for (const ix in list) {
|
||||
const 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.
|
||||
var fileid = 'loopback-boot#' + name + '#' + path.relative(root, filepath);
|
||||
const 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 instructions entry with the new id that will be
|
||||
// Rewrite the context entry with the new id that will be
|
||||
// used to load the file via `require(fileid)`.
|
||||
list[ix] = fileid;
|
||||
}
|
||||
}
|
||||
|
||||
function bundleInstructions(instructions, bundler) {
|
||||
instructions = cloneDeep(instructions);
|
||||
function bundleInstructions(context, bundler) {
|
||||
const instructions = cloneDeep(context.instructions);
|
||||
|
||||
var hasMiddleware = instructions.middleware.phases.length ||
|
||||
const 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;
|
||||
|
||||
var instructionsString = JSON.stringify(instructions, null, 2);
|
||||
const 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
|
||||
|
@ -102,7 +120,7 @@ function bundleInstructions(instructions, bundler) {
|
|||
b.require(instructionsStream, { expose: 'loopback-boot#instructions' });
|
||||
*/
|
||||
|
||||
var instructionId = 'instructions';
|
||||
let instructionId = 'instructions';
|
||||
// Create an unique instruction identifier using the application ID.
|
||||
// This is only useful when multiple loopback applications are being bundled
|
||||
// together.
|
||||
|
@ -111,10 +129,10 @@ function bundleInstructions(instructions, 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
|
||||
var instructionsFile = path.resolve(__dirname,
|
||||
const instructionsFile = path.resolve(__dirname,
|
||||
'..', 'generated-' + instructionId + '.json');
|
||||
fs.writeFileSync(instructionsFile, instructionsString, 'utf-8');
|
||||
|
||||
var moduleName = 'loopback-boot#' + instructionId;
|
||||
bundler.require(instructionsFile, { expose: moduleName });
|
||||
const moduleName = 'loopback-boot#' + instructionId;
|
||||
bundler.require(instructionsFile, {expose: moduleName});
|
||||
}
|
||||
|
|
823
lib/compiler.js
823
lib/compiler.js
|
@ -1,823 +0,0 @@
|
|||
// 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 FILE_EXTENSION_JSON = '.json';
|
||||
|
||||
/**
|
||||
* 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 };
|
||||
}
|
||||
|
||||
var appRootDir = options.appRootDir = options.appRootDir || process.cwd();
|
||||
var env = options.env || process.env.NODE_ENV || 'development';
|
||||
|
||||
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));
|
||||
var envdir = dir + '/' + env;
|
||||
bootScripts = bootScripts.concat(findScripts(envdir));
|
||||
});
|
||||
|
||||
// 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);
|
||||
|
||||
var mixinDirs = options.mixinDirs || [];
|
||||
var mixinSources = options.mixinSources || modelsMeta.mixins || ['./mixins'];
|
||||
var mixinInstructions = buildAllMixinInstructions(
|
||||
appRootDir, mixinDirs, mixinSources, options, 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, extensions) {
|
||||
assert(dir, g.f('cannot require directory contents without directory name'));
|
||||
|
||||
var files = tryReadDir(dir);
|
||||
extensions = extensions || _.keys(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 require.extensions (.txt .md etc.)
|
||||
if (stats.isFile()) {
|
||||
if (isPreferredExtension(filename))
|
||||
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) {
|
||||
var registry = verifyModelDefinitions(rootDir, modelDefinitions) ||
|
||||
findModelDefinitions(rootDir, sources);
|
||||
|
||||
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) {
|
||||
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)),
|
||||
true);
|
||||
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) {
|
||||
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);
|
||||
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;
|
||||
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 (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) {
|
||||
var definition = require(jsonFile);
|
||||
var basename = path.basename(jsonFile, path.extname(jsonFile));
|
||||
definition.name = definition.name || _.capitalize(_.camelCase(basename));
|
||||
|
||||
// find a matching file with a supported extension like `.js` or `.coffee`
|
||||
var sourceFile = fixFileExtension(jsonFile, allFiles, true);
|
||||
|
||||
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 = require(pathName);
|
||||
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 cloneDeep(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',
|
||||
'.node': 'node',
|
||||
};
|
||||
}
|
||||
|
||||
function isPreferredExtension(filename) {
|
||||
var includeExtensions = require.extensions;
|
||||
|
||||
var ext = path.extname(filename);
|
||||
return (ext in includeExtensions) && !(ext in getExcludedExtensions());
|
||||
}
|
||||
|
||||
function fixFileExtension(filepath, files, onlyScriptsExportingFunction) {
|
||||
var results = [];
|
||||
var otherFile;
|
||||
|
||||
/* Prefer coffee scripts over json */
|
||||
if (isPreferredExtension(filepath)) 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 (!onlyScriptsExportingFunction)
|
||||
results.push(otherFile);
|
||||
else if (onlyScriptsExportingFunction &&
|
||||
(typeof require.extensions[otherFileExtension]) === 'function') {
|
||||
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, false);
|
||||
return (fixedFile === undefined ? resolvedPath : fixedFile);
|
||||
}
|
||||
|
||||
function buildAllMixinInstructions(appRootDir, mixinDirs, mixinSources, options,
|
||||
modelInstructions) {
|
||||
var extensions = _.without(_.keys(require.extensions),
|
||||
_.keys(getExcludedExtensions()));
|
||||
|
||||
// load mixins from `options.mixins`
|
||||
var sourceFiles = options.mixins || [];
|
||||
var instructionsFromMixins = loadMixins(sourceFiles, options);
|
||||
|
||||
// load mixins from `options.mixinDirs`
|
||||
sourceFiles = findMixinDefinitions(appRootDir, mixinDirs, extensions);
|
||||
if (sourceFiles === undefined) return;
|
||||
var instructionsFromMixinDirs = loadMixins(sourceFiles, options);
|
||||
|
||||
/* 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, extensions);
|
||||
if (sourceFiles === undefined) return;
|
||||
var instructionsFromMixinSources = loadMixins(sourceFiles, options);
|
||||
|
||||
// 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, extensions) {
|
||||
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, extensions));
|
||||
});
|
||||
return files;
|
||||
}
|
||||
|
||||
function loadMixins(sourceFiles, options) {
|
||||
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, options);
|
||||
var 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) {
|
||||
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, options) {
|
||||
var normalization = options.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;
|
||||
}
|
||||
}
|
|
@ -1,313 +0,0 @@
|
|||
// 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';
|
||||
}
|
454
lib/executor.js
454
lib/executor.js
|
@ -1,454 +0,0 @@
|
|||
// 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')();
|
||||
|
||||
/**
|
||||
* 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 = 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);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
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 = 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;
|
||||
});
|
||||
}
|
||||
|
||||
// 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 exports = require(filepath);
|
||||
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);
|
||||
if (f.func.length >= 2) {
|
||||
debug('Starting async function %s', f.path);
|
||||
f.func(app, function(err) {
|
||||
debug('Async function finished %s', f.path);
|
||||
done(err);
|
||||
});
|
||||
} else {
|
||||
debug('Starting sync function %s', f.path);
|
||||
f.func(app);
|
||||
debug('Sync function finished %s', f.path);
|
||||
done();
|
||||
}
|
||||
}, 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 = require(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 = require(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;
|
||||
}
|
|
@ -0,0 +1,11 @@
|
|||
// 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();
|
|
@ -0,0 +1,338 @@
|
|||
// 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);
|
||||
};
|
|
@ -0,0 +1,66 @@
|
|||
// 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;
|
||||
});
|
||||
};
|
|
@ -0,0 +1,135 @@
|
|||
// 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();
|
||||
});
|
||||
};
|
|
@ -0,0 +1,105 @@
|
|||
// 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);
|
||||
}
|
|
@ -0,0 +1,52 @@
|
|||
// 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);
|
||||
});
|
||||
};
|
|
@ -0,0 +1,41 @@
|
|||
// 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);
|
||||
});
|
||||
};
|
||||
|
|
@ -0,0 +1,266 @@
|
|||
// 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);
|
||||
});
|
||||
};
|
|
@ -0,0 +1,200 @@
|
|||
// 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);
|
||||
}
|
||||
});
|
||||
};
|
|
@ -0,0 +1,319 @@
|
|||
// 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);
|
||||
});
|
||||
};
|
|
@ -0,0 +1,31 @@
|
|||
// 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;
|
||||
};
|
347
lib/utils.js
347
lib/utils.js
|
@ -1,11 +1,354 @@
|
|||
// Copyright IBM Corp. 2014,2016. All Rights Reserved.
|
||||
// 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
|
||||
|
||||
var fs = require('fs');
|
||||
'use strict';
|
||||
|
||||
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.
|
||||
|
|
47
package.json
47
package.json
|
@ -1,6 +1,6 @@
|
|||
{
|
||||
"name": "loopback-boot",
|
||||
"version": "2.22.0",
|
||||
"version": "3.3.1",
|
||||
"description": "Convention-based bootstrapper for LoopBack applications",
|
||||
"keywords": [
|
||||
"StrongLoop",
|
||||
|
@ -12,33 +12,40 @@
|
|||
"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": "eslint .",
|
||||
"lint:fix": "eslint . --fix"
|
||||
},
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"async": "~0.9.0",
|
||||
"commondir": "0.0.1",
|
||||
"debug": "^2.0.0",
|
||||
"lodash": "^3.6.0",
|
||||
"semver": "^4.1.0",
|
||||
"strong-globalize": "^2.6.2",
|
||||
"toposort": "^0.2.10"
|
||||
"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"
|
||||
},
|
||||
"devDependencies": {
|
||||
"browserify": "^4.1.8",
|
||||
"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"
|
||||
}
|
||||
"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."
|
||||
}
|
||||
|
|
|
@ -0,0 +1,40 @@
|
|||
// 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);
|
||||
});
|
||||
});
|
||||
});
|
|
@ -0,0 +1,122 @@
|
|||
// 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;
|
||||
});
|
||||
});
|
|
@ -1,18 +1,22 @@
|
|||
// Copyright IBM Corp. 2015,2016. All Rights Reserved.
|
||||
// Copyright IBM Corp. 2015,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
|
||||
|
||||
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;
|
||||
'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;
|
||||
|
||||
describe('browser support for multiple apps', function() {
|
||||
this.timeout(60000); // 60s to give browserify enough time to finish
|
||||
|
@ -20,10 +24,10 @@ describe('browser support for multiple apps', function() {
|
|||
beforeEach(sandbox.reset);
|
||||
|
||||
it('has API for bundling and booting multiple apps', function(done) {
|
||||
var app1Dir = path.resolve(__dirname, './fixtures/browser-app');
|
||||
var app2Dir = path.resolve(__dirname, './fixtures/browser-app-2');
|
||||
const app1Dir = path.resolve(__dirname, './fixtures/browser-app');
|
||||
const app2Dir = path.resolve(__dirname, './fixtures/browser-app-2');
|
||||
|
||||
var apps = [
|
||||
const apps = [
|
||||
{
|
||||
appDir: app1Dir,
|
||||
appFile: './app.js',
|
||||
|
@ -40,67 +44,82 @@ describe('browser support for multiple apps', function() {
|
|||
browserifyTestApps(apps, function(err, bundlePath) {
|
||||
if (err) return done(err);
|
||||
|
||||
var bundledApps = executeBundledApps(bundlePath, apps);
|
||||
var app1 = bundledApps.defaultApp;
|
||||
var app2 = bundledApps.browserApp2;
|
||||
const bundledApps = executeBundledApps(bundlePath, apps, function(err) {
|
||||
const app1 = bundledApps.defaultApp;
|
||||
const 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) {
|
||||
var b = browserify({
|
||||
const b = browserify({
|
||||
debug: true,
|
||||
basedir: path.resolve(__dirname, './fixtures'),
|
||||
packageFilter,
|
||||
});
|
||||
|
||||
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;
|
||||
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;
|
||||
|
||||
appFile = path.join(appDir, appFile);
|
||||
b.require(appFile, { expose: moduleName });
|
||||
b.require(appFile, {expose: moduleName});
|
||||
|
||||
var opts = appDir;
|
||||
let opts = appDir;
|
||||
if (appId) {
|
||||
opts = {
|
||||
appId: appId,
|
||||
appRootDir: appDir,
|
||||
};
|
||||
}
|
||||
boot.compileToBrowserify(opts, b);
|
||||
bundles.push(opts);
|
||||
}
|
||||
|
||||
exportBrowserifyToFile(b, 'browser-app-bundle.js', next);
|
||||
async.eachSeries(bundles, function(opts, done) {
|
||||
boot.compileToBrowserify(opts, b, done);
|
||||
}, function(err) {
|
||||
exportBrowserifyToFile(b, 'browser-app-bundle.js', next);
|
||||
});
|
||||
}
|
||||
|
||||
function executeBundledApps(bundlePath, apps) {
|
||||
var code = fs.readFileSync(bundlePath);
|
||||
var context = createBrowserLikeContext();
|
||||
function executeBundledApps(bundlePath, apps, done) {
|
||||
const code = fs.readFileSync(bundlePath);
|
||||
const context = createBrowserLikeContext();
|
||||
vm.runInContext(code, context, bundlePath);
|
||||
|
||||
var script = 'var apps = {};\n';
|
||||
for (var i in apps) {
|
||||
var moduleName = apps[i].moduleName;
|
||||
var id = apps[i].appId || 'defaultApp';
|
||||
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);
|
||||
script += 'apps.' + id + ' = require("' + moduleName + '");\n';
|
||||
}
|
||||
script += 'apps;\n';
|
||||
|
||||
var appsInContext = vm.runInContext(script, context);
|
||||
|
||||
printContextLogs(context);
|
||||
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);
|
||||
});
|
||||
|
||||
return appsInContext;
|
||||
}
|
||||
|
|
|
@ -1,40 +1,43 @@
|
|||
// Copyright IBM Corp. 2014,2016. All Rights Reserved.
|
||||
// Copyright IBM Corp. 2014,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
|
||||
|
||||
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;
|
||||
'use strict';
|
||||
|
||||
var compileStrategies = {
|
||||
'default': function(appDir) {
|
||||
var b = browserify({
|
||||
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({
|
||||
basedir: appDir,
|
||||
debug: true,
|
||||
packageFilter,
|
||||
});
|
||||
|
||||
b.require('./app.js', { expose: 'browser-app' });
|
||||
b.require('./app.js', {expose: 'browser-app'});
|
||||
return b;
|
||||
},
|
||||
|
||||
'coffee': function(appDir) {
|
||||
var b = browserify({
|
||||
coffee: function(appDir) {
|
||||
const 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;
|
||||
},
|
||||
};
|
||||
|
@ -45,92 +48,96 @@ describe('browser support', function() {
|
|||
beforeEach(sandbox.reset);
|
||||
|
||||
it('has API for bundling and executing boot instructions', function(done) {
|
||||
var appDir = path.resolve(__dirname, './fixtures/browser-app');
|
||||
const appDir = path.resolve(__dirname, './fixtures/browser-app');
|
||||
|
||||
browserifyTestApp(appDir, function(err, bundlePath) {
|
||||
if (err) return done(err);
|
||||
|
||||
var app = executeBundledApp(bundlePath);
|
||||
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',
|
||||
);
|
||||
|
||||
// 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');
|
||||
|
||||
// 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/component-config.json
|
||||
// and fixtures/browser-app/components/dummy-component.js
|
||||
expect(app.dummyComponentOptions).to.eql({option: 'value'});
|
||||
done();
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
it('loads mixins', function(done) {
|
||||
var appDir = path.resolve(__dirname, './fixtures/browser-app');
|
||||
var options = {
|
||||
const appDir = path.resolve(__dirname, './fixtures/browser-app');
|
||||
const options = {
|
||||
appRootDir: appDir,
|
||||
};
|
||||
|
||||
browserifyTestApp(options, function(err, bundlePath) {
|
||||
if (err) return done(err);
|
||||
|
||||
var app = executeBundledApp(bundlePath);
|
||||
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 modelBuilder = app.registry.modelBuilder;
|
||||
var registry = modelBuilder.mixins.mixins;
|
||||
expect(Object.keys(registry)).to.eql(['TimeStamps']);
|
||||
expect(app.models.Customer.timeStampsMixin).to.eql(true);
|
||||
|
||||
done();
|
||||
done();
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
it('supports coffee-script files', function(done) {
|
||||
// add coffee-script to require.extensions
|
||||
require('coffee-script/register');
|
||||
require('coffeescript/register');
|
||||
|
||||
var appDir = path.resolve(__dirname, './fixtures/coffee-app');
|
||||
const appDir = path.resolve(__dirname, './fixtures/coffee-app');
|
||||
|
||||
browserifyTestApp(appDir, 'coffee', function(err, bundlePath) {
|
||||
if (err) return done(err);
|
||||
|
||||
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();
|
||||
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();
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
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';
|
||||
|
||||
var appDir = typeof(options) === 'object' ? options.appRootDir : options;
|
||||
var b = compileStrategies[strategy](appDir);
|
||||
const appDir = typeof options === 'object' ? options.appRootDir : options;
|
||||
const b = compileStrategies[strategy](appDir);
|
||||
|
||||
boot.compileToBrowserify(options, b);
|
||||
|
||||
exportBrowserifyToFile(b, 'browser-app-bundle.js', next);
|
||||
boot.compileToBrowserify(options, b, function(err) {
|
||||
exportBrowserifyToFile(b, 'browser-app-bundle.js', next);
|
||||
});
|
||||
}
|
||||
|
||||
function executeBundledApp(bundlePath) {
|
||||
var code = fs.readFileSync(bundlePath);
|
||||
var context = createBrowserLikeContext();
|
||||
function executeBundledApp(bundlePath, done) {
|
||||
const code = fs.readFileSync(bundlePath);
|
||||
const context = createBrowserLikeContext();
|
||||
vm.runInContext(code, context, bundlePath);
|
||||
var app = vm.runInContext('require("browser-app")', context);
|
||||
|
||||
printContextLogs(context);
|
||||
|
||||
const app = vm.runInContext('require("browser-app")', context);
|
||||
app.once('booted', function(err) {
|
||||
printContextLogs(context);
|
||||
done(err, app);
|
||||
});
|
||||
return app;
|
||||
}
|
||||
|
|
File diff suppressed because it is too large
Load Diff
|
@ -1,37 +0,0 @@
|
|||
// 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
|
@ -1,13 +1,14 @@
|
|||
// Copyright IBM Corp. 2015,2016. All Rights Reserved.
|
||||
// Copyright IBM Corp. 2015,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
|
||||
|
||||
var loopback = require('loopback');
|
||||
var boot = require('../../../');
|
||||
'use strict';
|
||||
|
||||
var app = module.exports = loopback();
|
||||
const loopback = require('loopback');
|
||||
const boot = require('../../../');
|
||||
|
||||
const app = module.exports = loopback();
|
||||
boot(app, {
|
||||
appId: 'browserApp2',
|
||||
appRootDir: __dirname,
|
||||
|
|
|
@ -1,8 +1,10 @@
|
|||
// Copyright IBM Corp. 2015. All Rights Reserved.
|
||||
// Copyright IBM Corp. 2015,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(Robot) {
|
||||
Robot.settings._customized = 'Robot';
|
||||
Robot.base.settings._customized = 'Robot';
|
||||
|
|
|
@ -1,10 +1,12 @@
|
|||
// Copyright IBM Corp. 2014,2016. All Rights Reserved.
|
||||
// Copyright IBM Corp. 2014,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
|
||||
|
||||
var loopback = require('loopback');
|
||||
var boot = require('../../../');
|
||||
'use strict';
|
||||
|
||||
var app = module.exports = loopback();
|
||||
boot(app);
|
||||
const loopback = require('loopback');
|
||||
const boot = require('../../../');
|
||||
|
||||
const app = module.exports = loopback();
|
||||
boot(app, __dirname);
|
||||
|
|
|
@ -1,8 +1,10 @@
|
|||
// Copyright IBM Corp. 2014. All Rights Reserved.
|
||||
// Copyright IBM Corp. 2014,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(app) {
|
||||
app.set('custom-key', 'custom-value');
|
||||
};
|
||||
|
|
|
@ -1,8 +1,10 @@
|
|||
// Copyright IBM Corp. 2015. All Rights Reserved.
|
||||
// Copyright IBM Corp. 2015,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(app, options) {
|
||||
app.dummyComponentOptions = options;
|
||||
};
|
||||
|
|
|
@ -1,8 +1,10 @@
|
|||
// Copyright IBM Corp. 2015,2016. All Rights Reserved.
|
||||
// Copyright IBM Corp. 2015,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(Model, options) {
|
||||
Model.timeStampsMixin = true;
|
||||
};
|
||||
|
|
|
@ -1,8 +1,10 @@
|
|||
// Copyright IBM Corp. 2014. All Rights Reserved.
|
||||
// Copyright IBM Corp. 2014,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(Customer) {
|
||||
Customer.settings._customized = 'Customer';
|
||||
Customer.base.settings._customized = 'Base';
|
||||
|
|
|
@ -2,4 +2,4 @@ loopback = require 'loopback'
|
|||
boot = require '../../../'
|
||||
|
||||
module.exports = client = loopback()
|
||||
boot(client)
|
||||
boot(client, __dirname)
|
||||
|
|
|
@ -0,0 +1,2 @@
|
|||
{
|
||||
}
|
|
@ -1,8 +1,10 @@
|
|||
// Copyright IBM Corp. 2015. All Rights Reserved.
|
||||
// Copyright IBM Corp. 2015,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';
|
||||
|
||||
process.bootFlags.push('barLoadedInTest');
|
||||
module.exports = function(app, callback) {
|
||||
callback();
|
||||
|
|
|
@ -1,9 +1,11 @@
|
|||
// Copyright IBM Corp. 2015,2016. All Rights Reserved.
|
||||
// Copyright IBM Corp. 2015,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
|
||||
|
||||
var framework = {
|
||||
'use strict';
|
||||
|
||||
const framework = {
|
||||
initialize: function(passport) {
|
||||
return function(req, res, next) {
|
||||
req._passport = passport;
|
||||
|
@ -13,7 +15,7 @@ var framework = {
|
|||
},
|
||||
};
|
||||
|
||||
var Passport = function() {
|
||||
const Passport = function() {
|
||||
this._framework = framework;
|
||||
};
|
||||
|
||||
|
|
|
@ -1,8 +1,10 @@
|
|||
// Copyright IBM Corp. 2014. All Rights Reserved.
|
||||
// Copyright IBM Corp. 2014,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';
|
||||
|
||||
process.bootFlags.push('barLoaded');
|
||||
module.exports = function(app, callback) {
|
||||
process.bootFlags.push('barStarted');
|
||||
|
|
|
@ -1,8 +1,10 @@
|
|||
// Copyright IBM Corp. 2014. All Rights Reserved.
|
||||
// Copyright IBM Corp. 2014,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';
|
||||
|
||||
process.bootFlags.push('barSyncLoaded');
|
||||
module.exports = function(app) {
|
||||
process.bootFlags.push('barSyncExecuted');
|
||||
|
|
|
@ -1,8 +1,10 @@
|
|||
// Copyright IBM Corp. 2015. All Rights Reserved.
|
||||
// Copyright IBM Corp. 2015,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(app, cb) {
|
||||
if (app.booting)
|
||||
process.bootingFlagSet = true;
|
||||
|
|
|
@ -0,0 +1,6 @@
|
|||
'use strict';
|
||||
|
||||
module.exports = function(app, callback) {
|
||||
process.bootFlags.push('customjs');
|
||||
callback();
|
||||
};
|
|
@ -0,0 +1,6 @@
|
|||
'use strict';
|
||||
|
||||
module.exports = function(app, callback) {
|
||||
process.bootFlags.push('customjs2');
|
||||
callback();
|
||||
};
|
|
@ -1,6 +1,8 @@
|
|||
// Copyright IBM Corp. 2014. All Rights Reserved.
|
||||
// Copyright IBM Corp. 2014,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';
|
||||
|
||||
process.bootFlags.push('fooLoaded');
|
||||
|
|
|
@ -0,0 +1,15 @@
|
|||
// Copyright IBM Corp. 2017,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 Promise = require('bluebird');
|
||||
|
||||
module.exports = function(app, callback) {
|
||||
callback();
|
||||
if (process.promiseAndCallback) {
|
||||
return Promise.reject();
|
||||
}
|
||||
};
|
|
@ -0,0 +1,21 @@
|
|||
// Copyright IBM Corp. 2017,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 Promise = require('bluebird');
|
||||
|
||||
process.bootFlags.push('promiseLoaded');
|
||||
module.exports = function(app) {
|
||||
process.bootFlags.push('promiseStarted');
|
||||
return Promise.resolve({
|
||||
then: function(onFulfill, onReject) {
|
||||
process.nextTick(function() {
|
||||
process.bootFlags.push('promiseFinished');
|
||||
onFulfill();
|
||||
});
|
||||
},
|
||||
});
|
||||
};
|
|
@ -0,0 +1,14 @@
|
|||
// Copyright IBM Corp. 2017,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 Promise = require('bluebird');
|
||||
|
||||
module.exports = function(app) {
|
||||
if (process.rejectPromise) {
|
||||
return Promise.reject(new Error('reject'));
|
||||
}
|
||||
};
|
|
@ -0,0 +1,19 @@
|
|||
// 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';
|
||||
|
||||
process.bootFlags.push('thenableLoaded');
|
||||
module.exports = function(app) {
|
||||
process.bootFlags.push('thenableStarted');
|
||||
return {
|
||||
then: function(onFulfill, onReject) {
|
||||
process.nextTick(function() {
|
||||
process.bootFlags.push('thenableFinished');
|
||||
onFulfill();
|
||||
});
|
||||
},
|
||||
};
|
||||
};
|
|
@ -0,0 +1,12 @@
|
|||
// 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';
|
||||
|
||||
module.exports = function(app) {
|
||||
if (process.throwError) {
|
||||
throw new Error('throw');
|
||||
}
|
||||
};
|
|
@ -0,0 +1,13 @@
|
|||
// Copyright IBM Corp. 2018,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 = {
|
||||
default: function() {
|
||||
process.bootFlags.push('umdLoaded');
|
||||
},
|
||||
};
|
||||
Object.defineProperty(module.exports, '__esModule', {value: true});
|
|
@ -1,8 +1,10 @@
|
|||
// Copyright IBM Corp. 2014. All Rights Reserved.
|
||||
// Copyright IBM Corp. 2014,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';
|
||||
|
||||
exports.myMiddleware = function(name) {
|
||||
return function(req, res, next) {
|
||||
req._names = req._names || [];
|
||||
|
|
|
@ -1,8 +1,10 @@
|
|||
// Copyright IBM Corp. 2014. All Rights Reserved.
|
||||
// Copyright IBM Corp. 2014,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';
|
||||
|
||||
/**
|
||||
* Exporting a middleware as a property of the main module
|
||||
*/
|
||||
|
|
|
@ -0,0 +1,30 @@
|
|||
// 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);
|
||||
};
|
|
@ -1,8 +1,10 @@
|
|||
// Copyright IBM Corp. 2015,2016. All Rights Reserved.
|
||||
// Copyright IBM Corp. 2015,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(loopbackApp, params) {
|
||||
loopbackApp.use('/component', function(req, res, next) {
|
||||
res.send(params);
|
||||
|
|
|
@ -1,8 +1,10 @@
|
|||
// Copyright IBM Corp. 2015. All Rights Reserved.
|
||||
// Copyright IBM Corp. 2015,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(params) {
|
||||
return function(req, res, next) {
|
||||
res.send(params);
|
||||
|
|
|
@ -1,16 +1,18 @@
|
|||
// Copyright IBM Corp. 2014,2016. All Rights Reserved.
|
||||
// Copyright IBM Corp. 2014,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
|
||||
|
||||
var path = require('path');
|
||||
var fs = require('fs-extra');
|
||||
var extend = require('util')._extend;
|
||||
var sandbox = require('./sandbox');
|
||||
'use strict';
|
||||
|
||||
var appdir = exports;
|
||||
const path = require('path');
|
||||
const fs = require('fs-extra');
|
||||
const extend = require('util')._extend;
|
||||
const sandbox = require('./sandbox');
|
||||
|
||||
var PATH = appdir.PATH = null;
|
||||
const appdir = exports;
|
||||
|
||||
let PATH = appdir.PATH = null;
|
||||
|
||||
appdir.init = function(cb) {
|
||||
// Node's module loader has a very aggressive caching, therefore
|
||||
|
@ -18,7 +20,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);
|
||||
var randomStr = buf.toString('hex');
|
||||
const randomStr = buf.toString('hex');
|
||||
PATH = appdir.PATH = sandbox.resolve(randomStr);
|
||||
cb(null, appdir.PATH);
|
||||
});
|
||||
|
@ -27,7 +29,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: {
|
||||
|
@ -35,11 +37,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) {
|
||||
|
@ -47,7 +49,7 @@ appdir.writeConfigFileSync = function(name, json) {
|
|||
};
|
||||
|
||||
appdir.writeFileSync = function(name, content) {
|
||||
var filePath = this.resolve(name);
|
||||
const filePath = this.resolve(name);
|
||||
fs.mkdirsSync(path.dirname(filePath));
|
||||
fs.writeFileSync(filePath, content, 'utf-8');
|
||||
return filePath;
|
||||
|
|
|
@ -1,14 +1,17 @@
|
|||
// Copyright IBM Corp. 2015,2016. All Rights Reserved.
|
||||
// Copyright IBM Corp. 2015,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
|
||||
|
||||
var vm = require('vm');
|
||||
'use strict';
|
||||
|
||||
const vm = require('vm');
|
||||
|
||||
function createContext() {
|
||||
var context = {
|
||||
const context = {
|
||||
// required by browserify
|
||||
XMLHttpRequest: function() { throw new Error('not implemented'); },
|
||||
XMLHttpRequest: function() {},
|
||||
clearTimeout: function() {},
|
||||
FormData: function() { throw new Error('not implemented'); },
|
||||
|
||||
localStorage: {
|
||||
|
@ -20,17 +23,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) {
|
||||
var randomBuffer = require('crypto').randomBytes(typedArray.length);
|
||||
const 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.
|
||||
|
@ -58,6 +61,8 @@ function createContext() {
|
|||
error: [],
|
||||
},
|
||||
},
|
||||
|
||||
ArrayBuffer: ArrayBuffer,
|
||||
};
|
||||
|
||||
// `window` is used by loopback to detect browser runtime
|
||||
|
@ -68,9 +73,9 @@ function createContext() {
|
|||
exports.createContext = createContext;
|
||||
|
||||
function printContextLogs(context) {
|
||||
var k, ix; // see https://github.com/eslint/eslint/issues/5744
|
||||
let k, ix; // see https://github.com/eslint/eslint/issues/5744
|
||||
for (k in context.console._logs) {
|
||||
var items = context.console._logs[k];
|
||||
const items = context.console._logs[k];
|
||||
for (ix in items) {
|
||||
console[k].apply(console, items[ix]);
|
||||
}
|
||||
|
|
|
@ -1,14 +1,16 @@
|
|||
// Copyright IBM Corp. 2015. All Rights Reserved.
|
||||
// Copyright IBM Corp. 2015,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
|
||||
|
||||
var fs = require('fs');
|
||||
var sandbox = require('./sandbox');
|
||||
'use strict';
|
||||
|
||||
const fs = require('fs');
|
||||
const sandbox = require('./sandbox');
|
||||
|
||||
function exportToSandbox(b, fileName, callback) {
|
||||
var bundlePath = sandbox.resolve(fileName);
|
||||
var out = fs.createWriteStream(bundlePath);
|
||||
const bundlePath = sandbox.resolve(fileName);
|
||||
const out = fs.createWriteStream(bundlePath);
|
||||
b.bundle().pipe(out);
|
||||
|
||||
out.on('error', function(err) {
|
||||
|
@ -19,3 +21,16 @@ 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;
|
||||
};
|
||||
|
|
|
@ -1,8 +1,10 @@
|
|||
// Copyright IBM Corp. 2014. All Rights Reserved.
|
||||
// Copyright IBM Corp. 2014,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(name) {
|
||||
return function(req, res, next) {
|
||||
req._names = req._names || [];
|
||||
|
|
|
@ -1,12 +1,14 @@
|
|||
// Copyright IBM Corp. 2014. All Rights Reserved.
|
||||
// Copyright IBM Corp. 2014,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
|
||||
|
||||
var fs = require('fs-extra');
|
||||
var path = require('path');
|
||||
'use strict';
|
||||
|
||||
var sandbox = exports;
|
||||
const fs = require('fs-extra');
|
||||
const path = require('path');
|
||||
|
||||
const sandbox = exports;
|
||||
sandbox.PATH = path.join(__dirname, '..', 'sandbox');
|
||||
|
||||
sandbox.reset = function() {
|
||||
|
@ -15,7 +17,7 @@ sandbox.reset = function() {
|
|||
};
|
||||
|
||||
sandbox.resolve = function() {
|
||||
var args = Array.prototype.slice.apply(arguments);
|
||||
const args = Array.prototype.slice.apply(arguments);
|
||||
args.unshift(sandbox.PATH);
|
||||
return path.resolve.apply(path.resolve, args);
|
||||
};
|
||||
|
|
|
@ -1,12 +1,14 @@
|
|||
// Copyright IBM Corp. 2014,2016. All Rights Reserved.
|
||||
// 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
|
||||
|
||||
var boot = require('../');
|
||||
var expect = require('chai').expect;
|
||||
var sandbox = require('./helpers/sandbox');
|
||||
var appdir = require('./helpers/appdir');
|
||||
'use strict';
|
||||
|
||||
const utils = require('../lib/utils');
|
||||
const expect = require('chai').expect;
|
||||
const sandbox = require('./helpers/sandbox');
|
||||
const appdir = require('./helpers/appdir');
|
||||
|
||||
describe('utils', function() {
|
||||
beforeEach(sandbox.reset);
|
||||
|
@ -15,16 +17,16 @@ describe('utils', function() {
|
|||
});
|
||||
describe('fileExistsSync', function() {
|
||||
it('returns false when a file does not exist', function() {
|
||||
var doesNotExist = sandbox.resolve('does-not-exist.json');
|
||||
expect(boot.utils.fileExistsSync(doesNotExist))
|
||||
const doesNotExist = sandbox.resolve('does-not-exist.json');
|
||||
expect(utils.fileExistsSync(doesNotExist))
|
||||
.to.equal(false);
|
||||
});
|
||||
|
||||
it('returns true when a file does exist', function() {
|
||||
var doesExist = appdir.writeConfigFileSync('does-exist.json', {
|
||||
const doesExist = appdir.writeConfigFileSync('does-exist.json', {
|
||||
exists: true,
|
||||
});
|
||||
expect(boot.utils.fileExistsSync(doesExist))
|
||||
expect(utils.fileExistsSync(doesExist))
|
||||
.to.equal(true);
|
||||
});
|
||||
});
|
||||
|
|
Loading…
Reference in New Issue