Compare commits

...

99 Commits

Author SHA1 Message Date
Diana Lau c8241b2b35
Merge pull request #308 from achrinzafork/chore/update-lts
chore: update LTS status to End-of-Life
2021-02-09 10:48:13 -05:00
Rifa Achrinza afe57b5b04 chore: update LTS status to End-of-Life
see https://github.com/strongloop/loopback-next/issues/6957
2021-01-22 11:13:00 +08:00
Miroslav Bajtoš 56dcfaab6c
Merge pull request #305 from strongloop/feat/maintenance-lts
Update LTS status in README
2020-03-05 17:28:05 +01:00
Miroslav Bajtoš 51ec554632
Update LTS status in README 2020-03-05 13:41:27 +01:00
Agnes Lin f4313949c3
Merge pull request #302 from strongloop/copyright
chore: update copyrights year
2020-02-10 08:32:31 -05:00
Diana Lau 98883a877f chore: update copyrights year 2020-02-08 17:01:01 -05:00
Diana Lau 7a6021dc77
Merge pull request #301 from strongloop/fixbot
fix stale bot
2020-02-06 08:58:27 -05:00
Diana Lau f9d6d147bc fix stale bot 2020-02-03 14:18:55 -05:00
Nora 298aec304a
Merge pull request #300 from strongloop/chore/improve-issue-templates
chore: improve issue and PR templates
2019-11-21 10:31:47 -05:00
Nora 0043b9e27d
Merge pull request #299 from strongloop/fix-eslint
chore: fix eslint violations
2019-11-21 10:31:38 -05:00
Nora 3cbe0028a4 chore: improve issue and PR templates 2019-11-17 14:22:06 -05:00
Nora e9b108f9c6 chore: fix eslint violations 2019-11-17 14:19:25 -05:00
Miroslav Bajtoš 0bbe8335a4
Merge pull request #297 from strongloop/fix/ci
test: fix browserify-based tests
2019-10-07 10:38:13 +02:00
Miroslav Bajtoš c11ba18f73
test: fix browserify-based tests
Add `packageFilter` to handle buggy `async` browserify config.

Signed-off-by: Miroslav Bajtoš <mbajtoss@gmail.com>
2019-10-03 17:06:12 +02:00
Miroslav Bajtoš 1fec4ea375
Merge pull request #298 from strongloop/drop-node6
Drop support for Node.js 6.x
2019-10-03 17:03:36 +02:00
Miroslav Bajtoš 974c63ccd5
Drop support for Node.js 6.x
It has reached end-of-life long time.

Signed-off-by: Miroslav Bajtoš <mbajtoss@gmail.com>
2019-10-03 16:57:02 +02:00
Diana Lau f6b2f24b29
Merge pull request #296 from strongloop/stalebot
chore: add stalebot
2019-09-19 10:00:00 -04:00
Diana Lau b4d1f96494 chore: add stalebot 2019-09-19 09:36:49 -04:00
Diana Lau 6d8d9c92ce
Merge pull request #293 from strongloop/fix/publish-config
Tag 3.x versions as `latest` in npm registry
2019-06-25 09:54:04 -04:00
Miroslav Bajtoš 14050c199f
Tag 3.x versions as `latest` in npm registry
Signed-off-by: Miroslav Bajtoš <mbajtoss@gmail.com>
2019-06-25 08:38:07 +02:00
Diana Lau 59e46b3d91 3.3.1
* chore: update LTS status (Diana Lau)
 * chore: update copyrights years (Agnes Lin)
2019-06-24 11:22:44 -04:00
Diana Lau 8bb4368ca8
Merge pull request #292 from strongloop/lts
chore: update LTS status
2019-06-18 15:33:11 -04:00
Diana Lau a1d2954626 chore: update LTS status 2019-06-12 21:54:11 -04:00
Agnes Lin 837b8185f2
Merge pull request #290 from strongloop/copyrights
chore: update copyrights years
2019-05-07 10:29:23 -04:00
Agnes Lin ef39abf6eb chore: update copyrights years 2019-05-07 10:16:03 -04:00
Raymond Feng 8587dd6a19 3.3.0
* chore: upgrade deps to avoid npm audit warnings (Raymond Feng)
2019-03-28 14:28:27 -07:00
Raymond Feng 561cbd5bca
Merge pull request #289 from strongloop/upgrade-deps
chore: upgrade deps to avoid npm audit warnings
2019-03-28 14:27:11 -07:00
Raymond Feng 02da15cd3e chore: upgrade deps to avoid npm audit warnings 2019-03-26 11:14:56 -07:00
Miroslav Bajtoš 17ba74ed1c
3.2.1
* fix: set `app.booting` flag immediately (Miroslav Bajtoš)
 * fix: update lodash (jannyHou)
2019-03-22 16:34:33 +01:00
Miroslav Bajtoš e73b90ed97
Merge pull request #286 from strongloop/fix/booting-flag
fix: set `app.booting` flag immediately
2019-03-22 16:34:04 +01:00
Miroslav Bajtoš c10e8225bb
fix: set `app.booting` flag immediately
Before this change, `app.booting` was set by Application plugin as part
of regular phase invocation, which executes individual plugins in
subsequent turns of the event loop. As a result, `app.booting` was
initially `undefined` despite the fact that the booting process was
already in progress.

This patch moves manipulation of `app.booting` flag directly to
`Bootstrapper#run` method, to ensure it's set early enough and always
properly cleared at the end.
2019-03-22 13:57:25 +01:00
Janny 825411e6b4
Merge pull request #285 from strongloop/update/dependency-lodash
update lodash
2019-03-01 14:08:27 -05:00
jannyHou 9b77d8159a fix: update lodash 2019-03-01 12:32:47 -05:00
Miroslav Bajtoš fd3a381289
3.2.0
* README: update LTS status (Miroslav Bajtoš)
 * Add support for es6 modules for boot scripts (Walker)
2018-10-18 09:01:15 +02:00
Miroslav Bajtoš 3e7031c82e
Merge pull request #284 from strongloop/update-lts
README: update LTS status
2018-10-18 09:00:25 +02:00
Miroslav Bajtoš c278fe6705
README: update LTS status 2018-10-16 13:15:49 +02:00
Miroslav Bajtoš 0ace54cd9e
Merge pull request #280 from klarkc/master
Add support for es6 modules in boot scripts
2018-10-11 16:23:54 +02:00
Walker bfa874de63
Add support for es6 modules for boot scripts 2018-10-11 16:17:29 +02:00
Diana Lau b7a14e30e1 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)
2018-07-26 17:33:46 -04:00
Janny 2675771ec1
Merge pull request #283 from strongloop/update-dev
Update dev
2018-07-26 14:54:35 -04:00
jannyHou 14eeb4487d update: dependency 2018-07-26 14:16:16 -04:00
Diana Lau e89e353ab0
Merge pull request #282 from strongloop/update-dep
chore: update dependencies
2018-07-26 11:09:04 -04:00
Diana Lau d598c3738a chore: update dependencies 2018-07-25 21:59:39 -04:00
Taranveer Virk c4ab39c4e0
Merge pull request #281 from candytangnb/webfm-0629-000536-cs,pl,ru-translation
[WebFM] cs/pl/ru translation Check-in by YI TANG (tangyinb@cn.ibm.com)
2018-07-06 14:51:07 -04:00
candytangnb ded5bccb60 [WebFM] cs/pl/ru translation
cs/pl/ru translation check-in by YI TANG (tangyinb@cn.ibm.com) using
WebFM tool.
2018-06-29 00:05:36 -04:00
Miroslav Bajtoš 473e685a69
Merge pull request #253 from lehni/feature/async-boot-scripts-master
Add support for ES6 style async boot scripts
2017-12-11 10:35:22 +01:00
Diana Lau cdba5f0748
Merge pull request #274 from strongloop/license
chore: update license
2017-11-13 14:52:16 -05:00
Diana Lau 47ba28898a chore: update license 2017-11-10 18:02:24 -05:00
Miroslav Bajtoš a035aaa3ae Merge pull request #271 from strongloop/good-bye-lehni
CODEOWNERS: move @lehni to Alumni section
2017-10-19 10:53:05 +02:00
Miroslav Bajtoš c1cda7aefc
CODEOWNERS: move @lehni to Alumni section 2017-10-19 10:52:32 +02:00
Diana Lau d7efabaebd 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-10-13 13:32:25 -04:00
Diana Lau a9ae78ea54 Merge pull request #268 from strongloop/update-strong-globalize
update strong-globalize to 3.1.0
2017-10-13 12:35:46 -04:00
shimks 3277738937 update strong-globalize to 3.1.0 2017-10-12 15:01:48 -04:00
Miroslav Bajtoš 123f3da523 Merge pull request #266 from strongloop/welcome-zbarbuto
CODEOWNERS: add zbarbuto
2017-09-27 11:12:14 +02:00
Miroslav Bajtoš e64adb92de CODEOWNERS: add zbarbuto 2017-09-25 09:56:08 +02:00
Miroslav Bajtoš 6e7809d3ef Merge pull request #264 from NextFaze/fix/ignore-maps-3
Ignore source maps in boot
2017-09-21 12:59:47 +02:00
Zak Barbuto a78a05f9b4 Ignore source maps in boot 2017-09-21 16:35:14 +09:30
Miroslav Bajtoš 42a7152474 Merge pull request #262 from strongloop/welcome-lehni
CODEOWNERS: add lehni
2017-08-31 17:02:24 +02:00
Miroslav Bajtoš 038bfeae43
CODEOWNERS: add lehni 2017-08-31 11:26:07 +02:00
Jürg Lehni 6e36f02005 Add support for ES6 style async boot scripts 2017-08-18 12:43:21 +02:00
Sakib Hasan a054fb0064 Create Issue and PR Templates (#261)
* create issue template

* create pr template
2017-08-16 15:02:49 -04:00
Diana Lau 10ec2c5ed6 Merge pull request #254 from strongloop/add-codeowner
Add CODEOWNERS file
2017-07-26 20:42:40 -04:00
Diana Lau 3f42e66dea Add CODEOWNER file 2017-07-26 19:40:35 -04:00
Diana Lau 2bf14c8cb4 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-06-22 09:47:10 -04:00
Miroslav Bajtoš 6e6ecb3ecc Merge pull request #238 from strongloop/replicate-issue-template
Replicate issue_template from loopback repo
2017-06-07 13:58:03 +02:00
Candy 0d216ac360 Merge pull request #249 from kallenboone/master
Update Italian translated strings Q2 2017
2017-06-02 16:36:07 -04:00
Allen Boone 96d59ce610 Update Italian translated strings Q2 2017 2017-06-02 16:21:20 -04:00
Candy 756e3aedda Merge pull request #248 from kallenboone/master
Update translated strings Q2 2017
2017-05-23 14:52:24 -04:00
Allen Boone 3ec9d3ab1c Update translated strings Q2 2017 2017-05-23 14:27:47 -04:00
Raymond Feng 92d6a1f91c 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š)
2017-05-22 08:44:21 -07:00
Raymond Feng 63a11502a6 Merge pull request #247 from strongloop/feature/upgrade-deps
Upgrade deps and fix style issues
2017-05-22 10:41:07 -05:00
Raymond Feng 3bb519d5e9 Upgrade deps and fix style issues 2017-05-08 14:48:48 -07:00
Raymond Feng 480380224d Merge pull request #240 from supasate/provide-script-extensions-option
Provide scriptExtensions option for 3.x
2017-04-03 10:02:51 -07:00
Supasate Choochaisri d68ffc6f6f Provide scriptExtensions option 2017-04-01 02:14:47 +07:00
Siddhi Pai aa321cf072 Replicate new issue_template from loopback 2017-02-15 15:27:59 -08:00
Siddhi Pai f39fe30e47 Replicate issue_template from loopback repo 2017-02-13 10:25:44 -08:00
Simon Ho 79d9ddb835 Merge pull request #234 from strongloop/update-support-URL
Replicate .github from loopback repo
2016-12-07 00:30:46 -08:00
Siddhi Pai ba688e0026 Update paid support URL 2016-12-06 03:06:40 -08:00
David Cheung f7c9cbc2c6 Merge pull request #181 from strongloop/feature/extensibility
[SEMVER-MAJOR] Refactor for modular and pluggable design
2016-11-22 14:19:29 -05:00
Raymond Feng ac1571ccf1 Refactor for modular and pluggable design
- refactor logic of processing artifacts into their own classes
- introduce Container as the main class for bootstrapping and build a
  registry of handlers during boot to organize them by a hierarchy
  denoted by path
- adopt middleware like registration and invocation
- container.use(path, handler)
- container.run(context, done)
- allow more phases during boot
- boot is now asynchronous
2016-11-22 13:38:28 -05:00
Miroslav Bajtoš 314dff9f5f Merge pull request #231 from strongloop/drop-support-node-0x
Drop support for Node v0.10 and v0.12
2016-11-15 15:42:39 +01:00
Miroslav Bajtoš ecc2d43957 Add Node v7 to Travis CI platforms 2016-11-15 15:07:33 +01:00
Miroslav Bajtoš fbea19a002 Drop support for Node v0.10 and v0.12 2016-11-15 15:07:22 +01:00
David Cheung e96b08087d Merge pull request #227 from strongloop/update-new-docs-url
readme: update URL to new doc site
2016-11-08 13:58:01 -05:00
David Cheung a17c6c50e3 readme: update URL to new doc site 2016-10-28 16:28:11 -04:00
Miroslav Bajtoš 6491cc8e71 Merge pull request #220 from Sequoia/patch-1
Update header-browser.md
2016-10-11 14:25:15 +02:00
David Cheung 94aef17122 Merge pull request #221 from strongloop/add_translation3
Update ja translation file
2016-10-06 15:27:34 -04:00
Candy 295db6d873 Update ja translation file 2016-10-06 11:25:39 -04:00
Sequoia McDowell 63cc0ecf7b Update header-browser.md
closes #53
2016-10-05 17:25:52 -04:00
Amirali Jafarian 0d985bae0b Merge pull request #219 from strongloop/add_translation2
Update translation files - round#2
2016-09-28 17:13:36 -04:00
Candy 5da1420027 Update translation files - round#2 2016-09-28 13:56:16 -04:00
Miroslav Bajtoš 57e5e64b4d Merge pull request #214 from strongloop/update-lb-3-rc-1
Update deps to loopback 3.0.0 RC
2016-09-23 12:41:58 +02:00
Miroslav Bajtoš 49ed10caaf Normalize line endings to support both LF and CRLF
This should fix build failures on Windows caused by line-ending
mismatch.
2016-09-23 11:16:26 +02:00
Miroslav Bajtoš 748a728a4f Remove "defaultForType" from datasource config
The option "defaultForType" is no longer supported by LoopBack
and causes an unhandled error.
2016-09-23 10:57:53 +02:00
Miroslav Bajtoš 58ef16993b Update deps to loopback 3.0.0 RC 2016-09-22 13:11:01 +02:00
David Cheung a6076ae8be Merge pull request #213 from strongloop/add-translatedFiles
Add translated files.
2016-09-21 15:19:30 -04:00
gunjpan 422fa6c11d globalization: add translated strings 2016-09-21 11:38:52 -04:00
David Cheung 28d58ede8e Merge pull request #211 from strongloop/start/3.0
Start development of 3.0
2016-09-16 12:02:59 -04:00
Miroslav Bajtoš 889a9fe275 Start development of 3.0 2016-09-16 10:17:29 +02:00
91 changed files with 6357 additions and 3713 deletions

50
.github/ISSUE_TEMPLATE/Bug_report.md vendored Normal file
View File

@ -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_

View File

@ -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.

27
.github/ISSUE_TEMPLATE/Question.md vendored Normal file
View File

@ -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/
-->

11
.github/ISSUE_TEMPLATE/config.yml vendored Normal file
View File

@ -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.

18
.github/PULL_REQUEST_TEMPLATE.md vendored Normal file
View File

@ -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)

24
.github/stale.yml vendored Normal file
View File

@ -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.

3
.gitignore vendored
View File

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

1
.npmrc Normal file
View File

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

View File

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

30
3.0-RELEASE-NOTES.md Normal file
View File

@ -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.

View File

@ -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
==========================

11
CODEOWNERS Normal file
View File

@ -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

View File

@ -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.

View File

@ -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.

View File

@ -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;

View File

@ -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.

View File

@ -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);
};

17
intl/cs/messages.json Normal file
View File

@ -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}"
}

17
intl/de/messages.json Normal file
View File

@ -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}"
}

View File

@ -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}"
}

17
intl/es/messages.json Normal file
View File

@ -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}"
}

17
intl/fr/messages.json Normal file
View File

@ -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}"
}

17
intl/it/messages.json Normal file
View File

@ -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}"
}

17
intl/ja/messages.json Normal file
View File

@ -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}"
}

17
intl/ko/messages.json Normal file
View File

@ -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}"
}

17
intl/nl/messages.json Normal file
View File

@ -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}"
}

17
intl/pl/messages.json Normal file
View File

@ -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}"
}

17
intl/pt/messages.json Normal file
View File

@ -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}"
}

17
intl/ru/messages.json Normal file
View File

@ -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}"
}

17
intl/tr/messages.json Normal file
View File

@ -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}"
}

View File

@ -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}"
}

View File

@ -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}"
}

222
lib/bootstrapper.js vendored Normal file
View File

@ -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;
};

View File

@ -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});
}

View File

@ -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;
}
}

View File

@ -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';
}

View File

@ -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;
}

11
lib/globalize.js Normal file
View File

@ -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();

338
lib/plugin-base.js Normal file
View File

@ -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);
};

66
lib/plugin-loader.js Normal file
View File

@ -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;
});
};

135
lib/plugins/application.js Normal file
View File

@ -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();
});
};

105
lib/plugins/boot-script.js Normal file
View File

@ -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);
}

52
lib/plugins/component.js Normal file
View File

@ -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);
});
};

41
lib/plugins/datasource.js Normal file
View File

@ -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);
});
};

266
lib/plugins/middleware.js Normal file
View File

@ -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);
});
};

200
lib/plugins/mixin.js Normal file
View File

@ -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);
}
});
};

319
lib/plugins/model.js Normal file
View File

@ -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);
});
};

31
lib/plugins/swagger.js Normal file
View File

@ -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;
};

View File

@ -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.

View File

@ -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."
}

40
test/acceptance.test.js Normal file
View File

@ -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);
});
});
});

122
test/bootstrapper.test.js vendored Normal file
View File

@ -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;
});
});

View File

@ -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;
}

View File

@ -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

View File

@ -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

View File

@ -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,

View File

@ -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';

View File

@ -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);

View File

@ -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');
};

View File

@ -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;
};

View File

@ -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;
};

View File

@ -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';

View File

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

2
test/fixtures/empty-app/config.json vendored Normal file
View File

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

View File

@ -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();

View File

@ -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;
};

View File

@ -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');

View File

@ -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');

View File

@ -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;

View File

@ -0,0 +1,6 @@
'use strict';
module.exports = function(app, callback) {
process.bootFlags.push('customjs');
callback();
};

View File

@ -0,0 +1,6 @@
'use strict';
module.exports = function(app, callback) {
process.bootFlags.push('customjs2');
callback();
};

View File

@ -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');

View File

@ -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();
}
};

View File

@ -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();
});
},
});
};

14
test/fixtures/simple-app/boot/reject.js vendored Normal file
View File

@ -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'));
}
};

View File

@ -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();
});
},
};
};

12
test/fixtures/simple-app/boot/throw.js vendored Normal file
View File

@ -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');
}
};

View File

@ -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});

View File

@ -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 || [];

View File

@ -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
*/

View File

@ -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);
};

View File

@ -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);

View File

@ -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);

View File

@ -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;

View File

@ -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]);
}

View File

@ -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;
};

View File

@ -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 || [];

View File

@ -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);
};

View File

@ -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);
});
});