Compare commits
402 Commits
Author | SHA1 | Date |
---|---|---|
|
c8241b2b35 | |
|
afe57b5b04 | |
|
56dcfaab6c | |
|
51ec554632 | |
|
f4313949c3 | |
|
98883a877f | |
|
7a6021dc77 | |
|
f9d6d147bc | |
|
298aec304a | |
|
0043b9e27d | |
|
3cbe0028a4 | |
|
e9b108f9c6 | |
|
0bbe8335a4 | |
|
c11ba18f73 | |
|
1fec4ea375 | |
|
974c63ccd5 | |
|
f6b2f24b29 | |
|
b4d1f96494 | |
|
6d8d9c92ce | |
|
14050c199f | |
|
59e46b3d91 | |
|
8bb4368ca8 | |
|
a1d2954626 | |
|
837b8185f2 | |
|
ef39abf6eb | |
|
8587dd6a19 | |
|
561cbd5bca | |
|
02da15cd3e | |
|
17ba74ed1c | |
|
e73b90ed97 | |
|
c10e8225bb | |
|
825411e6b4 | |
|
9b77d8159a | |
|
fd3a381289 | |
|
3e7031c82e | |
|
c278fe6705 | |
|
0ace54cd9e | |
|
bfa874de63 | |
|
b7a14e30e1 | |
|
2675771ec1 | |
|
14eeb4487d | |
|
e89e353ab0 | |
|
d598c3738a | |
|
c4ab39c4e0 | |
|
ded5bccb60 | |
|
473e685a69 | |
|
cdba5f0748 | |
|
47ba28898a | |
|
a035aaa3ae | |
|
c1cda7aefc | |
|
d7efabaebd | |
|
a9ae78ea54 | |
|
3277738937 | |
|
123f3da523 | |
|
e64adb92de | |
|
6e7809d3ef | |
|
a78a05f9b4 | |
|
42a7152474 | |
|
038bfeae43 | |
|
6e36f02005 | |
|
a054fb0064 | |
|
10ec2c5ed6 | |
|
3f42e66dea | |
|
2bf14c8cb4 | |
|
6e6ecb3ecc | |
|
0d216ac360 | |
|
96d59ce610 | |
|
756e3aedda | |
|
3ec9d3ab1c | |
|
92d6a1f91c | |
|
63a11502a6 | |
|
3bb519d5e9 | |
|
480380224d | |
|
d68ffc6f6f | |
|
aa321cf072 | |
|
f39fe30e47 | |
|
79d9ddb835 | |
|
ba688e0026 | |
|
f7c9cbc2c6 | |
|
ac1571ccf1 | |
|
314dff9f5f | |
|
ecc2d43957 | |
|
fbea19a002 | |
|
e96b08087d | |
|
a17c6c50e3 | |
|
6491cc8e71 | |
|
94aef17122 | |
|
295db6d873 | |
|
63cc0ecf7b | |
|
0d985bae0b | |
|
5da1420027 | |
|
57e5e64b4d | |
|
49ed10caaf | |
|
748a728a4f | |
|
58ef16993b | |
|
a6076ae8be | |
|
422fa6c11d | |
|
28d58ede8e | |
|
889a9fe275 | |
|
d2c55e79c5 | |
|
53112957d9 | |
|
09c3f8365b | |
|
e68502a4f5 | |
|
dcc575f90b | |
|
13a049412a | |
|
1121721afe | |
|
8fb9f751cd | |
|
1382e8af2f | |
|
129938bacd | |
|
eb49c7a386 | |
|
9b23e71501 | |
|
ce7fe3fb07 | |
|
c2484a6265 | |
|
296c38568c | |
|
24571328a8 | |
|
5f5e86e47f | |
|
7b226b212e | |
|
58d9322190 | |
|
5eaa909006 | |
|
defe5f4bb8 | |
|
d334425ada | |
|
51a699af01 | |
|
47974fd1d2 | |
|
1d2649eee7 | |
|
cc551b0c0f | |
|
707214cac4 | |
|
4a815deb27 | |
|
b3e5f23865 | |
|
902005ed8e | |
|
058e0e2f56 | |
|
90b9211fff | |
|
b0e5a0bc63 | |
|
e9afcaac70 | |
|
2e3b37a4f8 | |
|
c475e4af84 | |
|
d850560a18 | |
|
a5b888a719 | |
|
ab6a8ae776 | |
|
3836078985 | |
|
adeece3b30 | |
|
d8b298b53d | |
|
de6ebf65ee | |
|
4e3e1a7394 | |
|
2574c9dbb6 | |
|
088ca864c2 | |
|
5f0175afbf | |
|
a023e1f142 | |
|
c17efda0eb | |
|
3f55acf9d5 | |
|
1d27cf0e05 | |
|
24fbfbebf1 | |
|
638368844d | |
|
5238bc1027 | |
|
c8fdbd5110 | |
|
63589c03ab | |
|
fbafa2b608 | |
|
d249b15ce0 | |
|
d1b00ce5d8 | |
|
6ef37ef651 | |
|
819e1ea9c8 | |
|
a5bc1056aa | |
|
55c87796cf | |
|
7cbb8f6b40 | |
|
43042905af | |
|
f0aa0f8fdb | |
|
26a50137c7 | |
|
1b491dee85 | |
|
0b0f0ed202 | |
|
8b475a97b2 | |
|
75c945effa | |
|
f6dfbd2625 | |
|
674a8da693 | |
|
8864e2b394 | |
|
b081d828b2 | |
|
be3997de15 | |
|
32276c79d4 | |
|
7362cc489e | |
|
952c4101ef | |
|
e052da83db | |
|
450aa6dd21 | |
|
329fc59113 | |
|
02c826337c | |
|
ab723b5490 | |
|
41af081c55 | |
|
4f6e768426 | |
|
6980a250ef | |
|
ac5265f726 | |
|
66cc099d1d | |
|
0aac03ebb5 | |
|
5c8334c7c7 | |
|
a63ae8e44b | |
|
6c33a60bbc | |
|
a7d6cf695c | |
|
50d99c7e6b | |
|
bd4c2fc968 | |
|
2e63631b31 | |
|
a06aaba1cb | |
|
083a1265a4 | |
|
44f733f59f | |
|
140180f667 | |
|
53f51820f5 | |
|
2815a42d81 | |
|
43df90a4c8 | |
|
0563840006 | |
|
b6c60a297d | |
|
0e19f0a1b2 | |
|
9bb988713e | |
|
aed73a9e51 | |
|
c3bbc13fd8 | |
|
e5fd8c7975 | |
|
a2000829bc | |
|
acb3d1815f | |
|
e3763ac28e | |
|
f9d74f2c5e | |
|
6c0c8b9f8b | |
|
d9c0f12a66 | |
|
044a4df07a | |
|
80831cdaaf | |
|
1d8bd15a77 | |
|
2e9f001100 | |
|
acf1868ba4 | |
|
1b9f137062 | |
|
39d820a657 | |
|
ade8605618 | |
|
6526ffeb3d | |
|
626200a098 | |
|
07d276977a | |
|
d7c67c803a | |
|
3a7b9739d9 | |
|
416738b679 | |
|
311b892a0f | |
|
61798455f8 | |
|
187a105333 | |
|
c9f377a4c9 | |
|
4913b3ffb9 | |
|
cb7ca7d9ad | |
|
6e6eaccadd | |
|
df81d5487c | |
|
2c4b6f06a4 | |
|
bfcd3ff15e | |
|
872889423a | |
|
464c389af9 | |
|
276391811a | |
|
d041d80933 | |
|
e89a60c45c | |
|
ce58b7d65e | |
|
9b407b1ada | |
|
d8bf8687a8 | |
|
af1d0dcccb | |
|
a03e881f0a | |
|
15eb54ac85 | |
|
9dde8b0adf | |
|
6424534831 | |
|
215abf9d30 | |
|
77b83ce3fd | |
|
4f9f112d80 | |
|
548dba0c37 | |
|
a935669d9c | |
|
bb98472965 | |
|
d2c70bad3c | |
|
2a1f07aad9 | |
|
30a7b6d9b8 | |
|
fbf1e95e29 | |
|
ab33132ef7 | |
|
8bc526377f | |
|
146a3183eb | |
|
4f8514a454 | |
|
30ff50c581 | |
|
33b3729b62 | |
|
5fd79f0339 | |
|
c47bde9281 | |
|
8aa7156d3b | |
|
1f7d8e56e8 | |
|
f8247be23c | |
|
1ef2616979 | |
|
c4473951a0 | |
|
6a442f8307 | |
|
4031f6b985 | |
|
8a3e529ea3 | |
|
301031b78f | |
|
beb4f8c34c | |
|
4f65067348 | |
|
a90964ab30 | |
|
4b9e13bfc6 | |
|
e1f7c592a1 | |
|
8da44e69dd | |
|
77ded6edfe | |
|
e81ff119a3 | |
|
3dfa3bcb13 | |
|
8cc2518cb0 | |
|
81303b3093 | |
|
d3145f5745 | |
|
097fced310 | |
|
6de571f442 | |
|
2f72006c88 | |
|
d25c64523d | |
|
1114bc9227 | |
|
08fcc5faa7 | |
|
5b5071864b | |
|
83723379a2 | |
|
e2aff71bf9 | |
|
6040f66adc | |
|
9cd5d3d125 | |
|
f4c7b1ba38 | |
|
27bd48e0f6 | |
|
db917bf03b | |
|
ed59cb2483 | |
|
176f96e242 | |
|
f5ac5273a7 | |
|
84f8a51138 | |
|
d7bdbd31b1 | |
|
b480efde8a | |
|
e936deffe2 | |
|
3961f1c615 | |
|
56c74174f1 | |
|
5cea78c6aa | |
|
b5c585291b | |
|
aa4cbdd80f | |
|
26abb43ad4 | |
|
011296d825 | |
|
c04946073f | |
|
927bee82f7 | |
|
fadfaffe25 | |
|
676f347f36 | |
|
68593c8100 | |
|
e0abff007e | |
|
94cb4d6342 | |
|
38c4944e2e | |
|
0d4b5bb7c4 | |
|
d54e2b54d0 | |
|
18121a4208 | |
|
f0836719c9 | |
|
e1d870dced | |
|
abda37fee9 | |
|
ac73288cef | |
|
ed0880d00f | |
|
e974033395 | |
|
6c26e99ab9 | |
|
0169f22cd0 | |
|
34de593202 | |
|
93bfc3a63a | |
|
edb1f1fdce | |
|
20ed867d79 | |
|
1ae0167b7c | |
|
51cf0052d0 | |
|
217f0ce32f | |
|
f98d2cb89c | |
|
bc233e83d3 | |
|
4d55ab82aa | |
|
5471129f79 | |
|
e2092bba17 | |
|
c4b09c6b7a | |
|
b5989e907e | |
|
e42ee20d00 | |
|
be74e7c1ec | |
|
eac6cdf645 | |
|
6bf995c55b | |
|
cb67fc4165 | |
|
85c40c635a | |
|
9493aca34e | |
|
e7250b6eac | |
|
2bcf1f60aa | |
|
431bdc9b18 | |
|
53ca2f697a | |
|
cfe5f40808 | |
|
3129f6495c | |
|
3522f117c0 | |
|
25704cf69a | |
|
5e142c5188 | |
|
fc18561cc7 | |
|
5e23db50a5 | |
|
f222be477e | |
|
92455f569c | |
|
a3c347d073 | |
|
d5cd0a3b50 | |
|
af9c4d8cc6 | |
|
c70b8ac413 | |
|
edd41be02f | |
|
b9b338ddde | |
|
18e1b3b4f5 | |
|
961e04b379 | |
|
2cc5a88699 | |
|
230360ef28 | |
|
ac16d92a8b | |
|
4b5f57a7df | |
|
0a0a6f5d01 | |
|
2eab6bf32a | |
|
57e96b0d38 | |
|
a0d5dafd99 | |
|
ef72efa70b | |
|
b887b33b57 | |
|
a9a401ad56 | |
|
bad5b60623 | |
|
c3a9a09941 | |
|
e5e8f29d30 | |
|
e22ecd39ce | |
|
a204fdc1c9 | |
|
fccc6e147a | |
|
43993bf975 | |
|
47b5bb5f5c | |
|
18bc572c20 | |
|
7c255f089e |
|
@ -1,3 +1,2 @@
|
||||||
node_modules/
|
|
||||||
coverage/
|
coverage/
|
||||||
test/sandbox/
|
test/sandbox/
|
|
@ -0,0 +1,50 @@
|
||||||
|
---
|
||||||
|
name: Bug report
|
||||||
|
about: Create a report to help us improve
|
||||||
|
labels: bug
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
<!-- 🚨 STOP 🚨 STOP 🚨 STOP 🚨
|
||||||
|
|
||||||
|
HELP US HELP YOU, PLEASE
|
||||||
|
- Do a quick search to avoid duplicate issues
|
||||||
|
- Provide as much information as possible (reproduction sandbox, use case for features, etc.)
|
||||||
|
- Consider using a more suitable venue for questions such as Stack Overflow, Gitter, etc.
|
||||||
|
|
||||||
|
Please fill in the *entire* template below.
|
||||||
|
|
||||||
|
-->
|
||||||
|
|
||||||
|
## Steps to reproduce
|
||||||
|
|
||||||
|
<!-- Describe how to reproduce the issue -->
|
||||||
|
|
||||||
|
## Current Behavior
|
||||||
|
|
||||||
|
<!-- Describe the observed result -->
|
||||||
|
|
||||||
|
## Expected Behavior
|
||||||
|
|
||||||
|
<!-- Describe what did you expect instead, what is the desired outcome? -->
|
||||||
|
|
||||||
|
## Link to reproduction sandbox
|
||||||
|
|
||||||
|
<!--
|
||||||
|
See https://loopback.io/doc/en/contrib/Reporting-issues.html#loopback-3x-bugs
|
||||||
|
Note: Failure to provide a sandbox application for reproduction purposes will result in the issue being closed.
|
||||||
|
-->
|
||||||
|
|
||||||
|
## Additional information
|
||||||
|
|
||||||
|
<!--
|
||||||
|
Copy+paste the output of these two commands:
|
||||||
|
node -e 'console.log(process.platform, process.arch, process.versions.node)'
|
||||||
|
npm ls --prod --depth 0 | grep loopback
|
||||||
|
-->
|
||||||
|
|
||||||
|
## Related Issues
|
||||||
|
|
||||||
|
<!-- Did you find other bugs that looked similar? -->
|
||||||
|
|
||||||
|
_See [Reporting Issues](http://loopback.io/doc/en/contrib/Reporting-issues.html) for more tips on writing good issues_
|
|
@ -0,0 +1,25 @@
|
||||||
|
---
|
||||||
|
name: Feature request
|
||||||
|
about: Suggest an idea for this project
|
||||||
|
labels: feature
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Suggestion
|
||||||
|
|
||||||
|
<!-- A summary of what you'd like to see added or changed -->
|
||||||
|
|
||||||
|
## Use Cases
|
||||||
|
|
||||||
|
<!--
|
||||||
|
What do you want to use this for?
|
||||||
|
What shortcomings exist with current approaches?
|
||||||
|
-->
|
||||||
|
|
||||||
|
## Examples
|
||||||
|
|
||||||
|
<!-- Show how this would be used and what the behavior would be -->
|
||||||
|
|
||||||
|
## Acceptance criteria
|
||||||
|
|
||||||
|
TBD - will be filled by the team.
|
|
@ -0,0 +1,27 @@
|
||||||
|
---
|
||||||
|
name: Question
|
||||||
|
about: The issue tracker is not for questions. Please use Stack Overflow or other resources for help.
|
||||||
|
labels: question
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
<!-- 🚨 STOP 🚨 STOP 🚨 STOP 🚨
|
||||||
|
|
||||||
|
THE ISSUE TRACKER IS NOT FOR QUESTIONS.
|
||||||
|
|
||||||
|
DO NOT CREATE A NEW ISSUE TO ASK A QUESTION.
|
||||||
|
|
||||||
|
Please use one of the following resources for help:
|
||||||
|
|
||||||
|
**Questions**
|
||||||
|
|
||||||
|
- https://stackoverflow.com/tags/loopbackjs
|
||||||
|
- https://groups.google.com/forum/#!forum/loopbackjs
|
||||||
|
- https://gitter.im/strongloop/loopback
|
||||||
|
|
||||||
|
**Immediate support**
|
||||||
|
|
||||||
|
- https://strongloop.com/api-connect-faqs/
|
||||||
|
- https://strongloop.com/node-js/subscription-plans/
|
||||||
|
|
||||||
|
-->
|
|
@ -0,0 +1,11 @@
|
||||||
|
blank_issues_enabled: false
|
||||||
|
contact_links:
|
||||||
|
- name: Report a security vulnerability
|
||||||
|
url: https://loopback.io/doc/en/contrib/Reporting-issues.html#security-issues
|
||||||
|
about: Do not report security vulnerabilities using GitHub issues. Please send an email to `reachsl@us.ibm.com` instead.
|
||||||
|
- name: Get help on StackOverflow
|
||||||
|
url: https://stackoverflow.com/tags/loopbackjs
|
||||||
|
about: Please ask and answer questions on StackOverflow.
|
||||||
|
- name: Join our mailing list
|
||||||
|
url: https://groups.google.com/forum/#!forum/loopbackjs
|
||||||
|
about: You can also post your question to our mailing list.
|
|
@ -0,0 +1,18 @@
|
||||||
|
<!--
|
||||||
|
Please provide a high-level description of the changes made by your pull request.
|
||||||
|
|
||||||
|
Include references to all related GitHub issues and other pull requests, for example:
|
||||||
|
|
||||||
|
Fixes #123
|
||||||
|
Implements #254
|
||||||
|
See also #23
|
||||||
|
-->
|
||||||
|
|
||||||
|
## Checklist
|
||||||
|
|
||||||
|
👉 [Read and sign the CLA (Contributor License Agreement)](https://cla.strongloop.com/agreements/strongloop/loopback-boot) 👈
|
||||||
|
|
||||||
|
- [ ] `npm test` passes on your machine
|
||||||
|
- [ ] New tests added or existing tests modified to cover all changes
|
||||||
|
- [ ] Code conforms with the [style guide](https://loopback.io/doc/en/contrib/style-guide-es6.html)
|
||||||
|
- [ ] Commit messages are following our [guidelines](https://loopback.io/doc/en/contrib/git-commit-messages.html)
|
|
@ -0,0 +1,24 @@
|
||||||
|
# Number of days of inactivity before an issue becomes stale
|
||||||
|
daysUntilStale: 60
|
||||||
|
# Number of days of inactivity before a stale issue is closed
|
||||||
|
daysUntilClose: 14
|
||||||
|
# Issues with these labels will never be considered stale
|
||||||
|
exemptLabels:
|
||||||
|
- pinned
|
||||||
|
- security
|
||||||
|
- critical
|
||||||
|
- p1
|
||||||
|
- major
|
||||||
|
# Label to use when marking an issue as stale
|
||||||
|
staleLabel: stale
|
||||||
|
# Comment to post when marking an issue as stale. Set to `false` to disable
|
||||||
|
markComment: >
|
||||||
|
This issue has been automatically marked as stale because it has not had
|
||||||
|
recent activity. It will be closed if no further activity occurs. Thank you
|
||||||
|
for your contributions.
|
||||||
|
# Comment to post when closing a stale issue. Set to `false` to disable
|
||||||
|
closeComment: >
|
||||||
|
This issue has been closed due to continued inactivity. Thank you for your understanding.
|
||||||
|
If you believe this to be in error, please contact one of the code owners,
|
||||||
|
listed in the `CODEOWNERS` file at the top-level of this repository.
|
||||||
|
|
|
@ -10,7 +10,10 @@
|
||||||
*.pid
|
*.pid
|
||||||
*.swp
|
*.swp
|
||||||
*.swo
|
*.swo
|
||||||
|
*.iml
|
||||||
node_modules
|
node_modules
|
||||||
|
generated-instructions*.json
|
||||||
checkstyle.xml
|
checkstyle.xml
|
||||||
loopback-boot-*.tgz
|
loopback-boot-*.tgz
|
||||||
/test/sandbox/
|
/test/sandbox/
|
||||||
|
intl/zz/
|
||||||
|
|
23
.jshintrc
23
.jshintrc
|
@ -1,23 +0,0 @@
|
||||||
{
|
|
||||||
"node": true,
|
|
||||||
"browser": true,
|
|
||||||
"camelcase" : true,
|
|
||||||
"eqnull" : true,
|
|
||||||
"indent": 2,
|
|
||||||
"undef": true,
|
|
||||||
"unused": true,
|
|
||||||
"quotmark": "single",
|
|
||||||
"maxlen": 80,
|
|
||||||
"trailing": true,
|
|
||||||
"newcap": true,
|
|
||||||
"nonew": true,
|
|
||||||
"sub": true,
|
|
||||||
"globals": {
|
|
||||||
"describe": true,
|
|
||||||
"it": true,
|
|
||||||
"before": true,
|
|
||||||
"beforeEach": true,
|
|
||||||
"after": true,
|
|
||||||
"afterEach": true
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -0,0 +1,6 @@
|
||||||
|
sudo: false
|
||||||
|
language: node_js
|
||||||
|
node_js:
|
||||||
|
- "8"
|
||||||
|
- "10"
|
||||||
|
|
|
@ -0,0 +1,30 @@
|
||||||
|
# List of notable changes made between 2.x and 3.0
|
||||||
|
|
||||||
|
All breaking changes must be described here. When adding a new entry,
|
||||||
|
always describe the impact on users and instructions for upgrading
|
||||||
|
applications from 2.x to 3.0.
|
||||||
|
|
||||||
|
## boot.compile is now async and returns context with instructions
|
||||||
|
|
||||||
|
Users that uses `boot.compile()` in the following syntax would need to update
|
||||||
|
their implementation, it now calls callback function with `instructions` in
|
||||||
|
`context` object rather than synchronously returning instructions.
|
||||||
|
|
||||||
|
Before:
|
||||||
|
|
||||||
|
```js
|
||||||
|
var APP_PATH = path.resolve('../sandbox');
|
||||||
|
var instructions = boot.compile(APP_PATH);
|
||||||
|
```
|
||||||
|
|
||||||
|
New signature:
|
||||||
|
|
||||||
|
```js
|
||||||
|
var APP_PATH = path.resolve('../sandbox');
|
||||||
|
var instructions;
|
||||||
|
boot.compile(APP_PATH, function(err, context) {
|
||||||
|
instructions = context.instructions;
|
||||||
|
});
|
||||||
|
```
|
||||||
|
|
||||||
|
Please see [PR #181](https://github.com/strongloop/loopback-boot/pull/181) for full details of change.
|
551
CHANGES.md
551
CHANGES.md
|
@ -1,14 +1,545 @@
|
||||||
## Changes in version 1.0
|
2019-06-24, Version 3.3.1
|
||||||
|
=========================
|
||||||
|
|
||||||
- New options: `modelsRootDir`, `dsRootDir`
|
* chore: update LTS status (Diana Lau)
|
||||||
|
|
||||||
- Load configuration from files, support dynamic (scripted) options
|
* chore: update copyrights years (Agnes Lin)
|
||||||
|
|
||||||
```sh
|
|
||||||
app.json, app.local.*, app.{env}.*
|
|
||||||
datasources.json, datasources.local.*, datasources.{env}.*
|
|
||||||
```
|
|
||||||
|
|
||||||
- Scripts in `models/` and `boot/` can export `function(app)`,
|
2019-03-28, Version 3.3.0
|
||||||
this function is then called by the bootstrapper. The existing code
|
=========================
|
||||||
using `var app = require('../app')` will continue to work.
|
|
||||||
|
* 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
|
||||||
|
==========================
|
||||||
|
|
||||||
|
* Replace fs.existsSync calls with fs.statSync (Joshua Estrin Skrzypek)
|
||||||
|
|
||||||
|
* Change test cases port to be dynamic (David Cheung)
|
||||||
|
|
||||||
|
* Globalization for Loopback-boot (David Cheung)
|
||||||
|
|
||||||
|
|
||||||
|
2016-07-27, Version 2.21.0
|
||||||
|
==========================
|
||||||
|
|
||||||
|
* Configurable dir for components and middleware (Doped Dude)
|
||||||
|
|
||||||
|
* test: fix security warning (Miroslav Bajtoš)
|
||||||
|
|
||||||
|
|
||||||
|
2016-07-14, Version 2.20.0
|
||||||
|
==========================
|
||||||
|
|
||||||
|
* Update URLs in CONTRIBUTING.md (#198) (Ryan Graham)
|
||||||
|
|
||||||
|
* travis: drop io.js, add Node v4 and v6 (Miroslav Bajtoš)
|
||||||
|
|
||||||
|
* Stop caching config files (Miroslav Bajtoš)
|
||||||
|
|
||||||
|
|
||||||
|
2016-06-20, Version 2.19.0
|
||||||
|
==========================
|
||||||
|
|
||||||
|
* update copyright notices and license (Ryan Graham)
|
||||||
|
|
||||||
|
* Add flag var lazyConnect to ds config (juehou)
|
||||||
|
|
||||||
|
|
||||||
|
2016-04-13, Version 2.18.1
|
||||||
|
==========================
|
||||||
|
|
||||||
|
* parse config: should ignore null values (Loïc Mahieu)
|
||||||
|
|
||||||
|
|
||||||
|
2016-04-07, Version 2.18.0
|
||||||
|
==========================
|
||||||
|
|
||||||
|
* Dynamic datasources.json from ENV and config.json (David Cheung)
|
||||||
|
|
||||||
|
* Use eslint with loopback config (Miroslav Bajtoš)
|
||||||
|
|
||||||
|
|
||||||
|
2016-02-23, Version 2.17.0
|
||||||
|
==========================
|
||||||
|
|
||||||
|
* executor: move "booted" and cb() to the next tick (Miroslav Bajtoš)
|
||||||
|
|
||||||
|
* Fix lodash 4.0.0 breaking changes (Jérémie Drouet)
|
||||||
|
|
||||||
|
* When config is overriden with null don't merge (Farid Neshat)
|
||||||
|
|
||||||
|
|
||||||
|
2015-12-22, Version 2.16.0
|
||||||
|
==========================
|
||||||
|
|
||||||
|
* executor: allow loopback versions >= 3.0.0-alpha (Miroslav Bajtoš)
|
||||||
|
|
||||||
|
|
||||||
|
2015-12-04, Version 2.15.0
|
||||||
|
==========================
|
||||||
|
|
||||||
|
* Bluemix prefers HOST/PORT over VCAP_APP_XXXX (Sai Vennam)
|
||||||
|
|
||||||
|
* Set app env if it is supplied in options object (Amir Jafarian)
|
||||||
|
|
||||||
|
|
||||||
|
2015-11-24, Version 2.14.2
|
||||||
|
==========================
|
||||||
|
|
||||||
|
* executor: preserve RegExps in middleware paths (Miroslav Bajtoš)
|
||||||
|
|
||||||
|
|
||||||
|
2015-11-24, Version 2.14.1
|
||||||
|
==========================
|
||||||
|
|
||||||
|
* Warn user if missing a config file (Amir Jafarian)
|
||||||
|
|
||||||
|
* Refer to licenses with a link (Sam Roberts)
|
||||||
|
|
||||||
|
|
||||||
|
2015-10-14, Version 2.14.0
|
||||||
|
==========================
|
||||||
|
|
||||||
|
* Support bluemix env variables for host and port (Miroslav Bajtoš)
|
||||||
|
|
||||||
|
|
||||||
|
2015-10-01, Version 2.13.0
|
||||||
|
==========================
|
||||||
|
|
||||||
|
* add env folder for boot (yorkie)
|
||||||
|
|
||||||
|
* Use strongloop conventions for licensing (Sam Roberts)
|
||||||
|
|
||||||
|
|
||||||
|
2015-09-09, Version 2.12.2
|
||||||
|
==========================
|
||||||
|
|
||||||
|
* test: fix strict mode failure (Ryan Graham)
|
||||||
|
|
||||||
|
|
||||||
|
2015-08-31, Version 2.12.1
|
||||||
|
==========================
|
||||||
|
|
||||||
|
* Add config variable checks (Simon Ho)
|
||||||
|
|
||||||
|
|
||||||
|
2015-08-28, Version 2.12.0
|
||||||
|
==========================
|
||||||
|
|
||||||
|
* Resolve ${var} values in component-config.json (Hage Yaapa)
|
||||||
|
|
||||||
|
* Resolve ${var} values in middleware.json (Hack Sparrow)
|
||||||
|
|
||||||
|
* Upgrade Travis to container-based infrastructure (Miroslav Bajtoš)
|
||||||
|
|
||||||
|
|
||||||
|
2015-08-11, Version 2.11.0
|
||||||
|
==========================
|
||||||
|
|
||||||
|
* Allow middleware array merge by a key in item objects (Raymond Feng)
|
||||||
|
|
||||||
|
|
||||||
|
2015-08-10, Version 2.10.0
|
||||||
|
==========================
|
||||||
|
|
||||||
|
* Enhance middleware config merge (Raymond Feng)
|
||||||
|
|
||||||
|
|
||||||
|
2015-08-03, Version 2.9.0
|
||||||
|
=========================
|
||||||
|
|
||||||
|
* Fix the build failure (Raymond Feng)
|
||||||
|
|
||||||
|
* Fix the model-config/datasource merge (Raymond Feng)
|
||||||
|
|
||||||
|
* Resolved style issue (Dennis Ashby)
|
||||||
|
|
||||||
|
* Added code to allow model-config to respect model-config.local.js and model-config.env.js as do other config files. (Dennis Ashby)
|
||||||
|
|
||||||
|
* Add jsdoc for `options.mixinSources` (Miroslav Bajtoš)
|
||||||
|
|
||||||
|
* allow middleware to be optional (Hage Yaapa)
|
||||||
|
|
||||||
|
|
||||||
|
2015-06-24, Version 2.8.2
|
||||||
|
=========================
|
||||||
|
|
||||||
|
* Excl. mod. main path from middleware instructions (Miroslav Bajtoš)
|
||||||
|
|
||||||
|
|
||||||
|
2015-06-10, Version 2.8.1
|
||||||
|
=========================
|
||||||
|
|
||||||
|
* Add more debug info for config loading (Ritchie Martori)
|
||||||
|
|
||||||
|
* use a new variable for better debug output (Bryan Clark)
|
||||||
|
|
||||||
|
|
||||||
|
2015-05-29, Version 2.8.0
|
||||||
|
=========================
|
||||||
|
|
||||||
|
* Support iisnode using named pipes as PORT value (Jonathan Sheely)
|
||||||
|
|
||||||
|
* support 'mixinsources' option (Pradnya Baviskar)
|
||||||
|
|
||||||
|
* compiler: Simplify verifyModelDefinitions() (Miroslav Bajtoš)
|
||||||
|
|
||||||
|
* Fix coding style issues, add API docs (Miroslav Bajtoš)
|
||||||
|
|
||||||
|
* Extend options arg to support custom model definitions (Shlomi Assaf)
|
||||||
|
|
||||||
|
* add support for mixins - [mixinDirs]: List of directories to look for files containing model mixin definition. (Pradnya Baviskar)
|
||||||
|
|
||||||
|
|
||||||
|
2015-04-23, Version 2.7.1
|
||||||
|
=========================
|
||||||
|
|
||||||
|
* executor: fix port lookup (Miroslav Bajtoš)
|
||||||
|
|
||||||
|
* Clean up compiler.tryResolveAppPath (Miroslav Bajtoš)
|
||||||
|
|
||||||
|
* Configure Travis CI builds (Miroslav Bajtoš)
|
||||||
|
|
||||||
|
|
||||||
|
2015-04-15, Version 2.7.0
|
||||||
|
=========================
|
||||||
|
|
||||||
|
* Upgrade lodash and drop underscore.string (Bryan Clark)
|
||||||
|
|
||||||
|
* add console.error message to a bad require in a boot script (Bryan Clark)
|
||||||
|
|
||||||
|
* Support per-application registry of models (Miroslav Bajtoš)
|
||||||
|
|
||||||
|
* Use filename as default value for Model name (Pradnya Baviskar)
|
||||||
|
|
||||||
|
* compiler: code cleanup (Miroslav Bajtoš)
|
||||||
|
|
||||||
|
* Improve the resolution of relative paths - resolve module relative path for component - prioritize coffeescript over json (Pradnya Baviskar)
|
||||||
|
|
||||||
|
* Resolve module paths as relative to appRootDir - for middleware (Pradnya Baviskar)
|
||||||
|
|
||||||
|
* Support for multiple apps in browserified bundle. (Krishna Raman)
|
||||||
|
|
||||||
|
* Resolve missing file extension for module relative paths (Pradnya Baviskar)
|
||||||
|
|
||||||
|
* Resolve module paths as relative to appRootDir (Pradnya Baviskar)
|
||||||
|
|
||||||
|
* Resolve relative paths in using appRootDir (Pradnya Baviskar)
|
||||||
|
|
||||||
|
* Add feature to disable component (Pradnya Baviskar)
|
||||||
|
|
||||||
|
* Fix test for different line endings on Windows (Pradnya Baviskar)
|
||||||
|
|
||||||
|
* Refactor unit test assertions to be more specific (Simon Ho)
|
||||||
|
|
||||||
|
* Add unit test to verify `app.booting flag status (Simon Ho)
|
||||||
|
|
||||||
|
|
||||||
|
2015-02-20, Version 2.6.5
|
||||||
|
=========================
|
||||||
|
|
||||||
|
* Save instructions.json in root dir Saving in node_modules dir causes complaints and missing files fixes https://github.com/strongloop/loopback-boot/issues/94 (Berkeley Martinez)
|
||||||
|
|
||||||
|
|
||||||
|
2015-02-02, Version 2.6.4
|
||||||
|
=========================
|
||||||
|
|
||||||
|
* executor: pass correct `this` to middleware (Clark Wang)
|
||||||
|
|
||||||
|
* Fix broken links (Rand McKinney)
|
||||||
|
|
||||||
|
|
||||||
|
2015-01-13, Version 2.6.3
|
||||||
|
=========================
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
2015-01-13, Version 2.6.2
|
||||||
|
=========================
|
||||||
|
|
||||||
|
* Don't swallow error when a sub-dependency doesn't resolve. (Samuel Reed)
|
||||||
|
|
||||||
|
|
||||||
|
2015-01-12, Version 2.6.1
|
||||||
|
=========================
|
||||||
|
|
||||||
|
* Fix "incompatible loopback version" check & msg (Miroslav Bajtoš)
|
||||||
|
|
||||||
|
|
||||||
|
2015-01-08, Version 2.6.0
|
||||||
|
=========================
|
||||||
|
|
||||||
|
* Add "booting" flag and emit "booted" event (Simon Ho)
|
||||||
|
|
||||||
|
* Configure components via `component-config.json` (Miroslav Bajtoš)
|
||||||
|
|
||||||
|
* Fix bad CLA URL in CONTRIBUTING.md (Ryan Graham)
|
||||||
|
|
||||||
|
|
||||||
|
2014-12-19, Version 2.5.2
|
||||||
|
=========================
|
||||||
|
|
||||||
|
* Dedupe boot scripts (Eric Satterwhite)
|
||||||
|
|
||||||
|
|
||||||
|
2014-12-08, Version 2.5.1
|
||||||
|
=========================
|
||||||
|
|
||||||
|
* Replace underscore with lodash (Ryan Graham)
|
||||||
|
|
||||||
|
|
||||||
|
2014-12-02, Version 2.5.0
|
||||||
|
=========================
|
||||||
|
|
||||||
|
* compiler: resolve paths in middleware params (Miroslav Bajtoš)
|
||||||
|
|
||||||
|
|
||||||
|
2014-11-27, Version 2.4.0
|
||||||
|
=========================
|
||||||
|
|
||||||
|
* Implement shorthand notation for middleware paths (Raymond Feng)
|
||||||
|
|
||||||
|
* Load middleware and phases from `middleware.json` (Miroslav Bajtoš)
|
||||||
|
|
||||||
|
* Add jscs style check, fix violations found (Miroslav Bajtoš)
|
||||||
|
|
||||||
|
* Clean up .jshintrc (Miroslav Bajtoš)
|
||||||
|
|
||||||
|
* Use `chai` instead of `must` (Miroslav Bajtoš)
|
||||||
|
|
||||||
|
|
||||||
|
2014-11-10, Version 2.3.1
|
||||||
|
=========================
|
||||||
|
|
||||||
|
* Bump version (Raymond Feng)
|
||||||
|
|
||||||
|
* Fix the test for built-in models on Windows (Raymond Feng)
|
||||||
|
|
||||||
|
* Fix jsdoc (Raymond Feng)
|
||||||
|
|
||||||
|
|
||||||
|
2014-10-27, Version 2.3.0
|
||||||
|
=========================
|
||||||
|
|
||||||
|
* compiler: fix coding style violations (Miroslav Bajtoš)
|
||||||
|
|
||||||
|
* support coffee-script models and client code (bitmage)
|
||||||
|
|
||||||
|
|
||||||
|
2014-10-22, Version 2.2.0
|
||||||
|
=========================
|
||||||
|
|
||||||
|
* compiler: support module-relative model sources (Miroslav Bajtoš)
|
||||||
|
|
||||||
|
* Skip definitions of built-in loopback models (Miroslav Bajtoš)
|
||||||
|
|
||||||
|
* package: update dependency versions (Miroslav Bajtoš)
|
||||||
|
|
||||||
|
* Use loopback 2.x in unit tests. (Miroslav Bajtoš)
|
||||||
|
|
||||||
|
|
||||||
|
2014-10-09, Version 2.1.0
|
||||||
|
=========================
|
||||||
|
|
||||||
|
* Bump version (Raymond Feng)
|
||||||
|
|
||||||
|
* Add support for async boot scripts (Raymond Feng)
|
||||||
|
|
||||||
|
* Clean up jsdoc comments. (Miroslav Bajtoš)
|
||||||
|
|
||||||
|
* Custom rootDir for app config (johnsoftek)
|
||||||
|
|
||||||
|
* compiler: improve merging of Arrays and Objects (Miroslav Bajtoš)
|
||||||
|
|
||||||
|
* config-loader: deeply merge Array and Object vals (Shelby Sanders)
|
||||||
|
|
||||||
|
* gitignore: add Idea's *.iml files (Miroslav Bajtoš)
|
||||||
|
|
||||||
|
* package: Add `jshint` to `devDependencies` (Miroslav Bajtoš)
|
||||||
|
|
||||||
|
* Update contribution guidelines (Ryan Graham)
|
||||||
|
|
||||||
|
* test: ensure sandbox dir is present (Miroslav Bajtoš)
|
||||||
|
|
||||||
|
* test: add `global.navigator` for browser tests (Miroslav Bajtoš)
|
||||||
|
|
||||||
|
* test: increase timeout for browserify (Miroslav Bajtoš)
|
||||||
|
|
||||||
|
* index: fix jshint error (Miroslav Bajtoš)
|
||||||
|
|
||||||
|
* documentation fix (Alex)
|
||||||
|
|
||||||
|
* Fix typo (Fabien Franzen)
|
||||||
|
|
||||||
|
* Implemented modelSources, bootDirs and bootScripts options (Fabien Franzen)
|
||||||
|
|
||||||
|
|
||||||
|
2014-07-22, Version 2.0.0
|
||||||
|
=========================
|
||||||
|
|
||||||
|
* executor: remove `Base` arg from model function (Miroslav Bajtoš)
|
||||||
|
|
||||||
|
* package: update dependency versions (Miroslav Bajtoš)
|
||||||
|
|
||||||
|
|
||||||
|
2014-07-17, Version v2.0.0-beta3
|
||||||
|
================================
|
||||||
|
|
||||||
|
* compiler: return a clone of instructions (Miroslav Bajtoš)
|
||||||
|
|
||||||
|
|
||||||
|
2014-07-17, Version 2.0.0-beta2
|
||||||
|
===============================
|
||||||
|
|
||||||
|
* test: export Int32Array and DataView for browser (Miroslav Bajtoš)
|
||||||
|
|
||||||
|
* v2.0.0-beta2 (Miroslav Bajtoš)
|
||||||
|
|
||||||
|
* Rename `models.json` to `model-config.json` (Miroslav Bajtoš)
|
||||||
|
|
||||||
|
* Remove non-API docs. (Rand McKinney)
|
||||||
|
|
||||||
|
|
||||||
|
2014-06-26, Version 2.0.0-beta1
|
||||||
|
===============================
|
||||||
|
|
||||||
|
* test: fix jshint warnings (Miroslav Bajtoš)
|
||||||
|
|
||||||
|
* compiler: fix references to loopback (Miroslav Bajtoš)
|
||||||
|
|
||||||
|
* Rename `app.json` to `config.json` (Miroslav Bajtoš)
|
||||||
|
|
||||||
|
* compiler: Sort models topologically (Miroslav Bajtoš)
|
||||||
|
|
||||||
|
* executor: Split model boot into two phases (Miroslav Bajtoš)
|
||||||
|
|
||||||
|
* compiler: Move model-sources cfg to models.json (Miroslav Bajtoš)
|
||||||
|
|
||||||
|
* package: Bump up the version to 2.0.0-dev (Miroslav Bajtoš)
|
||||||
|
|
||||||
|
* Rework model configuration (Miroslav Bajtoš)
|
||||||
|
|
||||||
|
* Remove auto-attach. (Miroslav Bajtoš)
|
||||||
|
|
||||||
|
* Change models.json to configure existing models (Miroslav Bajtoš)
|
||||||
|
|
||||||
|
|
||||||
|
2014-06-26, Version 1.1.0
|
||||||
|
=========================
|
||||||
|
|
||||||
|
* docs: move hand-written content to README.md (Miroslav Bajtoš)
|
||||||
|
|
||||||
|
* executor: remove direct reference to loopback (Miroslav Bajtoš)
|
||||||
|
|
||||||
|
* Update link to doc (Rand McKinney)
|
||||||
|
|
||||||
|
* package: Fix repository url (Miroslav Bajtoš)
|
||||||
|
|
||||||
|
* Drop peer dep on loopback; add a runtime check (Miroslav Bajtoš)
|
||||||
|
|
||||||
|
* Wrap too long lines (Miroslav Bajtoš)
|
||||||
|
|
||||||
|
* Add disclaimer to JSDoc and small correction. (crandmck)
|
||||||
|
|
||||||
|
|
||||||
|
2014-06-05, Version 1.0.0
|
||||||
|
=========================
|
||||||
|
|
||||||
|
* First release!
|
||||||
|
|
|
@ -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
|
|
@ -1,65 +1,24 @@
|
||||||
|
|
||||||
### Contributing ###
|
### Contributing ###
|
||||||
|
|
||||||
Thank you for your interest in `loopback`, an open source project
|
Thank you for your interest in `loopback-boot`, an open source project
|
||||||
administered by StrongLoop.
|
administered by StrongLoop.
|
||||||
|
|
||||||
Contributing to loopback is easy. In a few simple steps:
|
Contributing to `loopback-boot` is easy. In a few simple steps:
|
||||||
|
|
||||||
* Ensure that your effort is aligned with the project’s roadmap by
|
* Ensure that your effort is aligned with the project's roadmap by
|
||||||
talking to the maintainers, especially if you are going to spend a
|
talking to the maintainers, especially if you are going to spend a
|
||||||
lot of time on it. This project is currently maintained by
|
lot of time on it.
|
||||||
[@ritch](https://github.com/ritch), [@raymondfeng](https://github.com/raymondfeng),
|
|
||||||
and [@bajtos](https://github.com/bajtos). The preferred channel of communication
|
|
||||||
is [LoopBack Forum](https://groups.google.com/forum/#!forum/loopbackjs) or
|
|
||||||
[Github Issues](https://github.com/strongloop/loopback/issues).
|
|
||||||
|
|
||||||
* Make something better or fix a bug.
|
* Make something better or fix a bug.
|
||||||
|
|
||||||
* Adhere to code style outlined in the
|
* Adhere to code style outlined in the [Google C++ Style Guide][] and
|
||||||
[Google Javascript Style Guide][].
|
[Google Javascript Style Guide][].
|
||||||
|
|
||||||
* [Sign your patches](#signing-patches) to indicate that your are
|
* Sign the [Contributor License Agreement](https://cla.strongloop.com/agreements/strongloop/loopback-boot)
|
||||||
making your contribution available under the terms of the
|
|
||||||
[Contributor License Agreement](#contributor-license-agreement).
|
|
||||||
|
|
||||||
* Submit a pull request through Github.
|
* Submit a pull request through Github.
|
||||||
|
|
||||||
|
|
||||||
### Signing patches ###
|
|
||||||
|
|
||||||
Like many open source projects, we need a contributor license agreement
|
|
||||||
from you before we can merge in your changes.
|
|
||||||
|
|
||||||
In summary, by submitting your code, you are granting us a right to use
|
|
||||||
that code under the terms of this Agreement, including providing it to
|
|
||||||
others. You are also certifying that you wrote it, and that you are
|
|
||||||
allowed to license it to us. You are not giving up your copyright in
|
|
||||||
your work. The license does not change your rights to use your own
|
|
||||||
contributions for any other purpose.
|
|
||||||
|
|
||||||
Contributor License Agreements are important because they define the
|
|
||||||
chain of ownership of a piece of software. Some companies won't allow
|
|
||||||
the use of free software without clear agreements around code ownership.
|
|
||||||
That's why many open source projects collect similar agreements from
|
|
||||||
contributors. The CLA here is based on the Apache CLA.
|
|
||||||
|
|
||||||
To signify your agreement to these terms, add the following line to the
|
|
||||||
bottom of your commit message. Use your real name and an actual e-mail
|
|
||||||
address.
|
|
||||||
|
|
||||||
```
|
|
||||||
Signed-off-by: Random J Developer <random@developer.example.org>
|
|
||||||
```
|
|
||||||
|
|
||||||
Alternatively you can use the git command line to automatically add this
|
|
||||||
line, as follows:
|
|
||||||
|
|
||||||
```
|
|
||||||
$ git commit -sm "Replace rainbows by unicorns"
|
|
||||||
```
|
|
||||||
|
|
||||||
|
|
||||||
### Contributor License Agreement ###
|
### Contributor License Agreement ###
|
||||||
|
|
||||||
```
|
```
|
||||||
|
@ -188,7 +147,5 @@ $ git commit -sm "Replace rainbows by unicorns"
|
||||||
inaccurate in any respect. Email us at callback@strongloop.com.
|
inaccurate in any respect. Email us at callback@strongloop.com.
|
||||||
```
|
```
|
||||||
|
|
||||||
|
[Google C++ Style Guide]: https://google.github.io/styleguide/cppguide.html
|
||||||
[Google Javascript Style Guide]: https://google-styleguide.googlecode.com/svn/trunk/javascriptguide.xml
|
[Google Javascript Style Guide]: https://google.github.io/styleguide/javascriptguide.xml
|
||||||
[license]: LICENSE
|
|
||||||
|
|
||||||
|
|
299
LICENSE
299
LICENSE
|
@ -1,8 +1,8 @@
|
||||||
Copyright (c) 2013-2014 StrongLoop, Inc and other contributors.
|
Copyright (c) IBM Corp. 2014,2017. All Rights Reserved.
|
||||||
|
Node module: loopback-boot
|
||||||
|
This project is licensed under the MIT License, full text below.
|
||||||
|
|
||||||
loopback uses a 'dual license' model. Users may use loopback under the terms of
|
--------
|
||||||
the MIT license, or under the StrongLoop License. The text of both is included
|
|
||||||
below.
|
|
||||||
|
|
||||||
MIT license
|
MIT license
|
||||||
|
|
||||||
|
@ -23,294 +23,3 @@ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||||
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||||
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
||||||
THE SOFTWARE.
|
THE SOFTWARE.
|
||||||
|
|
||||||
StrongLoop License
|
|
||||||
|
|
||||||
You may obtain a copy of the License at
|
|
||||||
|
|
||||||
http://www.strongloop.com/license/
|
|
||||||
|
|
||||||
STRONGLOOP SUBSCRIPTION AGREEMENT
|
|
||||||
|
|
||||||
PLEASE READ THIS AGREEMENT CAREFULLY BEFORE YOU AGREE TO THESE TERMS. IF YOU
|
|
||||||
ARE ACTING ON BEHALF OF AN ENTITY, THEN YOU REPRESENT THAT YOU HAVE THE
|
|
||||||
AUTHORITY TO ENTER INTO THIS AGREEMENT ON BEHALF OF THAT ENTITY. IF YOU DO NOT
|
|
||||||
AGREE TO THESE TERMS, YOU SHOULD NOT AGREE TO THE TERMS OF THIS AGREEMENT OR
|
|
||||||
INSTALL OR USE THE SOFTWARE.
|
|
||||||
This StrongLoop Subscription Agreement ("Agreement") is made by and between
|
|
||||||
StrongLoop, Inc. ("StrongLoop") with its principal place of business at 107 S.
|
|
||||||
B St, Suite 220, San Mateo, CA 94401 and the person or entity entering into this
|
|
||||||
Agreement ("Customer"). The effective date ("Effective Date") of this Agreement
|
|
||||||
is the date Customer agrees to these terms or installs or uses the Software (as
|
|
||||||
defined below). This Agreement applies to Customer's use of the Software but it
|
|
||||||
shall be superseded by any signed agreement between you and StrongLoop
|
|
||||||
concerning the Software.
|
|
||||||
1. Subscriptions and Licenses.
|
|
||||||
1.1 Subscriptions. StrongLoop offers five different subscription levels to its
|
|
||||||
customers, each as more particularly described on StrongLoop's website located
|
|
||||||
at www.strongloop.com (the "StrongLoop Site"): (1) Free; (2) Developer; (3)
|
|
||||||
Professional; (4) Gold; and (5) Platinum. The actual subscription level
|
|
||||||
applicable to Customer (the "Subscription") will be specified in the purchase
|
|
||||||
order that Customer issues to StrongLoop. This Agreement applies to Customer
|
|
||||||
regardless of the level of the Subscription selected by Customer and whether or
|
|
||||||
not Customer upgrades or downgrades its Subscription. StrongLoop hereby agrees
|
|
||||||
to provide the services as described on the StrongLoop Site for each
|
|
||||||
Subscription level during the term for which Customer has purchased the
|
|
||||||
applicable Subscription, subject to Customer paying the fees applicable to the
|
|
||||||
Subscription level purchased, if any (the "Subscription Fees"). StrongLoop may
|
|
||||||
modify the services to be provided under any Subscription upon notice to
|
|
||||||
Customer.
|
|
||||||
1.2 License Grant. Subject to the terms and conditions of this Agreement,
|
|
||||||
StrongLoop grants to Customer, during the Subscription Term (as defined in
|
|
||||||
Section 7.1 (Term and Termination) of this Agreement, a limited, non-exclusive,
|
|
||||||
non-transferable right and license, to install and use the StrongLoop Suite
|
|
||||||
software (the "Software") and the documentation made available electronically as
|
|
||||||
part of the Software (the "Documentation"), either of which may be modified
|
|
||||||
during the Term (as defined in Section 7.1 below), solely for development,
|
|
||||||
production and commercial purposes so long as Customer is using the Software to
|
|
||||||
run only one process on a given operating system at a time. This Agreement,
|
|
||||||
including but not limited to the license and restrictions contained herein,
|
|
||||||
apply to Customer regardless of whether Customer accesses the Software via
|
|
||||||
download from the StrongLoop Site or through a third-party website or service,
|
|
||||||
even if Customer acquired the Software prior to agreeing to this Agreement.
|
|
||||||
1.3 License Restrictions. Customer shall not itself, or through any parent,
|
|
||||||
subsidiary, affiliate, agent or other third party:
|
|
||||||
1.3.1 sell, lease, license, distribute, sublicense or otherwise transfer
|
|
||||||
in whole or in part, any Software or the Documentation to a third party;
|
|
||||||
or
|
|
||||||
1.3.2 decompile, disassemble, translate, reverse engineer or otherwise
|
|
||||||
attempt to derive source code from the Software, in whole or in part, nor
|
|
||||||
shall Customer use any mechanical, electronic or other method to trace,
|
|
||||||
decompile, disassemble, or identify the source code of the Software or
|
|
||||||
encourage others to do so, except to the limited extent, if any, that
|
|
||||||
applicable law permits such acts notwithstanding any contractual
|
|
||||||
prohibitions, provided, however, before Customer exercises any rights that
|
|
||||||
Customer believes to be entitled to based on mandatory law, Customer shall
|
|
||||||
provide StrongLoop with thirty (30) days prior written notice and provide
|
|
||||||
all reasonably requested information to allow StrongLoop to assess
|
|
||||||
Customer's claim and, at StrongLoop's sole discretion, to provide
|
|
||||||
alternatives that reduce any adverse impact on StrongLoop's intellectual
|
|
||||||
property or other rights; or
|
|
||||||
1.3.3 allow access or permit use of the Software by any users other than
|
|
||||||
Customer's employees or authorized third-party contractors who are
|
|
||||||
providing services to Customer and agree in writing to abide by the terms
|
|
||||||
of this Agreement, provided further that Customer shall be liable for any
|
|
||||||
failure by such employees and third-party contractors to comply with the
|
|
||||||
terms of this Agreement and no usage restrictions, if any, shall be
|
|
||||||
exceeded; or
|
|
||||||
1.3.4 create, develop, license, install, use, or deploy any third party
|
|
||||||
software or services to circumvent or provide access, permissions or
|
|
||||||
rights which violate the license keys embedded within the Software; or
|
|
||||||
1.3.5 modify or create derivative works based upon the Software or
|
|
||||||
Documentation; or disclose the results of any benchmark test of the
|
|
||||||
Software to any third party without StrongLoop's prior written approval;
|
|
||||||
or
|
|
||||||
1.3.6 change any proprietary rights notices which appear in the Software
|
|
||||||
or Documentation; or
|
|
||||||
1.3.7 use the Software as part of a time sharing or service bureau
|
|
||||||
purposes or in any other resale capacity.
|
|
||||||
1.4 Third-Party Software. The Software may include individual certain software
|
|
||||||
that is owned by third parties, including individual open source software
|
|
||||||
components (the "Third-Party Software"), each of which has its own copyright and
|
|
||||||
its own applicable license conditions. Such third-party software is licensed to
|
|
||||||
Customer under the terms of the applicable third-party licenses and/or copyright
|
|
||||||
notices that can be found in the LICENSES file, the Documentation or other
|
|
||||||
materials accompanying the Software, except that Sections 5 (Warranty
|
|
||||||
Disclaimer) and 6 (Limitation of Liability) also govern Customer's use of the
|
|
||||||
third-party software. Customer agrees to comply with the terms and conditions
|
|
||||||
of the relevant third-party software licenses.
|
|
||||||
2. Support Services. StrongLoop has no obligation to provide any support for
|
|
||||||
the Software other than the support services specifically described on the
|
|
||||||
StrongLoop Site for the Subscription level procured by Customer. However,
|
|
||||||
StrongLoop has endeavored to establish a community of users of the Software who
|
|
||||||
have provided their own feedback, hints and advice regarding their experiences
|
|
||||||
in using the Software. You can find that community and user feedback on the
|
|
||||||
StrongLoop Site. The use of any information, content or other materials from,
|
|
||||||
contained in or on the StrongLoop Site are subject to the StrongLoop website
|
|
||||||
terms of use located here http://www.strongloop.com/terms-of-service.
|
|
||||||
3. Confidentiality. For purposes of this Agreement, "Confidential Information"
|
|
||||||
means any and all information or proprietary materials (in every form and media)
|
|
||||||
not generally known in the relevant trade or industry and which has been or is
|
|
||||||
hereafter disclosed or made available by StrongLoop to Customer in connection
|
|
||||||
with the transactions contemplated under this Agreement, including (i) all trade
|
|
||||||
secrets, (ii) existing or contemplated Software, services, designs, technology,
|
|
||||||
processes, technical data, engineering, techniques, methodologies and concepts
|
|
||||||
and any related information, and (iii) information relating to business plans,
|
|
||||||
sales or marketing methods and customer lists or requirements. For a period of
|
|
||||||
five (5) years from the date of disclosure of the applicable Confidential
|
|
||||||
Information, Customer shall (i) hold the Confidential Information in trust and
|
|
||||||
confidence and avoid the disclosure or release thereof to any other person or
|
|
||||||
entity by using the same degree of care as it uses to avoid unauthorized use,
|
|
||||||
disclosure, or dissemination of its own Confidential Information of a similar
|
|
||||||
nature, but not less than reasonable care, and (ii) not use the Confidential
|
|
||||||
Information for any purpose whatsoever except as expressly contemplated under
|
|
||||||
this Agreement; provided that, to the extent the Confidential Information
|
|
||||||
constitutes a trade secret under law, Customer agrees to protect such
|
|
||||||
information for so long as it qualifies as a trade secret under applicable law.
|
|
||||||
Customer shall disclose the Confidential Information only to those of its
|
|
||||||
employees and contractors having a need to know such Confidential Information
|
|
||||||
and shall take all reasonable precautions to ensure that such employees and
|
|
||||||
contractors comply with the provisions of this Section. The obligations of
|
|
||||||
Customer under this Section shall not apply to information that Customer can
|
|
||||||
demonstrate (i) was in its possession at the time of disclosure and without
|
|
||||||
restriction as to confidentiality, (ii) at the time of disclosure is generally
|
|
||||||
available to the public or after disclosure becomes generally available to the
|
|
||||||
public through no breach of agreement or other wrongful act by Customer, (iii)
|
|
||||||
has been received from a third party without restriction on disclosure and
|
|
||||||
without breach of agreement by Customer, or (iv) is independently developed by
|
|
||||||
Customer without regard to the Confidential Information. In addition, Customer
|
|
||||||
may disclose Confidential Information as required to comply with binding orders
|
|
||||||
of governmental entities that have jurisdiction over it; provided that Customer
|
|
||||||
gives StrongLoop reasonable written notice to allow StrongLoop to seek a
|
|
||||||
protective order or other appropriate remedy, discloses only such Confidential
|
|
||||||
Information as is required by the governmental entity, and uses commercially
|
|
||||||
reasonable efforts to obtain confidential treatment for any Confidential
|
|
||||||
Information disclosed. Notwithstanding the above, Customer agrees that
|
|
||||||
StrongLoop, its employees and agents shall be free to use and employ their
|
|
||||||
general skills, know-how, and expertise, and to use, disclose, and employ any
|
|
||||||
generalized ideas, concepts, know-how, methods, techniques or skills gained or
|
|
||||||
learned during the Term or thereafter.
|
|
||||||
4. Ownership. StrongLoop shall retain all intellectual property and proprietary
|
|
||||||
rights in the Software, Documentation, and related works, including but not
|
|
||||||
limited to any derivative work of the foregoing and StrongLoop's licensors shall
|
|
||||||
retain all intellectual property and proprietary rights in any Third-Party
|
|
||||||
Software that may be provided with or as a part of the Software. Customer shall
|
|
||||||
do nothing inconsistent with StrongLoop's or its licensors' title to the
|
|
||||||
Software and the intellectual property rights embodied therein, including, but
|
|
||||||
not limited to, transferring, loaning, selling, assigning, pledging, or
|
|
||||||
otherwise disposing, encumbering, or suffering a lien or encumbrance upon or
|
|
||||||
against any interest in the Software. The Software (including any Third-Party
|
|
||||||
Software) contain copyrighted material, trade secrets and other proprietary
|
|
||||||
material of StrongLoop and/or its licensors.
|
|
||||||
5. Warranty Disclaimer. THE SOFTWARE (INCLUDING ANY THIRD-PARTY SOFTWARE) AND
|
|
||||||
DOCUMENTATION MADE AVAILABLE TO CUSTOMER ARE PROVIDED "AS-IS" AND STRONGLOOP,
|
|
||||||
ON BEHALF OF ITSELF AND ITS LICENSORS, EXPRESSLY DISCLAIMS ALL WARRANTIES OF ANY
|
|
||||||
KIND, EXPRESS OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, ANY IMPLIED WARRANTIES
|
|
||||||
OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE, NON-INFRINGEMENT, TITLE,
|
|
||||||
PERFORMANCE, AND ACCURACY AND ANY IMPLIED WARRANTIES ARISING FROM STATUTE,
|
|
||||||
COURSE OF DEALING, COURSE OF PERFORMANCE, OR USAGE OF TRADE. STRONGLOOP DOES
|
|
||||||
NOT WARRANT THAT THE OPERATION OF THE SOFTWARE WILL BE UNINTERRUPTED OR
|
|
||||||
ERROR-FREE, THAT DEFECTS IN THE SOFTWARE WILL BE CORRECTED OR THAT THE SOFTWARE
|
|
||||||
WILL PROVIDE OR ENSURE ANY PARTICULAR RESULTS OR OUTCOME. NO ORAL OR WRITTEN
|
|
||||||
INFORMATION OR ADVICE GIVEN BY STRONGLOOP OR ITS AUTHORIZED REPRESENTATIVES
|
|
||||||
SHALL CREATE A WARRANTY OR IN ANY WAY INCREASE THE SCOPE OF THIS WARRANTY.
|
|
||||||
STRONGLOOP IS NOT OBLIGATED TO PROVIDE CUSTOMER WITH UPGRADES TO THE SOFTWARE,
|
|
||||||
BUT MAY ELECT TO DO SO IN ITS SOLE DISCRETION. SOME JURISDICTIONS DO NOT ALLOW
|
|
||||||
THE EXCLUSION OF IMPLIED WARRANTIES, SO THE ABOVE EXCLUSION MAY NOT APPLY TO
|
|
||||||
CUSTOMER.WITHOUT LIMITING THE GENERALITY OF THE FOREGOING DISCLAIMER, THE
|
|
||||||
SOFTWARE AND DOCUMENTATION ARE NOT DESIGNED, MANUFACTURED OR INTENDED FOR USE IN
|
|
||||||
THE PLANNING, CONSTRUCTION, MAINTENANCE, CONTROL, OR DIRECT OPERATION OF NUCLEAR
|
|
||||||
FACILITIES, AIRCRAFT NAVIGATION, CONTROL OR COMMUNICATION SYSTEMS, WEAPONS
|
|
||||||
SYSTEMS, OR DIRECT LIFE SUPPORT SYSTEMS.
|
|
||||||
6. Limitation of Liability.
|
|
||||||
6.1 Exclusion of Liability. IN NO EVENT WILL STRONGLOOP OR ITS LICENSORS
|
|
||||||
BE LIABLE UNDER THIS AGREEMENT FOR ANY INDIRECT, RELIANCE, PUNITIVE,
|
|
||||||
CONSEQUENTIAL, SPECIAL, EXEMPLARY, OR INCIDENTAL DAMAGES OF ANY KIND AND
|
|
||||||
HOWEVER CAUSED (INCLUDING, WITHOUT LIMITATION, DAMAGES FOR LOSS OF
|
|
||||||
BUSINESS PROFITS, BUSINESS INTERRUPTION, LOSS OF BUSINESS INFORMATION AND
|
|
||||||
THE LIKE), EVEN IF STRONGLOOP HAS BEEN ADVISED OF THE POSSIBILITY OF SUCH
|
|
||||||
DAMAGES. CUSTOMER BEARS FULL RESPONSIBILITY FOR USE OF THE SOFTWARE AND
|
|
||||||
THE SUBSCRIPTION AND STRONGLOOP DOES NOT GUARANTEE THAT THE USE OF THE
|
|
||||||
SOFTWARE AND SUBSCRIPTION WILL ENSURE THAT CUSTOMER'S NETWORK WILL BE
|
|
||||||
AVAILABLE, SECURE, MONITORED OR PROTECTED AGAINST ANY DOWNTIME, DENIAL OF
|
|
||||||
SERVICE ATTACKS, SECUITY BREACHES, HACKERS AND THE LIKE. IN NO EVENT WILL
|
|
||||||
STRONGLOOP'S CUMULATIVE LIABILITY FOR ANY DAMAGES, LOSSES AND CAUSES OF
|
|
||||||
ACTION (WHETHER IN CONTRACT, TORT, INCLUDING NEGLIGENCE, OR OTHERWISE)
|
|
||||||
ARISING OUT OF OR RELATED TO THIS AGREEMENT EXCEED THE GREATER OF ONE
|
|
||||||
HUNDRED DOLLARS (US$100) OR THE TOTAL SUBSCRIPTION FEES PAID BY CUSTOMER
|
|
||||||
TO STRONGLOOP IN THE TWELVE (12) MONTHS PRECEDING THE DATE THE CLAIM
|
|
||||||
ARISES.
|
|
||||||
6.2 Limitation of Damages. IN NO EVENT WILL STRONGLOOP'S LICENSORS HAVE
|
|
||||||
ANY LIABILITY FOR ANY CLAIM ARISING IN CONNECTION WITH THIS AGREEMENT.
|
|
||||||
THE PROVISIONS OF THIS SECTION 6 ALLOCATE RISKS UNDER THIS AGREEMENT
|
|
||||||
BETWEEN CUSTOMER, STRONGLOOP AND STRONGLOOP'S SUPPLIERS. THE FOREGOING
|
|
||||||
LIMITATIONS, EXCLUSIONS AND DISCLAIMERS APPLY TO THE MAXIMUM EXTENT
|
|
||||||
PERMITTED BY APPLICABLE LAW, EVEN IF ANY REMEDY FAILS IN ITS ESSENTIAL
|
|
||||||
PURPOSE.
|
|
||||||
6.3 Failure of Essential Purpose. THE PARTIES AGREE THAT THESE
|
|
||||||
LIMITATIONS SHALL APPLY EVEN IF THIS AGREEMENT OR ANY LIMITED REMEDY
|
|
||||||
SPECIFIED HEREIN IS FOUND TO HAVE FAILED OF ITS ESSENTIAL PURPOSE.
|
|
||||||
6.4 Allocation of Risk. The sections on limitation of liability and
|
|
||||||
disclaimer of warranties allocate the risks in the Agreement between the
|
|
||||||
parties. This allocation is an essential element of the basis of the
|
|
||||||
bargain between the parties.
|
|
||||||
7. Term and Termination.
|
|
||||||
7.1 This Agreement shall commence on the Effective Date and continue for so long
|
|
||||||
as Customer has a valid Subscription and is current on the payment of any
|
|
||||||
Subscription Fees required to be paid for that Subscription (the "Subscription
|
|
||||||
Term"). Either party may terminate this Agreement immediately upon written
|
|
||||||
notice to the other party, and the Subscription and licenses granted hereunder
|
|
||||||
automatically terminate upon the termination of this Agreement. This Agreement
|
|
||||||
will terminate immediately without notice from StrongLoop if Customer fails to
|
|
||||||
comply with or otherwise breaches any provision of this Agreement.
|
|
||||||
7.2 All Sections other than Section 1.1 (Subscriptions) and 1.2 (Licenses) shall
|
|
||||||
survive the expiration or termination of this Agreement.
|
|
||||||
8. Subscription Fees and Payments. StrongLoop, Customer agrees to pay
|
|
||||||
StrongLoop the Subscription Fees as described on the StrongLoop Site for the
|
|
||||||
Subscription purchased unless a different amount has been agreed to in a
|
|
||||||
separate agreement between Customer and StrongLoop. In addition, Customer shall
|
|
||||||
pay all sales, use, value added, withholding, excise taxes and other tax, duty,
|
|
||||||
custom and similar fees levied upon the delivery or use of the Software and the
|
|
||||||
Subscriptions described in this Agreement. Fees shall be invoiced in full upon
|
|
||||||
StrongLoop's acceptance of Customer's purchase order for the Subscription. All
|
|
||||||
invoices shall be paid in US dollars and are due upon receipt and shall be paid
|
|
||||||
within thirty (30) days. Payments shall be made without right of set-off or
|
|
||||||
chargeback. If Customer does not pay the invoices when due, StrongLoop may
|
|
||||||
charge interest at one percent (1%) per month or the highest rate permitted by
|
|
||||||
law, whichever is lower, on the unpaid balance from the original due date. If
|
|
||||||
Customer fails to pay fees in accordance with this Section, StrongLoop may
|
|
||||||
suspend fulfilling its obligations under this Agreement (including but not
|
|
||||||
limited to suspending the services under the Subscription) until payment is
|
|
||||||
received by StrongLoop. If any applicable law requires Customer to withhold
|
|
||||||
amounts from any payments to StrongLoop under this Agreement, (a) Customer shall
|
|
||||||
effect such withholding, remit such amounts to the appropriate taxing
|
|
||||||
authorities and promptly furnish StrongLoop with tax receipts evidencing the
|
|
||||||
payments of such amounts and (b) the sum payable by Customer upon which the
|
|
||||||
deduction or withholding is based shall be increased to the extent necessary to
|
|
||||||
ensure that, after such deduction or withholding, StrongLoop receives and
|
|
||||||
retains, free from liability for such deduction or withholding, a net amount
|
|
||||||
equal to the amount StrongLoop would have received and retained absent the
|
|
||||||
required deduction or withholding.
|
|
||||||
9. General.
|
|
||||||
9.1 Compliance with Laws. Customer shall abide by all local, state, federal and
|
|
||||||
international laws, rules, regulations and orders applying to Customer's use of
|
|
||||||
the Software, including, without limitation, the laws and regulations of the
|
|
||||||
United States that may restrict the export and re-export of certain commodities
|
|
||||||
and technical data of United States origin, including the Software. Customer
|
|
||||||
agrees that it will not export or re-export the Software without the appropriate
|
|
||||||
United States or foreign government licenses.
|
|
||||||
9.2 Entire Agreement. This Agreement constitutes the entire agreement between
|
|
||||||
the parties concerning the subject matter hereof. This Agreement supersedes all
|
|
||||||
prior or contemporaneous discussions, proposals and agreements between the
|
|
||||||
parties relating to the subject matter hereof. No amendment, modification or
|
|
||||||
waiver of any provision of this Agreement shall be effective unless in writing
|
|
||||||
and signed by both parties. Any additional or different terms on any purchase
|
|
||||||
orders issued by Customer to StrongLoop shall not be binding on either party,
|
|
||||||
are hereby rejected by StrongLoop and void.
|
|
||||||
9.3 Severability. If any provision of this Agreement is held to be invalid or
|
|
||||||
unenforceable, the remaining portions shall remain in full force and effect and
|
|
||||||
such provision shall be enforced to the maximum extent possible so as to effect
|
|
||||||
the intent of the parties and shall be reformed to the extent necessary to make
|
|
||||||
such provision valid and enforceable.
|
|
||||||
9.4 Waiver. No waiver of rights by either party may be implied from any actions
|
|
||||||
or failures to enforce rights under this Agreement.
|
|
||||||
9.5 Force Majeure. Neither party shall be liable to the other for any delay or
|
|
||||||
failure to perform due to causes beyond its reasonable control (excluding
|
|
||||||
payment of monies due).
|
|
||||||
9.6 No Third Party Beneficiaries. Unless otherwise specifically stated, the
|
|
||||||
terms of this Agreement are intended to be and are solely for the benefit of
|
|
||||||
StrongLoop and Customer and do not create any right in favor of any third party.
|
|
||||||
9.7 Governing Law and Jurisdiction. This Agreement shall be governed by the
|
|
||||||
laws of the State of California, without reference to the principles of
|
|
||||||
conflicts of law. The provisions of the Uniform Computerized Information
|
|
||||||
Transaction Act and United Nations Convention on Contracts for the International
|
|
||||||
Sale of Goods shall not apply to this Agreement. The parties shall attempt to
|
|
||||||
resolve any dispute related to this Agreement informally, initially through
|
|
||||||
their respective management, and then by non-binding mediation in San Francisco
|
|
||||||
County, California. Any litigation related to this Agreement shall be brought
|
|
||||||
in the state or federal courts located in San Francisco County, California, and
|
|
||||||
only in those courts and each party irrevocably waives any objections to such
|
|
||||||
venue.
|
|
||||||
9.8 Notices. All notices must be in writing and shall be effective three (3)
|
|
||||||
days after the date sent to the other party's headquarters, Attention Chief
|
|
||||||
Financial Officer.
|
|
||||||
|
|
53
README.md
53
README.md
|
@ -1,10 +1,40 @@
|
||||||
# LoopBack Boot
|
# LoopBack Boot
|
||||||
|
|
||||||
LoopBack Boot is a convention-based bootstrapper for LoopBack applications.
|
**⚠️ 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.)**
|
||||||
|
|
||||||
**For full documentation, see the official StrongLoop documentation:**
|
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.
|
||||||
|
|
||||||
* [Creating a LoopBack application](http://docs.strongloop.com/display/DOC/Creating+a+LoopBack+application)
|
## Overview
|
||||||
|
|
||||||
|
A convention-based bootstrapper for LoopBack applications.
|
||||||
|
|
||||||
|
For full documentation, see the official StrongLoop documentation: [Defining boot scripts](https://loopback.io/doc/en/lb2/Defining-boot-scripts) and [Creating a LoopBack application](https://loopback.io/doc/en/lb2/Creating-an-application).
|
||||||
|
|
||||||
|
The loopback-boot module initializes (bootstraps) a LoopBack application. Specifically, it:
|
||||||
|
- Configures data-sources.
|
||||||
|
- Defines custom models
|
||||||
|
- Configures models and attaches models to data-sources.
|
||||||
|
- Configures application settings
|
||||||
|
- Runs additional boot scripts, so you can put custom setup code in multiple small files instead of in the main application file.
|
||||||
|
|
||||||
|
For more information, see [Defining boot scripts](https://loopback.io/doc/en/lb2/Defining-boot-scripts).
|
||||||
|
|
||||||
|
### Version notes
|
||||||
|
|
||||||
|
The version range `1.x` is backwards compatible with `app.boot` provided
|
||||||
|
by LoopBack 1.x versions and the project layout scaffolded by `slc lb project`
|
||||||
|
up to slc version 2.5.
|
||||||
|
|
||||||
|
The version range `2.x` supports the new project layout as scaffolded by
|
||||||
|
`yo loopback`.
|
||||||
|
|
||||||
|
This document describes the configuration conventions of the `2.x` versions.
|
||||||
|
|
||||||
## Installation
|
## Installation
|
||||||
|
|
||||||
|
@ -25,3 +55,20 @@ app.listen();
|
||||||
|
|
||||||
See [API docs](http://apidocs.strongloop.com/loopback-boot/) for
|
See [API docs](http://apidocs.strongloop.com/loopback-boot/) for
|
||||||
complete API reference.
|
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.
|
||||||
|
|
34
browser.js
34
browser.js
|
@ -1,4 +1,11 @@
|
||||||
var execute = require('./lib/executor');
|
// 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';
|
||||||
|
|
||||||
|
const Bootstrapper = require('./lib/bootstrapper');
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* The browser version of `bootLoopBackApp`.
|
* The browser version of `bootLoopBackApp`.
|
||||||
|
@ -10,15 +17,30 @@ var execute = require('./lib/executor');
|
||||||
* the browser bundle, see `boot.compileToBrowserify`.
|
* the browser bundle, see `boot.compileToBrowserify`.
|
||||||
*
|
*
|
||||||
* @param {Object} app The loopback app to boot, as returned by `loopback()`.
|
* @param {Object} app The loopback app to boot, as returned by `loopback()`.
|
||||||
|
* @param {Object|string} [options] options as described in
|
||||||
|
* `boot.compileToBrowserify`.
|
||||||
*
|
*
|
||||||
* @header bootBrowserApp(app)
|
* @header boot(app)
|
||||||
*/
|
*/
|
||||||
|
|
||||||
exports = module.exports = function bootBrowserApp(app) {
|
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.
|
||||||
|
let moduleName = 'loopback-boot#instructions';
|
||||||
|
const appId = options && typeof options === 'object' && options.appId;
|
||||||
|
if (appId)
|
||||||
|
moduleName += '-' + appId;
|
||||||
|
|
||||||
// The name of the module containing instructions
|
// The name of the module containing instructions
|
||||||
// is hard-coded in lib/bundler
|
// is hard-coded in lib/bundler
|
||||||
var instructions = require('loopback-boot#instructions');
|
const instructions = require(moduleName);
|
||||||
execute(app, instructions);
|
|
||||||
|
const bootstrapper = new Bootstrapper(options);
|
||||||
|
bootstrapper.phases = ['starting', 'start', 'started'];
|
||||||
|
const context = {
|
||||||
|
app: app,
|
||||||
|
instructions: instructions,
|
||||||
|
};
|
||||||
|
return bootstrapper.run(context, callback);
|
||||||
};
|
};
|
||||||
|
|
||||||
exports.execute = execute;
|
|
||||||
|
|
10
docs.json
10
docs.json
|
@ -1,12 +1,8 @@
|
||||||
{
|
{
|
||||||
"content": [
|
"content": [
|
||||||
{
|
"docs/header-node.md",
|
||||||
"title": "Bootstrap API",
|
|
||||||
"depth": 2
|
|
||||||
},
|
|
||||||
"index.js",
|
"index.js",
|
||||||
"browser.js",
|
"docs/header-browser.md",
|
||||||
"docs/configuration.md",
|
"browser.js"
|
||||||
"docs/browserify.md"
|
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,74 +0,0 @@
|
||||||
## Running in a browser
|
|
||||||
|
|
||||||
The bootstrap process is implemented in two steps that can be called
|
|
||||||
independently.
|
|
||||||
|
|
||||||
### Build
|
|
||||||
|
|
||||||
The first step loads all configuration files, merges values from additional
|
|
||||||
config files like `app.local.js` and produces a set of instructions
|
|
||||||
that can be used to boot the application.
|
|
||||||
|
|
||||||
These instructions must be included in the browser bundle together
|
|
||||||
with all configuration scripts from `models/` and `boot/`.
|
|
||||||
|
|
||||||
Don't worry, you don't have to understand these details.
|
|
||||||
Just call `boot.compileToBrowserify`, it will take care of everything for you.
|
|
||||||
|
|
||||||
```js
|
|
||||||
/*-- build file --*/
|
|
||||||
var browserify = require('browserify');
|
|
||||||
var boot = require('loopback-boot');
|
|
||||||
|
|
||||||
var b = browserify({
|
|
||||||
basedir: appDir,
|
|
||||||
});
|
|
||||||
|
|
||||||
// add the main application file
|
|
||||||
b.require('./app.js', { expose: 'loopback-app' });
|
|
||||||
|
|
||||||
// add boot instructions
|
|
||||||
boot.compileToBrowserify(appDir, b);
|
|
||||||
|
|
||||||
// create the bundle
|
|
||||||
var out = fs.createWriteStream('app.bundle.js');
|
|
||||||
b.bundle().pipe(out);
|
|
||||||
// handle out.on('error') and out.on('close')
|
|
||||||
```
|
|
||||||
|
|
||||||
### Run
|
|
||||||
|
|
||||||
In the browser, the main application file should call loopback-boot
|
|
||||||
to setup the loopback application by executing the instructions
|
|
||||||
contained in the browser bundle:
|
|
||||||
|
|
||||||
```js
|
|
||||||
/*-- app.js --*/
|
|
||||||
var loopback = require('loopback');
|
|
||||||
var boot = require('loopback-boot');
|
|
||||||
|
|
||||||
var app = module.exports = loopback();
|
|
||||||
boot(app);
|
|
||||||
```
|
|
||||||
|
|
||||||
The app object created above can be accessed via `require('loopback-app')`,
|
|
||||||
where `loopback-app` is the identifier used for the main app file in
|
|
||||||
the browserify build shown above.
|
|
||||||
|
|
||||||
Here is a simple example demonstrating the concept:
|
|
||||||
|
|
||||||
```xml
|
|
||||||
<script src="app.bundle.js"></script>
|
|
||||||
<script>
|
|
||||||
var app = require('loopback-app');
|
|
||||||
var User = app.models.User;
|
|
||||||
|
|
||||||
User.login({ email: 'test@example.com', password: '12345', function(err, res) {
|
|
||||||
if (err) {
|
|
||||||
console.error('Login failed: ', err);
|
|
||||||
} else {
|
|
||||||
console.log('Logged in.');
|
|
||||||
}
|
|
||||||
});
|
|
||||||
</script>
|
|
||||||
```
|
|
|
@ -0,0 +1,35 @@
|
||||||
|
## Server Files in Coffee-Script
|
||||||
|
|
||||||
|
In order to create application files in coffee-script, you'll need to register the coffee-script extension:
|
||||||
|
|
||||||
|
```javascript
|
||||||
|
require('coffee-script/register');
|
||||||
|
```
|
||||||
|
|
||||||
|
You'll need to do this at any entry points for the app (e.g. the server.js file, mocha.opts, and gulp/grunt). It is recommended to leave the entry point of the app (server.js by default, specified as 'main' in package.json) as a javascript file, and then register coffee-script, and proceed with any coffee-requires.
|
||||||
|
|
||||||
|
## Client Files in Coffee-Script
|
||||||
|
|
||||||
|
You can use the Coffeeify module to include Coffee files in your browser package. Use a build script like this:
|
||||||
|
|
||||||
|
|
||||||
|
```javascript
|
||||||
|
// assuming you haven't done so already
|
||||||
|
require('coffee-script/register');
|
||||||
|
|
||||||
|
var b = browserify({
|
||||||
|
basedir: appDir,
|
||||||
|
extensions: ['.coffee'], //causes browserify to look for this extension
|
||||||
|
debug: true
|
||||||
|
});
|
||||||
|
|
||||||
|
b.transform('coffeeify'); //adds coffee compiler to the build pipeline
|
||||||
|
|
||||||
|
b.require('./app.coffee', { expose: 'browser-app' }); //requiring your file will set the entry point
|
||||||
|
boot.compileToBrowserify(appDir, b);
|
||||||
|
|
||||||
|
var bundlePath = sandbox.resolve('browser-app-bundle.js'); //remember, the final result is still '.js'
|
||||||
|
var out = fs.createWriteStream(bundlePath);
|
||||||
|
|
||||||
|
b.bundle().pipe(out);
|
||||||
|
```
|
|
@ -1,50 +0,0 @@
|
||||||
## Configuration and conventions
|
|
||||||
|
|
||||||
### Model Definitions
|
|
||||||
|
|
||||||
The following is example JSON for two `Model` definitions:
|
|
||||||
"dealership" and "location".
|
|
||||||
|
|
||||||
```js
|
|
||||||
{
|
|
||||||
"dealership": {
|
|
||||||
// a reference, by name, to a dataSource definition
|
|
||||||
"dataSource": "my-db",
|
|
||||||
// the options passed to Model.extend(name, properties, options)
|
|
||||||
"options": {
|
|
||||||
"relations": {
|
|
||||||
"cars": {
|
|
||||||
"type": "hasMany",
|
|
||||||
"model": "Car",
|
|
||||||
"foreignKey": "dealerId"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
},
|
|
||||||
// the properties passed to Model.extend(name, properties, options)
|
|
||||||
"properties": {
|
|
||||||
"id": {"id": true},
|
|
||||||
"name": "String",
|
|
||||||
"zip": "Number",
|
|
||||||
"address": "String"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"car": {
|
|
||||||
"dataSource": "my-db"
|
|
||||||
"properties": {
|
|
||||||
"id": {
|
|
||||||
"type": "String",
|
|
||||||
"required": true,
|
|
||||||
"id": true
|
|
||||||
},
|
|
||||||
"make": {
|
|
||||||
"type": "String",
|
|
||||||
"required": true
|
|
||||||
},
|
|
||||||
"model": {
|
|
||||||
"type": "String",
|
|
||||||
"required": true
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
```
|
|
|
@ -0,0 +1,15 @@
|
||||||
|
## Browser API
|
||||||
|
|
||||||
|
Use this API in the `app.js` file that you process by browserify and run in the browser.
|
||||||
|
|
||||||
|
```js
|
||||||
|
var loopback = require('loopback');
|
||||||
|
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.
|
|
@ -0,0 +1,11 @@
|
||||||
|
## Node API
|
||||||
|
|
||||||
|
Use this API in the `app.js` file of your server-side Node.js application.
|
||||||
|
|
||||||
|
```js
|
||||||
|
var loopback= require('loopback');
|
||||||
|
var boot = require('loopback-boot');
|
||||||
|
|
||||||
|
var app = module.exports = loopback();
|
||||||
|
boot(app, __dirname);
|
||||||
|
```
|
|
@ -0,0 +1,133 @@
|
||||||
|
## Migrating from 1.x to 2.x
|
||||||
|
|
||||||
|
**Starting point: a sample 1.x project**
|
||||||
|
|
||||||
|
*models.json*
|
||||||
|
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"car": {
|
||||||
|
"properties": {
|
||||||
|
"color": "string",
|
||||||
|
},
|
||||||
|
"dataSource": "db"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
*models/car.js*
|
||||||
|
|
||||||
|
```js
|
||||||
|
var app = require('../app');
|
||||||
|
var Car = app.models.Car;
|
||||||
|
|
||||||
|
Car.prototype.honk = function(duration, cb) {
|
||||||
|
// make some noise for `duration` seconds
|
||||||
|
cb();
|
||||||
|
};
|
||||||
|
```
|
||||||
|
|
||||||
|
*app.js*
|
||||||
|
```js
|
||||||
|
var loopback = require('loopback');
|
||||||
|
var boot = require('loopback-boot');
|
||||||
|
var app = loopback();
|
||||||
|
boot(app, __dirname);
|
||||||
|
```
|
||||||
|
|
||||||
|
### App settings
|
||||||
|
|
||||||
|
The files with applications settings were renamed from `app.*` to `config.*`.
|
||||||
|
Rename the following files to upgrade a 1.x project for loopback-boot 2.x:
|
||||||
|
|
||||||
|
- `app.json` to `config.json`
|
||||||
|
- `app.local.json` to `config.local.json`
|
||||||
|
- `app.local.js` to `config.local.js`
|
||||||
|
- etc.
|
||||||
|
|
||||||
|
### Data sources
|
||||||
|
|
||||||
|
The configuration of data sources remains the same in both 1.x and 2.x
|
||||||
|
versions.
|
||||||
|
|
||||||
|
### Models
|
||||||
|
|
||||||
|
**The 2.x version of loopback-boot no longer creates Models, it's up to the
|
||||||
|
developer to create them before booting the app.**
|
||||||
|
|
||||||
|
The folder `models/` has a different semantincs in 2.x than in 1.x. Instead
|
||||||
|
of extending Models already defined by `app.boot` and `models.json`,
|
||||||
|
it provides a set of Model definitions that do not depend on
|
||||||
|
any application that may use them.
|
||||||
|
|
||||||
|
Perform the following steps to update a 1.x project for loopback-boot 2.x.
|
||||||
|
All code samples are referring to the sample project described above.
|
||||||
|
|
||||||
|
1. Move all Model-definition metadata from `models.json`
|
||||||
|
to new per-model json files in `models/` directory.
|
||||||
|
|
||||||
|
*models/car.json*
|
||||||
|
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"name": "car",
|
||||||
|
"properties": {
|
||||||
|
"color": "string",
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
*models.json*
|
||||||
|
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"car": {
|
||||||
|
"dataSource": "db"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
2. Change per-model javascript files to export a function that adds
|
||||||
|
custom methods to the model class.
|
||||||
|
|
||||||
|
*models/car.js*
|
||||||
|
|
||||||
|
```js
|
||||||
|
module.exports = function(Car, Base) {
|
||||||
|
Car.prototype.honk = function(duration, cb) {
|
||||||
|
// make some noise for `duration` seconds
|
||||||
|
cb();
|
||||||
|
};
|
||||||
|
};
|
||||||
|
```
|
||||||
|
|
||||||
|
3. If your model definitions are not in `./models`, then add an entry
|
||||||
|
to `models.json` to specify the paths where to look for model definitions.
|
||||||
|
|
||||||
|
*models.json*
|
||||||
|
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"_meta": {
|
||||||
|
"sources": ["./custom/path/to/models"]
|
||||||
|
},
|
||||||
|
"Car": {
|
||||||
|
"dataSource": "db"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
### Attaching built-in models
|
||||||
|
|
||||||
|
Models provided by LoopBack, such as `User` or `Role`, are no longer
|
||||||
|
automatically attached to default data-sources. The data-source configuration
|
||||||
|
entry `defaultForType` is silently ignored.
|
||||||
|
|
||||||
|
You have to explicitly configure all built-in models used by your application
|
||||||
|
in the `models.json` file.
|
||||||
|
|
||||||
|
```
|
||||||
|
{
|
||||||
|
"Role": { "dataSource": "db" }
|
||||||
|
}
|
||||||
|
```
|
191
index.js
191
index.js
|
@ -1,12 +1,25 @@
|
||||||
var ConfigLoader = require('./lib/config-loader');
|
// Copyright IBM Corp. 2014,2019. All Rights Reserved.
|
||||||
var compile = require('./lib/compiler');
|
// Node module: loopback-boot
|
||||||
var execute = require('./lib/executor');
|
// This file is licensed under the MIT License.
|
||||||
var addInstructionsToBrowserify = require('./lib/bundler');
|
// License text available at https://opensource.org/licenses/MIT
|
||||||
|
|
||||||
|
'use strict';
|
||||||
|
|
||||||
|
// 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
|
* Initialize an application from an options object or
|
||||||
* a set of JSON and JavaScript files.
|
* a set of JSON and JavaScript files.
|
||||||
*
|
*
|
||||||
|
* > **NOTE**: This module is primarily intended for use with LoopBack 2.0.
|
||||||
|
* It _does_ work with LoopBack 1.x applications, but
|
||||||
|
* none of the LoopBack 1.x examples or generated code (scaffolding) use it.
|
||||||
|
*
|
||||||
* This function takes an optional argument that is either a string
|
* This function takes an optional argument that is either a string
|
||||||
* or an object.
|
* or an object.
|
||||||
*
|
*
|
||||||
|
@ -16,25 +29,70 @@ var addInstructionsToBrowserify = require('./lib/bundler');
|
||||||
* 1. Creates DataSources from the `datasources.json` file in the application
|
* 1. Creates DataSources from the `datasources.json` file in the application
|
||||||
* root directory.
|
* root directory.
|
||||||
*
|
*
|
||||||
* 2. Creates Models from the `models.json` file in the application
|
* 2. Configures Models from the `model-config.json` file in the application
|
||||||
* root directory.
|
* root directory.
|
||||||
*
|
*
|
||||||
* If the argument is an object, then it looks for `model`, `dataSources`,
|
* 3. Configures the LoopBack Application object from the `config.json` file
|
||||||
* and `appRootDir` properties of the object.
|
* in the application root directory. These properties can be accessed
|
||||||
|
* using `app.get('propname')`.
|
||||||
|
*
|
||||||
|
* If the argument is an object, then it looks for `models`, `dataSources`,
|
||||||
|
* 'config', `modelsRootDir`, `dsRootDir`, `appConfigRootDir` and `appRootDir`
|
||||||
|
* properties of the object.
|
||||||
|
*
|
||||||
* If the object has no `appRootDir` property then it sets the current working
|
* If the object has no `appRootDir` property then it sets the current working
|
||||||
* directory as the application root directory.
|
* directory as the application root directory.
|
||||||
|
*
|
||||||
|
* The execution environment, {env}, is established from, in order,
|
||||||
|
* - `options.env`
|
||||||
|
* - `process.env.NODE_ENV`,
|
||||||
|
* - the literal `development`.
|
||||||
|
*
|
||||||
* Then it:
|
* Then it:
|
||||||
*
|
*
|
||||||
* 1. Creates DataSources from the `options.dataSources` object.
|
* 1. Creates DataSources from the `options.dataSources` object, if provided;
|
||||||
|
* otherwise, it searches for the files
|
||||||
|
* - `datasources.json`,
|
||||||
|
* - `datasources.local.js` or `datasources.local.json` (only one),
|
||||||
|
* - `datasources.{env}.js` or `datasources.{env}.json` (only one)
|
||||||
*
|
*
|
||||||
* 2. Creates Models from the `options.models` object.
|
* in the directory designated by 'options.dsRootDir', if present, or the
|
||||||
|
* application root directory. It merges the data source definitions from
|
||||||
|
* the files found.
|
||||||
*
|
*
|
||||||
* In both cases, the function loads JavaScript files in the `/models` and
|
* 2. Creates Models from the `options.models` object, if provided;
|
||||||
* `/boot` subdirectories of the application root directory with `require()`.
|
* otherwise, it searches for the files
|
||||||
|
* - `model-config.json`,
|
||||||
|
* - `model-config.local.js` or `model-config.local.json` (only one),
|
||||||
|
* - `model-config.{env}.js` or `model-config.{env}.json` (only one)
|
||||||
*
|
*
|
||||||
* **NOTE:** mixing `app.boot()` and `app.model(name, config)` in multiple
|
* in the directory designated by 'options.modelsRootDir', if present, or
|
||||||
* files may result in models being **undefined** due to race conditions.
|
* the application root directory. It merges the model definitions from the
|
||||||
* To avoid this when using `app.boot()` make sure all models are passed
|
* files found.
|
||||||
|
*
|
||||||
|
* 3. Configures the Application object from the `options.config` object,
|
||||||
|
* if provided;
|
||||||
|
* otherwise, it searches for the files
|
||||||
|
* - `config.json`,
|
||||||
|
* - `config.local.js` or `config.local.json` (only one),
|
||||||
|
* - `config.{env}.js` or `config.{env}.json` (only one)
|
||||||
|
*
|
||||||
|
* in the directory designated by 'options.appConfigRootDir', if present, or
|
||||||
|
* the application root directory. It merges the properties from the files
|
||||||
|
* found.
|
||||||
|
*
|
||||||
|
* In both cases, the function loads JavaScript files in the
|
||||||
|
* `/boot` subdirectory of the application root directory with `require()`.
|
||||||
|
*
|
||||||
|
* **NOTE:** The version 2.0 of loopback-boot changed the way how models
|
||||||
|
* are created. The `model-config.json` file contains only configuration
|
||||||
|
* options like dataSource and extra relations. To define a model,
|
||||||
|
* create a per-model JSON file in `models/` directory.
|
||||||
|
*
|
||||||
|
* **NOTE:** Mixing `bootLoopBackApp(app, bootConfig)` and
|
||||||
|
* `app.model(name, modelConfig)` in multiple
|
||||||
|
* files may result in models being undefined due to race conditions.
|
||||||
|
* To avoid this when using `bootLoopBackApp()` make sure all models are passed
|
||||||
* as part of the `models` definition.
|
* as part of the `models` definition.
|
||||||
*
|
*
|
||||||
* Throws an error if the config object is not valid or if boot fails.
|
* Throws an error if the config object is not valid or if boot fails.
|
||||||
|
@ -42,46 +100,111 @@ var addInstructionsToBrowserify = require('./lib/bundler');
|
||||||
* @param app LoopBack application created by `loopback()`.
|
* @param app LoopBack application created by `loopback()`.
|
||||||
* @options {String|Object} options Boot options; If String, this is
|
* @options {String|Object} options Boot options; If String, this is
|
||||||
* the application root directory; if object, has below properties.
|
* the application root directory; if object, has below properties.
|
||||||
* @property {String} appRootDir Directory to use when loading JSON and
|
* @property {String} [appRootDir] Directory to use when loading JSON and
|
||||||
* JavaScript files (optional).
|
* JavaScript files.
|
||||||
* Defaults to the current directory (`process.cwd()`).
|
* Defaults to the current directory (`process.cwd()`).
|
||||||
* @property {Object} models Object containing `Model` definitions (optional).
|
* @property {String} [appConfigRootDir] Directory to use when loading
|
||||||
* @property {Object} dataSources Object containing `DataSource`
|
* `config.json`. Defaults to `appRootDir`.
|
||||||
* definitions (optional).
|
* @property {Object} [models] Object containing `Model` configurations.
|
||||||
* @property {String} modelsRootDir Directory to use when loading `models.json`
|
* @property {Array} [modelDefinitions] List of model definitions to use.
|
||||||
* and `models/*.js`. Defaults to `appRootDir`.
|
* When `options.modelDefinitions` is provided, loopback-boot does not
|
||||||
* @property {String} datasourcesRootDir Directory to use when loading
|
* search filesystem and use only the models provided in this argument.
|
||||||
|
* @property {Object} [dataSources] Object containing `DataSource` definitions.
|
||||||
|
* @property {String} [modelsRootDir] Directory to use when loading
|
||||||
|
* `model-config.json`. Defaults to `appRootDir`.
|
||||||
|
* @property {String} [dsRootDir] Directory to use when loading
|
||||||
* `datasources.json`. Defaults to `appRootDir`.
|
* `datasources.json`. Defaults to `appRootDir`.
|
||||||
* @property {String} env Environment type, defaults to `process.env.NODE_ENV`
|
* @property {String} [middlewareRootDir] Directory to use when loading
|
||||||
|
* `middleware.json`. Defaults to `appRootDir`.
|
||||||
|
* @property {String} [componentRootDir] Directory to use when loading
|
||||||
|
* `component-config.json`. Defaults to `appRootDir`.
|
||||||
|
* @property {String} [env] Environment type, defaults to `process.env.NODE_ENV`
|
||||||
* or `development`. Common values are `development`, `staging` and
|
* or `development`. Common values are `development`, `staging` and
|
||||||
* `production`; however the applications are free to use any names.
|
* `production`; however the applications are free to use any names.
|
||||||
|
* @property {Array.<String>} [modelSources] List of directories where to look
|
||||||
|
* for files containing model definitions.
|
||||||
|
* @property {Object} [middleware] Middleware configuration to use instead
|
||||||
|
* of `{appRootDir}/middleware.json`
|
||||||
|
* @property {Object} [components] Component configuration to use instead
|
||||||
|
* of `{appRootDir}/component-config.json`
|
||||||
|
* @property {Array.<String>} [mixinDirs] List of directories where to look
|
||||||
|
* for files containing model mixin definitions. All files (mixins) found
|
||||||
|
* in these directory are loaded.
|
||||||
|
* @property {Array.<String>} [mixinSources] List of directories where to look
|
||||||
|
* for files containing model mixin definitions. Only mixins used by
|
||||||
|
* application models are loaded from these directories.
|
||||||
|
* @property {Array.<String>} [bootDirs] List of directories where to look
|
||||||
|
* for boot scripts.
|
||||||
|
* @property {Array.<String>} [bootScripts] List of script files to execute
|
||||||
|
* on boot.
|
||||||
|
* @property {String|Function|Boolean} [normalization] Mixin normalization
|
||||||
|
* format: false, 'none', 'classify', 'dasherize' - defaults to 'classify'.
|
||||||
* @end
|
* @end
|
||||||
|
* @param {Function} [callback] Callback function.
|
||||||
*
|
*
|
||||||
* @header bootLoopBackApp(app, [options])
|
* @header boot(app, [options], [callback])
|
||||||
*/
|
*/
|
||||||
|
|
||||||
exports = module.exports = function bootLoopBackApp(app, options) {
|
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
|
// backwards compatibility with loopback's app.boot
|
||||||
options.env = options.env || app.get('env');
|
options.env = options.env || app.get('env');
|
||||||
|
|
||||||
var instructions = compile(options);
|
const bootstrapper = new Bootstrapper(options);
|
||||||
execute(app, instructions);
|
|
||||||
|
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);
|
||||||
};
|
};
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Compile boot instructions and add them to a browserify bundler.
|
* Compile boot instructions and add them to a browserify bundler.
|
||||||
* @param {Object|String} options as described in `bootLoopBackApp` above.
|
* @param {Object|String} options as described in `bootLoopBackApp` above.
|
||||||
|
* @property {String} [appId] Application identifier used to load the correct
|
||||||
|
* boot configuration when building multiple applications using browserify.
|
||||||
|
* @end
|
||||||
* @param {Object} bundler A browserify bundler created by `browserify()`.
|
* @param {Object} bundler A browserify bundler created by `browserify()`.
|
||||||
*
|
*
|
||||||
* @header boot.compileToBrowserify(options, bundler)
|
* @header boot.compileToBrowserify(options, bundler)
|
||||||
*/
|
*/
|
||||||
exports.compileToBrowserify = function(options, bundler) {
|
exports.compileToBrowserify = function(options, bundler, done) {
|
||||||
addInstructionsToBrowserify(compile(options), bundler);
|
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.addInstructionsToBrowserify = addInstructionsToBrowserify;
|
exports.addInstructionsToBrowserify = addInstructionsToBrowserify;
|
||||||
|
exports.Bootstrapper = Bootstrapper;
|
||||||
|
exports.PluginBase = PluginBase;
|
||||||
|
|
||||||
|
exports.execute = function(app, instructions, done) {
|
||||||
|
const bootstrapper = new Bootstrapper(
|
||||||
|
{phases: ['starting', 'start', 'started']},
|
||||||
|
);
|
||||||
|
const context = {
|
||||||
|
app: app,
|
||||||
|
instructions: instructions,
|
||||||
|
};
|
||||||
|
return bootstrapper.run(context, done);
|
||||||
|
};
|
||||||
|
|
|
@ -0,0 +1,17 @@
|
||||||
|
{
|
||||||
|
"1e5fea50eef843cbffd1d438494912c8": "Nelze vyřešit cestu \"{0}\"",
|
||||||
|
"34319676975b1abf107da7a056abb434": "Neplatný formát normalizace - \"{0}\"",
|
||||||
|
"3a7049e42006e8bc19e0f4fc8df63b6b": "Aplikace `app` je založena na nekompatibilní verzí Loopback {0}. Podporované verze: {1}",
|
||||||
|
"3f93b626dd9a1c33d67490f6e71018b5": "VAROVÁNÍ: Hlavní konfigurační soubor \"{0}{{.json}}\" chybí",
|
||||||
|
"4d052d84c8620730afd4a30832f11724": "Nelze konfigurovat neznámý model {0}",
|
||||||
|
"4ed668e9187650d898acf97707df445a": "Fáze {{phase}} \"{0}\" není definována v hlavní konfiguraci.",
|
||||||
|
"6447e6b342a2c51ab0bc53b3cbdf3742": "Konflikt řazení: Nelze přidat \"{0}\" za \"{1}\", protože již bylo uvedené opačné pořadí",
|
||||||
|
"70654dc6eb565613a33344efed3de998": "Nezdařilo se načíst zaváděcí skript: {0}\n{1}",
|
||||||
|
"7f7bdcadb75abfef1bd8a126d547dd6d": "{0} se neinterpretuje na platnou hodnotu, vráceno jako {1}. \"{2}\" musí být rozdělitelný v proměnné prostředí nebo {{app.get()}}.",
|
||||||
|
"91a742b7c3568cf6b6755741a70b3c52": "{{middleware}} \"{0}\" v {{phase}} \"{1}\" není definováno v hlavní konfiguraci.",
|
||||||
|
"a3aa22086ae4976cd013065c9a3ff81c": "Nelze použít {0}: ",
|
||||||
|
"be2cf2868ba54624fe38e9908dde5e9e": "Data v {{model-config.json}} jsou v nepodporovaném formátu {{1.x}}.",
|
||||||
|
"ec551b6f2fafd8d40af801ebe5bb09f6": "Vyřazení pokynů {{middleware}}, klient {{loopback}} nepodporuje {{middleware}}.",
|
||||||
|
"fdc23df1bd0fe55fe3faabcc89ff60f3": "Middleware \"{0}\" nelze nalézt: {1}"
|
||||||
|
}
|
||||||
|
|
|
@ -0,0 +1,17 @@
|
||||||
|
{
|
||||||
|
"1e5fea50eef843cbffd1d438494912c8": "Pfad \"{0}\" kann nicht aufgelöst werden",
|
||||||
|
"34319676975b1abf107da7a056abb434": "Ungültiges Normalisierungsformat - \"{0}\"",
|
||||||
|
"3a7049e42006e8bc19e0f4fc8df63b6b": "Die `app` wird von einer nicht kompatiblen Loopback-Version {0} betrieben. Unterstützte Versionen: {1}",
|
||||||
|
"3f93b626dd9a1c33d67490f6e71018b5": "WARNUNG: Hauptkonfigurationsdatei \"{0}{{.json}}\" fehlt",
|
||||||
|
"4d052d84c8620730afd4a30832f11724": "Unbekanntes Modell {0} kann nicht konfiguriert werden",
|
||||||
|
"4ed668e9187650d898acf97707df445a": "Die {{phase}} \"{0}\" ist in der Hauptkonfiguration nicht definiert.",
|
||||||
|
"6447e6b342a2c51ab0bc53b3cbdf3742": "Sortierungskonflikt: \"{0}\" kann nicht nach \"{1}\" hinzugefügt werden, da die entgegengesetzte Reihenfolge bereits angegeben wurde",
|
||||||
|
"70654dc6eb565613a33344efed3de998": "Laden von Boot-Script fehlgeschlagen: {0}\n{1}",
|
||||||
|
"7f7bdcadb75abfef1bd8a126d547dd6d": "{0} wird nicht in einen gültigen Wert aufgelöst; zurückgegeben als {1}. \"{2}\" muss in der Umgebungsvariable oder über {{app.get()}} auflösbar sein.",
|
||||||
|
"91a742b7c3568cf6b6755741a70b3c52": "Die {{middleware}} \"{0}\" in {{phase}} \"{1}\" ist in der Hauptkonfiguration nicht definiert.",
|
||||||
|
"a3aa22086ae4976cd013065c9a3ff81c": "{0} kann nicht angewendet werden: ",
|
||||||
|
"be2cf2868ba54624fe38e9908dde5e9e": "Die Daten in {{model-config.json}} haben das nicht unterstützte {{1.x}}-Format.",
|
||||||
|
"ec551b6f2fafd8d40af801ebe5bb09f6": "{{middleware}}-Anweisungen werden verworfen, {{loopback}}-Client unterstützt {{middleware}} nicht.",
|
||||||
|
"fdc23df1bd0fe55fe3faabcc89ff60f3": "Middleware \"{0}\" nicht gefunden: {1}"
|
||||||
|
}
|
||||||
|
|
|
@ -0,0 +1,16 @@
|
||||||
|
{
|
||||||
|
"1e5fea50eef843cbffd1d438494912c8": "Cannot resolve path \"{0}\"",
|
||||||
|
"34319676975b1abf107da7a056abb434": "Invalid normalization format - \"{0}\"",
|
||||||
|
"3a7049e42006e8bc19e0f4fc8df63b6b": "The `app` is powered by an incompatible loopback version {0}. Supported versions: {1}",
|
||||||
|
"3f93b626dd9a1c33d67490f6e71018b5": "WARNING: Main config file \"{0}{{.json}}\" is missing",
|
||||||
|
"4d052d84c8620730afd4a30832f11724": "Cannot configure unknown model {0}",
|
||||||
|
"4ed668e9187650d898acf97707df445a": "The {{phase}} \"{0}\" is not defined in the main config.",
|
||||||
|
"6447e6b342a2c51ab0bc53b3cbdf3742": "Ordering conflict: cannot add \"{0}\" after \"{1}\", because the opposite order was already specified",
|
||||||
|
"70654dc6eb565613a33344efed3de998": "Failed loading boot script: {0}\n{1}",
|
||||||
|
"7f7bdcadb75abfef1bd8a126d547dd6d": "{0} does not resolve to a valid value, returned as {1}. \"{2}\" must be resolvable in Environment variable or by {{app.get()}}.",
|
||||||
|
"91a742b7c3568cf6b6755741a70b3c52": "The {{middleware}} \"{0}\" in {{phase}} \"{1}\"is not defined in the main config.",
|
||||||
|
"a3aa22086ae4976cd013065c9a3ff81c": "Cannot apply {0}: ",
|
||||||
|
"be2cf2868ba54624fe38e9908dde5e9e": "The data in {{model-config.json}} is in the unsupported {{1.x}} format.",
|
||||||
|
"ec551b6f2fafd8d40af801ebe5bb09f6": "Discarding {{middleware}} instructions, {{loopback}} client does not support {{middleware}}.",
|
||||||
|
"fdc23df1bd0fe55fe3faabcc89ff60f3": "Middleware \"{0}\" not found: {1}"
|
||||||
|
}
|
|
@ -0,0 +1,17 @@
|
||||||
|
{
|
||||||
|
"1e5fea50eef843cbffd1d438494912c8": "No se puede resolver la vía de acceso \"{0}\"",
|
||||||
|
"34319676975b1abf107da7a056abb434": "Formato de normalización no válido - \"{0}\"",
|
||||||
|
"3a7049e42006e8bc19e0f4fc8df63b6b": "La `app` está basada en una versión de loopback incompatible {0}. Versiones soportadas: {1}",
|
||||||
|
"3f93b626dd9a1c33d67490f6e71018b5": "AVISO: falta el archivo de configuración principal \"{0}{{.json}}\"",
|
||||||
|
"4d052d84c8620730afd4a30832f11724": "No se puede configurar el modelo desconocido {0}",
|
||||||
|
"4ed668e9187650d898acf97707df445a": "La {{phase}} \"{0}\" no está definida en la configuración principal.",
|
||||||
|
"6447e6b342a2c51ab0bc53b3cbdf3742": "Conflicto de orden: no se puede añadir \"{0}\" después de \"{1}\", porque ya se ha especificado el orden inverso.",
|
||||||
|
"70654dc6eb565613a33344efed3de998": "No se ha podido cargar el script de arranque: {0}\n{1}",
|
||||||
|
"7f7bdcadb75abfef1bd8a126d547dd6d": "{0} no se resuelve como un valor válido, se ha devuelto como {1}. \"{2}\" debe poder resolverse en la variable de entorno o por medio de {{app.get()}}.",
|
||||||
|
"91a742b7c3568cf6b6755741a70b3c52": "El {{middleware}} \"{0}\" en la {{phase}} \"{1}\" no está definido en la configuración principal.",
|
||||||
|
"a3aa22086ae4976cd013065c9a3ff81c": "No se puede aplicar {0}: ",
|
||||||
|
"be2cf2868ba54624fe38e9908dde5e9e": "Los datos de {{model-config.json}} están en un formato {{1.x}} no soportado.",
|
||||||
|
"ec551b6f2fafd8d40af801ebe5bb09f6": "Descartando instrucciones de {{middleware}}, el cliente de {{loopback}} no da soporte a {{middleware}}.",
|
||||||
|
"fdc23df1bd0fe55fe3faabcc89ff60f3": "Middleware \"{0}\" no encontrado: {1}"
|
||||||
|
}
|
||||||
|
|
|
@ -0,0 +1,17 @@
|
||||||
|
{
|
||||||
|
"1e5fea50eef843cbffd1d438494912c8": "Impossible de résoudre le chemin \"{0}\"",
|
||||||
|
"34319676975b1abf107da7a056abb434": "Format de normalisation non valide - \"{0}\"",
|
||||||
|
"3a7049e42006e8bc19e0f4fc8df63b6b": "L'application `app` est basée sur une version loopback {0} incompatible. Versions prises en charge : {1}",
|
||||||
|
"3f93b626dd9a1c33d67490f6e71018b5": "AVERTISSEMENT : le fichier de configuration principal \"{0}{{.json}}\" est manquant",
|
||||||
|
"4d052d84c8620730afd4a30832f11724": "Impossible de configurer le modèle inconnu {0}",
|
||||||
|
"4ed668e9187650d898acf97707df445a": "{{phase}} \"{0}\" n'est pas défini dans la configuration principale.",
|
||||||
|
"6447e6b342a2c51ab0bc53b3cbdf3742": "Conflit concernant l'ordre : impossible d'ajouter \"{0}\" après \"{1}\" car l'ordre opposé à déjà été spécifié",
|
||||||
|
"70654dc6eb565613a33344efed3de998": "Echec du chargement du script d'amorçage : {0}\n{1}",
|
||||||
|
"7f7bdcadb75abfef1bd8a126d547dd6d": "{0} n'est pas résolu en une valeur valide, renvoyé sous forme de {1}. \"{2}\" doit pouvoir être résolu dans la variable d'environnement ou par {{app.get()}}.",
|
||||||
|
"91a742b7c3568cf6b6755741a70b3c52": "Le {{middleware}} \"{0}\" dans {{phase}} \"{1}\" n'est pas défini dans la configuration principale.",
|
||||||
|
"a3aa22086ae4976cd013065c9a3ff81c": "Impossible d'appliquer {0} : ",
|
||||||
|
"be2cf2868ba54624fe38e9908dde5e9e": "Les données contenues dans {{model-config.json}} sont au format {{1.x}} qui n'est pas pris en charge.",
|
||||||
|
"ec551b6f2fafd8d40af801ebe5bb09f6": "Les instructions {{middleware}} sont ignorées ; le client {{loopback}} ne prend pas en charge {{middleware}}.",
|
||||||
|
"fdc23df1bd0fe55fe3faabcc89ff60f3": "Middleware \"{0}\" introuvable : {1}"
|
||||||
|
}
|
||||||
|
|
|
@ -0,0 +1,17 @@
|
||||||
|
{
|
||||||
|
"1e5fea50eef843cbffd1d438494912c8": "Impossibile risolvere il percorso \"{0}\"",
|
||||||
|
"34319676975b1abf107da7a056abb434": "Formato di normalizzazione non valido - \"{0}\"",
|
||||||
|
"3a7049e42006e8bc19e0f4fc8df63b6b": "La `app` si basa su una versione loopback non compatibile {0}. Versioni supportate: {1}",
|
||||||
|
"3f93b626dd9a1c33d67490f6e71018b5": "AVVERTENZA: file di configurazione principale \"{0}{{.json}}\" mancante",
|
||||||
|
"4d052d84c8620730afd4a30832f11724": "Impossibile configurare il modello {0} sconosciuto",
|
||||||
|
"4ed668e9187650d898acf97707df445a": "{{phase}} \"{0}\" non definita nella configurazione principale.",
|
||||||
|
"6447e6b342a2c51ab0bc53b3cbdf3742": "Conflitto di ordinamento: impossibile aggiungere \"{0}\" dopo \"{1}\", perché è già stato specificato l'ordine opposto",
|
||||||
|
"70654dc6eb565613a33344efed3de998": "Caricamento dello script di boot non riuscito: {0}\n{1}",
|
||||||
|
"7f7bdcadb75abfef1bd8a126d547dd6d": "{0} non viene risolto in un valore valido, restituito come {1}. \"{2}\" deve essere risolto in una variabile di ambiente o da {{app.get()}}.",
|
||||||
|
"91a742b7c3568cf6b6755741a70b3c52": "{{middleware}} \"{0}\" in {{phase}} \"{1}\" non definito nella configurazione principale.",
|
||||||
|
"a3aa22086ae4976cd013065c9a3ff81c": "Impossibile applicare {0}: ",
|
||||||
|
"be2cf2868ba54624fe38e9908dde5e9e": "I dati in {{model-config.json}} sono nel formato {{1.x}} non supportato.",
|
||||||
|
"ec551b6f2fafd8d40af801ebe5bb09f6": "Eliminazione delle istruzioni {{middleware}}, il client {{loopback}} non supporta {{middleware}}.",
|
||||||
|
"fdc23df1bd0fe55fe3faabcc89ff60f3": "Middleware \"{0}\" non trovato: {1}"
|
||||||
|
}
|
||||||
|
|
|
@ -0,0 +1,17 @@
|
||||||
|
{
|
||||||
|
"1e5fea50eef843cbffd1d438494912c8": "パス \"{0}\" を解決できません",
|
||||||
|
"34319676975b1abf107da7a056abb434": "無効な正規化形式 - \"{0}\"",
|
||||||
|
"3a7049e42006e8bc19e0f4fc8df63b6b": "「アプリケーション」は、互換性のない loopback バージョン {0} を使用しています。サポートされるバージョン: {1}",
|
||||||
|
"3f93b626dd9a1c33d67490f6e71018b5": "警告: メイン構成ファイル \"{0}{{.json}}\" が欠落しています",
|
||||||
|
"4d052d84c8620730afd4a30832f11724": "不明なモデル {0} を構成できません",
|
||||||
|
"4ed668e9187650d898acf97707df445a": "メイン構成内に {{phase}} \"{0}\" が定義されていません。",
|
||||||
|
"6447e6b342a2c51ab0bc53b3cbdf3742": "順序付けの競合: \"{0}\" を \"{1}\" の後に追加することはできません。既に逆の順序が指定されています",
|
||||||
|
"70654dc6eb565613a33344efed3de998": "ブート・スクリプトのロードに失敗しました: {0}\n{1}",
|
||||||
|
"7f7bdcadb75abfef1bd8a126d547dd6d": "{0} は有効な値に解決されず、{1} として返されました。 \"{2}\" は環境変数または {{app.get()}} で解決できなければなりません。",
|
||||||
|
"91a742b7c3568cf6b6755741a70b3c52": "{{phase}} \"{1}\" の {{middleware}} \"{0}\" がメイン構成内に定義されていません。",
|
||||||
|
"a3aa22086ae4976cd013065c9a3ff81c": "{0} を適用できません: ",
|
||||||
|
"be2cf2868ba54624fe38e9908dde5e9e": "{{model-config.json}} のデータが、サポートされていない {{1.x}} 形式になっています。",
|
||||||
|
"ec551b6f2fafd8d40af801ebe5bb09f6": "{{middleware}} 命令を破棄します。{{loopback}} クライアントでは {{middleware}} はサポートされません。",
|
||||||
|
"fdc23df1bd0fe55fe3faabcc89ff60f3": "ミドルウェア \"{0}\" が見つかりません: {1}"
|
||||||
|
}
|
||||||
|
|
|
@ -0,0 +1,17 @@
|
||||||
|
{
|
||||||
|
"1e5fea50eef843cbffd1d438494912c8": "\"{0}\" 경로를 해석할 수 없음",
|
||||||
|
"34319676975b1abf107da7a056abb434": "올바르지 않은 정규화 형식 - \"{0}\"",
|
||||||
|
"3a7049e42006e8bc19e0f4fc8df63b6b": "`앱`이 호환되지 않는 루프백 버전 {0}을(를) 기반으로 합니다. 지원되는 버전: {1}",
|
||||||
|
"3f93b626dd9a1c33d67490f6e71018b5": "경고: 기본 구성 파일 \"{0}{{.json}}\"이(가) 누락됨",
|
||||||
|
"4d052d84c8620730afd4a30832f11724": "알 수 없는 모델 {0}을(를) 구성할 수 없음",
|
||||||
|
"4ed668e9187650d898acf97707df445a": "{{phase}} \"{0}\"이(가) 기본 구성에 정의되어 있지 않습니다.",
|
||||||
|
"6447e6b342a2c51ab0bc53b3cbdf3742": "순서 지정 충돌: 반대 순서로 이미 지정되어서 \"{1}\" 뒤에 \"{0}\"을(를) 추가할 수 없음",
|
||||||
|
"70654dc6eb565613a33344efed3de998": "부트 스크립트를 로드하는 데 실패함: {0}\n{1}",
|
||||||
|
"7f7bdcadb75abfef1bd8a126d547dd6d": "{0}이(가) 올바른 값으로 해석되지 않아서 {1}(으)로 리턴되었습니다. \"{2}\"은(는) 환경 변수에서 또는 {{app.get()}}에 의해 해석 가능해야 합니다. ",
|
||||||
|
"91a742b7c3568cf6b6755741a70b3c52": "{{phase}} \"{1}\"의 {{middleware}} \"{0}\"이(가) 기본 구성에 정의되어 있지 않습니다. ",
|
||||||
|
"a3aa22086ae4976cd013065c9a3ff81c": "{0}을(를) 적용할 수 없음: ",
|
||||||
|
"be2cf2868ba54624fe38e9908dde5e9e": "{{model-config.json}}의 데이터가 지원되지 않는 {{1.x}} 형식입니다. ",
|
||||||
|
"ec551b6f2fafd8d40af801ebe5bb09f6": "{{middleware}} 지시사항을 버리십시오. {{loopback}} 클라이언트가 {{middleware}}을(를) 지원하지 않습니다.",
|
||||||
|
"fdc23df1bd0fe55fe3faabcc89ff60f3": "미들웨어 \"{0}\"을(를) 찾을 수 없음: {1}"
|
||||||
|
}
|
||||||
|
|
|
@ -0,0 +1,17 @@
|
||||||
|
{
|
||||||
|
"1e5fea50eef843cbffd1d438494912c8": "Pad \"{0}\" kan niet worden omgezet.",
|
||||||
|
"34319676975b1abf107da7a056abb434": "Ongeldige normalisatie-indeling - \"{0}\"",
|
||||||
|
"3a7049e42006e8bc19e0f4fc8df63b6b": "De 'app' wordt aangestuurd door een incompatibele versie van loopback, {0}. Ondersteunde versies: {1}",
|
||||||
|
"3f93b626dd9a1c33d67490f6e71018b5": "WAARSCHUWING: Hoofdconfiguratiebestand \"{0}{{.json}}\" ontbreekt.",
|
||||||
|
"4d052d84c8620730afd4a30832f11724": "Configuratie van onbekend model {0} kan niet ongedaan worden gemaakt",
|
||||||
|
"4ed668e9187650d898acf97707df445a": "De {{phase}} \"{0}\" is niet gedefinieerd in de hoofdconfiguratie.",
|
||||||
|
"6447e6b342a2c51ab0bc53b3cbdf3742": "Volgordeconflict: \"{0}\" kan niet worden toegevoegd na \"{1}\", omdat de omgekeerde volgorde al is opgegeven.",
|
||||||
|
"70654dc6eb565613a33344efed3de998": "Laden van opstartscript is mislukt: {0}\n{1}",
|
||||||
|
"7f7bdcadb75abfef1bd8a126d547dd6d": "{0} wordt niet omgezet in een geldige waarde; wordt geretourneerd als {1}. \"{2}\" moet omgezet kunnen worden in een omgevingsvariabele of door {{app.get()}}.",
|
||||||
|
"91a742b7c3568cf6b6755741a70b3c52": "De {{middleware}} \"{0}\" in {{phase}} \"{1}\" is niet gedefinieerd in de hoofdconfiguratie.",
|
||||||
|
"a3aa22086ae4976cd013065c9a3ff81c": "{0} kan niet worden toegepast: ",
|
||||||
|
"be2cf2868ba54624fe38e9908dde5e9e": "De gegevens in {{model-config.json}} hebben de niet ondersteunde indeling {{1.x}}.",
|
||||||
|
"ec551b6f2fafd8d40af801ebe5bb09f6": "{{middleware}} instructies worden verwijderd, {{loopback}}-client ondersteunt geen {{middleware}}.",
|
||||||
|
"fdc23df1bd0fe55fe3faabcc89ff60f3": "Middleware \"{0}\" is niet gevonden: {1}"
|
||||||
|
}
|
||||||
|
|
|
@ -0,0 +1,17 @@
|
||||||
|
{
|
||||||
|
"1e5fea50eef843cbffd1d438494912c8": "Nie można rozstrzygnąć ścieżki \"{0}\"",
|
||||||
|
"34319676975b1abf107da7a056abb434": "Niepoprawny format normalizacji — \"{0}\"",
|
||||||
|
"3a7049e42006e8bc19e0f4fc8df63b6b": "Aplikacja jest obsługiwana przez niezgodną wersję aplikacji LoopBack {0}. Obsługiwane wersje: {1}",
|
||||||
|
"3f93b626dd9a1c33d67490f6e71018b5": "OSTRZEŻENIE: Brak głównego pliku konfiguracyjnego \"{0}{{.json}}\"",
|
||||||
|
"4d052d84c8620730afd4a30832f11724": "Nie można skonfigurować nieznanego modelu {0}",
|
||||||
|
"4ed668e9187650d898acf97707df445a": "Faza {{phase}} \"{0}\" nie została zdefiniowana w konfiguracji głównej.",
|
||||||
|
"6447e6b342a2c51ab0bc53b3cbdf3742": "Konflikt porządkowania: nie można dodać elementu \"{0}\" po elemencie \"{1}\", ponieważ została już określona odwrotna kolejność",
|
||||||
|
"70654dc6eb565613a33344efed3de998": "Nie powiodło się ładowanie skryptu startowego: {0}\n{1}",
|
||||||
|
"7f7bdcadb75abfef1bd8a126d547dd6d": "{0} nie umożliwia rozstrzygnięcia na poprawną wartość, zwrócono jako {1}. \"{2}\" musi umożliwiać rozstrzygnięcie w zmiennej środowiskowej lub przez metodę {{app.get()}}.",
|
||||||
|
"91a742b7c3568cf6b6755741a70b3c52": "Warstwa pośrednia {{middleware}} \"{0}\" w fazie {{phase}} \"{1}\" nie została zdefiniowana w konfiguracji głównej.",
|
||||||
|
"a3aa22086ae4976cd013065c9a3ff81c": "Nie można zastosować {0}: ",
|
||||||
|
"be2cf2868ba54624fe38e9908dde5e9e": "Dane w pliku {{model-config.json}} mają nieobsługiwany format {{1.x}}.",
|
||||||
|
"ec551b6f2fafd8d40af801ebe5bb09f6": "Odrzucanie instrukcji warstwy pośredniej {{middleware}}, klient {{loopback}} nie obsługuje warstwy pośredniej {{middleware}}.",
|
||||||
|
"fdc23df1bd0fe55fe3faabcc89ff60f3": "Warstwa pośrednia \"{0}\" nie została znaleziona: {1}"
|
||||||
|
}
|
||||||
|
|
|
@ -0,0 +1,17 @@
|
||||||
|
{
|
||||||
|
"1e5fea50eef843cbffd1d438494912c8": "Não é possível resolver caminho \"{0}\"",
|
||||||
|
"34319676975b1abf107da7a056abb434": "Formato de normalização inválido - \"{0}\"",
|
||||||
|
"3a7049e42006e8bc19e0f4fc8df63b6b": "O `app` é desenvolvido com uma versão de loopback incompatível {0}. Versões suportadas: {1}",
|
||||||
|
"3f93b626dd9a1c33d67490f6e71018b5": "AVISO: o arquivo de configuração principal \"{0}{{.json}}\" está ausente",
|
||||||
|
"4d052d84c8620730afd4a30832f11724": "Não é possível configurar modelo desconhecido {0}",
|
||||||
|
"4ed668e9187650d898acf97707df445a": "A {{phase}} \"{0}\" não foi definida na configuração principal.",
|
||||||
|
"6447e6b342a2c51ab0bc53b3cbdf3742": "Conflito de ordem: não é possível incluir \"{0}\" após \"{1}\", porque a ordem oposta já foi especificada",
|
||||||
|
"70654dc6eb565613a33344efed3de998": "Falha ao carregar script de inicialização: {0}\n{1}",
|
||||||
|
"7f7bdcadb75abfef1bd8a126d547dd6d": "{0} não resolve para um valor válido, retornado como {1}. \"{2}\" deve ser resolvível na variável de ambiente ou pelo {{app.get()}}.",
|
||||||
|
"91a742b7c3568cf6b6755741a70b3c52": "O {{middleware}} \"{0}\" em {{phase}} \"{1}\" não é definido na configuração principal.",
|
||||||
|
"a3aa22086ae4976cd013065c9a3ff81c": "Não é possível aplicar {0}: ",
|
||||||
|
"be2cf2868ba54624fe38e9908dde5e9e": "Os dados em {{model-config.json}} estão no formato não suportado {{1.x}}.",
|
||||||
|
"ec551b6f2fafd8d40af801ebe5bb09f6": "Descartando instruções de {{middleware}}, cliente de {{loopback}} não suporta {{middleware}}.",
|
||||||
|
"fdc23df1bd0fe55fe3faabcc89ff60f3": "Middleware \"{0}\" não localizado: {1}"
|
||||||
|
}
|
||||||
|
|
|
@ -0,0 +1,17 @@
|
||||||
|
{
|
||||||
|
"1e5fea50eef843cbffd1d438494912c8": "Не удалось определить путь \"{0}\"",
|
||||||
|
"34319676975b1abf107da7a056abb434": "Недопустимый формат нормализации - \"{0}\"",
|
||||||
|
"3a7049e42006e8bc19e0f4fc8df63b6b": "Приложение `app` создано на основе несовместимой версии loopback {0}. Поддерживаемые версии: {1}",
|
||||||
|
"3f93b626dd9a1c33d67490f6e71018b5": "ПРЕДУПРЕЖДЕНИЕ: отсутствует главный файл конфигурации \"{0}{{.json}}\"",
|
||||||
|
"4d052d84c8620730afd4a30832f11724": "Не удается настроить неизвестную модель {0}",
|
||||||
|
"4ed668e9187650d898acf97707df445a": "Этап {{phase}} \"{0}\" не определен в главной конфигурации.",
|
||||||
|
"6447e6b342a2c51ab0bc53b3cbdf3742": "Конфликт упорядочения: не удается добавить \"{0}\" после \"{1}\", та как уже указан другой порядок",
|
||||||
|
"70654dc6eb565613a33344efed3de998": "Не удалось загрузить сценарий загрузки: {0}\n{1}",
|
||||||
|
"7f7bdcadb75abfef1bd8a126d547dd6d": "{0} не удается определить в допустимое значение, возвращено как {1}. \"{2}\" должен определяться как переменная среды или с помощью {{app.get()}}.",
|
||||||
|
"91a742b7c3568cf6b6755741a70b3c52": "{{middleware}} \"{0}\" на этапе {{phase}} \"{1}\"не определено в главной конфигурации.",
|
||||||
|
"a3aa22086ae4976cd013065c9a3ff81c": "Не удается применить {0}: ",
|
||||||
|
"be2cf2868ba54624fe38e9908dde5e9e": "Данные в {{model-config.json}} указаны в неподдерживаемом формате {{1.x}}.",
|
||||||
|
"ec551b6f2fafd8d40af801ebe5bb09f6": "Инструкции {{middleware}} отменяются, клиент {{loopback}} не поддерживает {{middleware}}.",
|
||||||
|
"fdc23df1bd0fe55fe3faabcc89ff60f3": "Не найдено промежуточное ПО \"{0}\": {1}"
|
||||||
|
}
|
||||||
|
|
|
@ -0,0 +1,17 @@
|
||||||
|
{
|
||||||
|
"1e5fea50eef843cbffd1d438494912c8": "\"{0}\" yolu çözülemiyor",
|
||||||
|
"34319676975b1abf107da7a056abb434": "Geçersiz normalleştirme biçimi - \"{0}\"",
|
||||||
|
"3a7049e42006e8bc19e0f4fc8df63b6b": "`app` uyumsuz olan geri döngü {0} sürümüyle güçlendirilmiş. Desteklenen sürümler: {1}",
|
||||||
|
"3f93b626dd9a1c33d67490f6e71018b5": "UYARI: Ana yapılandırma dosyası \"{0}{{.json}}\" eksik",
|
||||||
|
"4d052d84c8620730afd4a30832f11724": "Bilinmeyen {0} modeli yapılandırılamıyor",
|
||||||
|
"4ed668e9187650d898acf97707df445a": "{{phase}} \"{0}\", ana yapılandırmada tanımlı değil",
|
||||||
|
"6447e6b342a2c51ab0bc53b3cbdf3742": "Sıralama çakışması: \"{0}\", \"{1}\" sonrasına eklenemez; karşıt sıra belirtilmiş",
|
||||||
|
"70654dc6eb565613a33344efed3de998": "Önyükleme komut dosyasının yüklenmesi başarısız oldu: {0}\n{1}",
|
||||||
|
"7f7bdcadb75abfef1bd8a126d547dd6d": "{0} geçerli bir değere çözülmüyor, {1} olarak döndürüldü. \"{2}\" ortam değişkeninde ya da {{app.get()}} ile çözülebilir olmalıdır.",
|
||||||
|
"91a742b7c3568cf6b6755741a70b3c52": "{{phase}} \"{1}\" aşamasındaki {{middleware}} \"{0}\" ana yapılandırmada tanımlı değil.",
|
||||||
|
"a3aa22086ae4976cd013065c9a3ff81c": "{0} uygulanamıyor: ",
|
||||||
|
"be2cf2868ba54624fe38e9908dde5e9e": "{{model-config.json}} içindeki verileri desteklenmeyen {{1.x}} biçiminde.",
|
||||||
|
"ec551b6f2fafd8d40af801ebe5bb09f6": "{{middleware}} yönergeleri atılıyor, {{middleware}}, {{loopback}} istemcisi tarafından desteklenmiyor.",
|
||||||
|
"fdc23df1bd0fe55fe3faabcc89ff60f3": "Ara katman \"{0}\" bulunamadı: {1}"
|
||||||
|
}
|
||||||
|
|
|
@ -0,0 +1,17 @@
|
||||||
|
{
|
||||||
|
"1e5fea50eef843cbffd1d438494912c8": "无法解析路径“{0}”",
|
||||||
|
"34319676975b1abf107da7a056abb434": "标准化格式无效 -“{0}”",
|
||||||
|
"3a7049e42006e8bc19e0f4fc8df63b6b": "应用程序由不兼容的回环版本 {0} 支持。受支持的版本:{1}",
|
||||||
|
"3f93b626dd9a1c33d67490f6e71018b5": "警告:缺失主要配置文件“{0}{{.json}}”",
|
||||||
|
"4d052d84c8620730afd4a30832f11724": "无法配置未知的模型 {0}",
|
||||||
|
"4ed668e9187650d898acf97707df445a": "未在主配置中定义 {{phase}}“{0}”。",
|
||||||
|
"6447e6b342a2c51ab0bc53b3cbdf3742": "顺序冲突:不能在“{1}”之后添加“{0}”,因为已指定相反顺序",
|
||||||
|
"70654dc6eb565613a33344efed3de998": "无法装入引导脚本:{0}\n{1}",
|
||||||
|
"7f7bdcadb75abfef1bd8a126d547dd6d": "{0} 未解析为有效值,返回为 {1}。“{2}”必须可在环境变量中解析或由 {{app.get()}} 解析。",
|
||||||
|
"91a742b7c3568cf6b6755741a70b3c52": "未在主配置中定义 {{phase}}“{1}”中的 {{middleware}}“{0}”。",
|
||||||
|
"a3aa22086ae4976cd013065c9a3ff81c": "无法应用 {0}:",
|
||||||
|
"be2cf2868ba54624fe38e9908dde5e9e": "{{model-config.json}} 中的数据不是受支持的 {{1.x}} 格式。",
|
||||||
|
"ec551b6f2fafd8d40af801ebe5bb09f6": "正在丢弃 {{middleware}} 指示信息,{{loopback}} 客户机不支持 {{middleware}}。",
|
||||||
|
"fdc23df1bd0fe55fe3faabcc89ff60f3": "找不到中间件“{0}”:{1}"
|
||||||
|
}
|
||||||
|
|
|
@ -0,0 +1,17 @@
|
||||||
|
{
|
||||||
|
"1e5fea50eef843cbffd1d438494912c8": "無法解析路徑 \"{0}\"",
|
||||||
|
"34319676975b1abf107da7a056abb434": "無效的正規化格式 - \"{0}\"",
|
||||||
|
"3a7049e42006e8bc19e0f4fc8df63b6b": "'app' 採用不相容的 LoopBack 版本 {0}。支援的版本:{1}",
|
||||||
|
"3f93b626dd9a1c33d67490f6e71018b5": "警告:遺漏主要配置檔 \"{0}{{.json}}\"",
|
||||||
|
"4d052d84c8620730afd4a30832f11724": "無法配置不明模型 {0}",
|
||||||
|
"4ed668e9187650d898acf97707df445a": "主要配置中未定義 {{phase}} \"{0}\"。",
|
||||||
|
"6447e6b342a2c51ab0bc53b3cbdf3742": "排序衝突:不能將 \"{0}\" 新增至 \"{1}\" 後面,因為已指定相反順序",
|
||||||
|
"70654dc6eb565613a33344efed3de998": "載入啟動 Script 時失敗:{0}\n{1}",
|
||||||
|
"7f7bdcadb75abfef1bd8a126d547dd6d": "{0} 未解析成有效的值,傳回 {1}。\"{2}\" 必須可在環境變數中解析或由 {{app.get()}} 解析。",
|
||||||
|
"91a742b7c3568cf6b6755741a70b3c52": "主要配置中未定義 {{phase}} \"{1}\" 中的 {{middleware}} \"{0}\"。",
|
||||||
|
"a3aa22086ae4976cd013065c9a3ff81c": "無法套用 {0}:",
|
||||||
|
"be2cf2868ba54624fe38e9908dde5e9e": "{{model-config.json}} 中的資料採用不受支援的 {{1.x}} 格式。",
|
||||||
|
"ec551b6f2fafd8d40af801ebe5bb09f6": "正在捨棄 {{middleware}} 指令,{{loopback}} 用戶端不支援 {{middleware}}。",
|
||||||
|
"fdc23df1bd0fe55fe3faabcc89ff60f3": "找不到中介軟體 \"{0}\":{1}"
|
||||||
|
}
|
||||||
|
|
|
@ -0,0 +1,222 @@
|
||||||
|
// Copyright IBM Corp. 2016,2019. All Rights Reserved.
|
||||||
|
// Node module: loopback-boot
|
||||||
|
// This file is licensed under the MIT License.
|
||||||
|
// License text available at https://opensource.org/licenses/MIT
|
||||||
|
|
||||||
|
'use strict';
|
||||||
|
|
||||||
|
const _ = require('lodash');
|
||||||
|
const assert = require('assert');
|
||||||
|
const async = require('async');
|
||||||
|
const utils = require('./utils');
|
||||||
|
const path = require('path');
|
||||||
|
const pluginLoader = require('./plugin-loader');
|
||||||
|
const debug = require('debug')('loopback:boot:bootstrapper');
|
||||||
|
const Promise = require('bluebird');
|
||||||
|
const arrayToObject = require('./utils').arrayToObject;
|
||||||
|
|
||||||
|
module.exports = Bootstrapper;
|
||||||
|
|
||||||
|
function createPromiseCallback() {
|
||||||
|
let cb;
|
||||||
|
const promise = new Promise(function(resolve, reject) {
|
||||||
|
cb = function(err, data) {
|
||||||
|
if (err) return reject(err);
|
||||||
|
return resolve(data);
|
||||||
|
};
|
||||||
|
});
|
||||||
|
cb.promise = promise;
|
||||||
|
return cb;
|
||||||
|
}
|
||||||
|
|
||||||
|
const builtinPlugins = [
|
||||||
|
'application', 'datasource', 'model', 'mixin',
|
||||||
|
'middleware', 'component', 'boot-script', 'swagger',
|
||||||
|
];
|
||||||
|
|
||||||
|
const builtinPhases = [
|
||||||
|
'load', 'compile', 'starting', 'start', 'started',
|
||||||
|
];
|
||||||
|
|
||||||
|
function loadAndRegisterPlugins(bootstrapper, options) {
|
||||||
|
const loader = pluginLoader(options);
|
||||||
|
const loaderContext = {};
|
||||||
|
loader.load(loaderContext);
|
||||||
|
loader.compile(loaderContext);
|
||||||
|
|
||||||
|
for (const i in loaderContext.instructions.pluginScripts) {
|
||||||
|
bootstrapper.use('/boot/' + i, loaderContext.instructions.pluginScripts[i]);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Create a new Bootstrapper with options
|
||||||
|
* @param options
|
||||||
|
* @constructor
|
||||||
|
*/
|
||||||
|
function Bootstrapper(options) {
|
||||||
|
this.plugins = [];
|
||||||
|
options = options || {};
|
||||||
|
|
||||||
|
if (typeof options === 'string') {
|
||||||
|
options = {appRootDir: options};
|
||||||
|
}
|
||||||
|
|
||||||
|
// For setting properties without modifying the original object
|
||||||
|
options = Object.create(options);
|
||||||
|
|
||||||
|
const appRootDir = options.appRootDir = options.appRootDir || process.cwd();
|
||||||
|
const env = options.env || process.env.NODE_ENV || 'development';
|
||||||
|
const scriptExtensions = options.scriptExtensions ?
|
||||||
|
arrayToObject(options.scriptExtensions) :
|
||||||
|
require.extensions;
|
||||||
|
|
||||||
|
const appConfigRootDir = options.appConfigRootDir || appRootDir;
|
||||||
|
|
||||||
|
options.rootDir = appConfigRootDir;
|
||||||
|
options.env = env;
|
||||||
|
options.scriptExtensions = scriptExtensions;
|
||||||
|
this.options = options;
|
||||||
|
|
||||||
|
this.phases = options.phases || builtinPhases;
|
||||||
|
this.builtinPlugins = options.plugins || builtinPlugins;
|
||||||
|
assert(Array.isArray(this.phases), 'Invalid phases: ' + this.phases);
|
||||||
|
assert(Array.isArray(this.plugins), 'Invalid plugins: ' +
|
||||||
|
this.builtinPlugins);
|
||||||
|
|
||||||
|
const self = this;
|
||||||
|
self.builtinPlugins.forEach(function(p) {
|
||||||
|
const factory = require('./plugins/' + p);
|
||||||
|
self.use('/boot/' + p, factory(options));
|
||||||
|
});
|
||||||
|
|
||||||
|
try {
|
||||||
|
loadAndRegisterPlugins(self, options);
|
||||||
|
} catch (err) {
|
||||||
|
debug('Cannot load & register plugins: %s', err.stack || err);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Register a handler to a given path
|
||||||
|
* @param {String} path
|
||||||
|
* @param {Function} handler
|
||||||
|
*/
|
||||||
|
Bootstrapper.prototype.use = function(path, handler) {
|
||||||
|
const plugin = {
|
||||||
|
path: path,
|
||||||
|
handler: handler,
|
||||||
|
};
|
||||||
|
this.plugins.push(plugin);
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get a list of plugins for the given path
|
||||||
|
* @param {String} path
|
||||||
|
* @returns {*}
|
||||||
|
*/
|
||||||
|
Bootstrapper.prototype.getPlugins = function(path) {
|
||||||
|
if (path[path.length - 1] !== '/') {
|
||||||
|
path = path + '/';
|
||||||
|
}
|
||||||
|
return this.plugins.filter(function(p) {
|
||||||
|
return p.path.indexOf(path) === 0;
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get a list of extensions for the given path
|
||||||
|
* @param {String} path
|
||||||
|
* @returns {*}
|
||||||
|
*/
|
||||||
|
Bootstrapper.prototype.getExtensions = function(path) {
|
||||||
|
if (path[path.length - 1] !== '/') {
|
||||||
|
path = path + '/';
|
||||||
|
}
|
||||||
|
return this.plugins.filter(function(p) {
|
||||||
|
if (p.path.indexOf(path) === -1) return false;
|
||||||
|
const name = p.path.substring(path.length);
|
||||||
|
return name && name.indexOf('/') === -1;
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Add more phases. The order of phases is decided by the sequence of phase
|
||||||
|
* names
|
||||||
|
* @param {String[]} phases An array of phase names
|
||||||
|
* @returns {String[]} New list of phases
|
||||||
|
*/
|
||||||
|
Bootstrapper.prototype.addPhases = function(phases) {
|
||||||
|
this.phases = utils.mergePhaseNameLists(this.phases, phases || []);
|
||||||
|
return this.phases;
|
||||||
|
};
|
||||||
|
|
||||||
|
function pluginIteratorFactory(context, phase) {
|
||||||
|
return function executePluginPhase(plugin, done) {
|
||||||
|
let result;
|
||||||
|
if (typeof plugin.handler[phase] !== 'function') {
|
||||||
|
debug('Skipping %s.%s', plugin.handler.name, phase);
|
||||||
|
return done();
|
||||||
|
}
|
||||||
|
debug('Invoking %s.%s', plugin.handler.name, phase);
|
||||||
|
try {
|
||||||
|
if (plugin.handler[phase].length === 2) {
|
||||||
|
plugin.handler[phase](context, done);
|
||||||
|
} else {
|
||||||
|
result = plugin.handler[phase](context);
|
||||||
|
Promise.resolve(result)
|
||||||
|
.then(function onPluginPhaseResolved(value) {
|
||||||
|
done(null, value);
|
||||||
|
}, function onPluginPhaseRejected(err) {
|
||||||
|
debug('Unable to invoke %s.%s()', plugin.name, phase, err);
|
||||||
|
done(err);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
} catch (err) {
|
||||||
|
debug('Unable to invoke %s.%s()', plugin.name, phase, err);
|
||||||
|
done(err);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Invoke the plugins phase by phase with the given context
|
||||||
|
* @param {Object} context Context object
|
||||||
|
* @param {Function} done Callback function. If not provided, a promise will be
|
||||||
|
* returned
|
||||||
|
* @returns {*}
|
||||||
|
*/
|
||||||
|
Bootstrapper.prototype.run = function(context, done) {
|
||||||
|
if (!done) {
|
||||||
|
done = createPromiseCallback();
|
||||||
|
}
|
||||||
|
const options = this.options;
|
||||||
|
const appRootDir = options.appRootDir = options.appRootDir || process.cwd();
|
||||||
|
const env = options.env || process.env.NODE_ENV || 'development';
|
||||||
|
|
||||||
|
const appConfigRootDir = options.appConfigRootDir || appRootDir;
|
||||||
|
|
||||||
|
options.rootDir = appConfigRootDir;
|
||||||
|
options.env = env;
|
||||||
|
|
||||||
|
context = context || {};
|
||||||
|
|
||||||
|
const phases = context.phases || this.phases;
|
||||||
|
|
||||||
|
if (phases.includes('starting') || phases.includes('start')) {
|
||||||
|
context.app.booting = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
const bootPlugins = this.getExtensions('/boot');
|
||||||
|
async.eachSeries(phases, function(phase, done) {
|
||||||
|
debug('Phase %s', phase);
|
||||||
|
async.eachSeries(bootPlugins, pluginIteratorFactory(context, phase), done);
|
||||||
|
}, function(err) {
|
||||||
|
if (phases.includes('started')) {
|
||||||
|
context.app.booting = false;
|
||||||
|
}
|
||||||
|
|
||||||
|
return done(err, context);
|
||||||
|
});
|
||||||
|
return done.promise;
|
||||||
|
};
|
139
lib/bundler.js
139
lib/bundler.js
|
@ -1,45 +1,116 @@
|
||||||
var fs = require('fs');
|
// Copyright IBM Corp. 2014,2019. All Rights Reserved.
|
||||||
var path = require('path');
|
// Node module: loopback-boot
|
||||||
var commondir = require('commondir');
|
// This file is licensed under the MIT License.
|
||||||
|
// License text available at https://opensource.org/licenses/MIT
|
||||||
|
|
||||||
|
'use strict';
|
||||||
|
|
||||||
|
const fs = require('fs');
|
||||||
|
const path = require('path');
|
||||||
|
const commondir = require('commondir');
|
||||||
|
const cloneDeep = require('lodash').cloneDeep;
|
||||||
|
const g = require('./globalize');
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Add boot instructions to a browserify bundler.
|
* Add boot instructions to a browserify bundler.
|
||||||
* @param {Object} instructions Boot instructions.
|
* @param {Object} instructions Boot instructions.
|
||||||
* @param {Object} bundler A browserify object created by `browserify()`.
|
* @param {Object} bundler A browserify object created by `browserify()`.
|
||||||
*/
|
*/
|
||||||
|
module.exports = function addInstructionsToBrowserify(context, bundler) {
|
||||||
module.exports = function addInstructionsToBrowserify(instructions, bundler) {
|
addPlugins(bundler);
|
||||||
bundleScripts(instructions.files, bundler);
|
// bundlePluginScripts(context, bundler);
|
||||||
bundleInstructions(instructions, bundler);
|
bundleModelScripts(context, bundler);
|
||||||
|
bundleMixinScripts(context, bundler);
|
||||||
|
bundleComponentScripts(context, bundler);
|
||||||
|
bundleOtherScripts(context, bundler);
|
||||||
|
bundleInstructions(context, bundler);
|
||||||
};
|
};
|
||||||
|
|
||||||
function bundleScripts(files, bundler) {
|
function addPlugins(bundler) {
|
||||||
for (var key in files) {
|
const dir = path.join(__dirname, './plugins');
|
||||||
var list = files[key];
|
const files = fs.readdirSync(dir);
|
||||||
if (!list.length) continue;
|
files.forEach(function(f) {
|
||||||
|
bundler.require(path.join(dir, f),
|
||||||
|
{expose: './plugins/' + path.basename(f, '.js')});
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
var root = commondir(files[key].map(path.dirname));
|
function bundleOtherScripts(context, bundler) {
|
||||||
|
const list = context.instructions.bootScripts;
|
||||||
|
addScriptsToBundle('boot', list, bundler);
|
||||||
|
}
|
||||||
|
|
||||||
for (var ix in list) {
|
function bundlePluginScripts(context, bundler) {
|
||||||
var filepath = list[ix];
|
const list = context.instructions.pluginScripts;
|
||||||
|
addScriptsToBundle('plugins', list, bundler);
|
||||||
|
}
|
||||||
|
|
||||||
// Build a short unique id that does not expose too much
|
function bundleModelScripts(context, bundler) {
|
||||||
// information about the file system, but still preserves
|
bundleSourceFiles(context, 'models', bundler);
|
||||||
// useful information about where is the file coming from.
|
}
|
||||||
var fileid = 'loopback-boot#' + key + '#' + path.relative(root, filepath);
|
|
||||||
|
|
||||||
// Add the file to the bundle.
|
function bundleMixinScripts(context, bundler) {
|
||||||
bundler.require(filepath, { expose: fileid });
|
bundleSourceFiles(context, 'mixins', bundler);
|
||||||
|
}
|
||||||
|
|
||||||
// Rewrite the instructions entry with the new id that will be
|
function bundleComponentScripts(context, bundler) {
|
||||||
// used to load the file via `require(fileid)`.
|
bundleSourceFiles(context, 'components', bundler);
|
||||||
list[ix] = fileid;
|
}
|
||||||
}
|
|
||||||
|
function bundleSourceFiles(context, type, bundler) {
|
||||||
|
const files = context.instructions[type]
|
||||||
|
.map(function(m) { return m.sourceFile; })
|
||||||
|
.filter(function(f) { return !!f; });
|
||||||
|
|
||||||
|
const instructionToFileMapping = context.instructions[type]
|
||||||
|
.map(function(m) { return files.indexOf(m.sourceFile); });
|
||||||
|
|
||||||
|
addScriptsToBundle(type, files, bundler);
|
||||||
|
|
||||||
|
// Update `sourceFile` properties with the new paths
|
||||||
|
instructionToFileMapping.forEach(function(fileIx, sourceIx) {
|
||||||
|
if (fileIx === -1) return;
|
||||||
|
context.instructions[type][sourceIx].sourceFile = files[fileIx];
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
function addScriptsToBundle(name, list, bundler) {
|
||||||
|
if (!list.length) return;
|
||||||
|
|
||||||
|
const root = commondir(list.map(path.dirname));
|
||||||
|
|
||||||
|
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.
|
||||||
|
const fileid =
|
||||||
|
'loopback-boot#' + name + '#' + path.relative(root, filepath);
|
||||||
|
|
||||||
|
// Add the file to the bundle.
|
||||||
|
bundler.require(filepath, {expose: fileid});
|
||||||
|
|
||||||
|
// 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) {
|
function bundleInstructions(context, bundler) {
|
||||||
var instructionsString = JSON.stringify(instructions, null, 2);
|
const instructions = cloneDeep(context.instructions);
|
||||||
|
|
||||||
|
const hasMiddleware = instructions.middleware.phases.length ||
|
||||||
|
instructions.middleware.middleware.length;
|
||||||
|
if (hasMiddleware) {
|
||||||
|
g.warn(
|
||||||
|
'Discarding {{middleware}} instructions,' +
|
||||||
|
' {{loopback}} client does not support {{middleware}}.',
|
||||||
|
);
|
||||||
|
}
|
||||||
|
delete instructions.middleware;
|
||||||
|
|
||||||
|
const instructionsString = JSON.stringify(instructions, null, 2);
|
||||||
|
|
||||||
/* The following code does not work due to a bug in browserify
|
/* The following code does not work due to a bug in browserify
|
||||||
* https://github.com/substack/node-browserify/issues/771
|
* https://github.com/substack/node-browserify/issues/771
|
||||||
|
@ -49,11 +120,19 @@ function bundleInstructions(instructions, bundler) {
|
||||||
b.require(instructionsStream, { expose: 'loopback-boot#instructions' });
|
b.require(instructionsStream, { expose: 'loopback-boot#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.
|
||||||
|
if (instructions.appId)
|
||||||
|
instructionId += '-' + instructions.appId;
|
||||||
|
|
||||||
// Write the instructions to a file in our node_modules folder.
|
// Write the instructions to a file in our node_modules folder.
|
||||||
// The location should not really matter as long as it is .gitignore-ed
|
// The location should not really matter as long as it is .gitignore-ed
|
||||||
var instructionsFile = path.resolve(__dirname,
|
const instructionsFile = path.resolve(__dirname,
|
||||||
'..', 'node_modules', 'instructions.json');
|
'..', 'generated-' + instructionId + '.json');
|
||||||
|
|
||||||
fs.writeFileSync(instructionsFile, instructionsString, 'utf-8');
|
fs.writeFileSync(instructionsFile, instructionsString, 'utf-8');
|
||||||
bundler.require(instructionsFile, { expose: 'loopback-boot#instructions' });
|
|
||||||
|
const moduleName = 'loopback-boot#' + instructionId;
|
||||||
|
bundler.require(instructionsFile, {expose: moduleName});
|
||||||
}
|
}
|
||||||
|
|
126
lib/compiler.js
126
lib/compiler.js
|
@ -1,126 +0,0 @@
|
||||||
var assert = require('assert');
|
|
||||||
var fs = require('fs');
|
|
||||||
var path = require('path');
|
|
||||||
var ConfigLoader = require('./config-loader');
|
|
||||||
var debug = require('debug')('loopback:boot:compiler');
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 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 appConfig = options.app || ConfigLoader.loadAppConfig(appRootDir, env);
|
|
||||||
assertIsValidConfig('app', appConfig);
|
|
||||||
|
|
||||||
var modelsRootDir = options.modelsRootDir || appRootDir;
|
|
||||||
var modelsConfig = options.models ||
|
|
||||||
ConfigLoader.loadModels(modelsRootDir, env);
|
|
||||||
assertIsValidConfig('model', modelsConfig);
|
|
||||||
|
|
||||||
var dsRootDir = options.dsRootDir || appRootDir;
|
|
||||||
var dataSourcesConfig = options.dataSources ||
|
|
||||||
ConfigLoader.loadDataSources(dsRootDir, env);
|
|
||||||
assertIsValidConfig('data source', dataSourcesConfig);
|
|
||||||
|
|
||||||
// require directories
|
|
||||||
var modelsScripts = findScripts(path.join(modelsRootDir, 'models'));
|
|
||||||
var bootScripts = findScripts(path.join(appRootDir, 'boot'));
|
|
||||||
|
|
||||||
return {
|
|
||||||
app: appConfig,
|
|
||||||
dataSources: dataSourcesConfig,
|
|
||||||
models: modelsConfig,
|
|
||||||
files: {
|
|
||||||
models: modelsScripts,
|
|
||||||
boot: bootScripts
|
|
||||||
}
|
|
||||||
};
|
|
||||||
};
|
|
||||||
|
|
||||||
function assertIsValidConfig(name, config) {
|
|
||||||
if(config) {
|
|
||||||
assert(typeof config === 'object',
|
|
||||||
name + ' config must be a valid JSON object');
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 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) {
|
|
||||||
assert(dir, 'cannot require directory contents without directory name');
|
|
||||||
|
|
||||||
var files = tryReadDir(dir);
|
|
||||||
|
|
||||||
// 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 ext = path.extname(filename);
|
|
||||||
var stats = fs.statSync(filepath);
|
|
||||||
|
|
||||||
// only require files supported by require.extensions (.txt .md etc.)
|
|
||||||
if (stats.isFile()) {
|
|
||||||
if (ext in require.extensions)
|
|
||||||
results.push(filepath);
|
|
||||||
else
|
|
||||||
debug('Skipping file %s - unknown extension', filepath);
|
|
||||||
} else {
|
|
||||||
try {
|
|
||||||
path.join(require.resolve(filepath));
|
|
||||||
} catch(err) {
|
|
||||||
debug('Skipping directory %s - %s', filepath, err.code || err);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
return results;
|
|
||||||
}
|
|
||||||
|
|
||||||
function tryReadDir() {
|
|
||||||
try {
|
|
||||||
return fs.readdirSync.apply(fs, arguments);
|
|
||||||
} catch(e) {
|
|
||||||
return [];
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,154 +0,0 @@
|
||||||
var fs = require('fs');
|
|
||||||
var path = require('path');
|
|
||||||
|
|
||||||
var ConfigLoader = exports;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Load application config from `app.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, 'app', 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 models config from `models.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) {
|
|
||||||
/*jshint unused:false */
|
|
||||||
return tryReadJsonConfig(rootDir, 'models') || {};
|
|
||||||
};
|
|
||||||
|
|
||||||
/*-- 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);
|
|
||||||
var configs = loadConfigFiles(files);
|
|
||||||
return mergeConfigurations(configs, mergeFn);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 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) 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 fs.existsSync(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 = require(f);
|
|
||||||
Object.defineProperty(config, '_filename', {
|
|
||||||
enumerable: false,
|
|
||||||
value: f
|
|
||||||
});
|
|
||||||
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) {
|
|
||||||
for (var ds in target) {
|
|
||||||
var err = applyCustomConfig(target[ds], config[ds]);
|
|
||||||
if (err) {
|
|
||||||
throw new Error('Cannot apply ' + fileName + ' to `' + ds + '`: ' + err);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
function mergeAppConfig(target, config, fileName) {
|
|
||||||
var err = applyCustomConfig(target, config);
|
|
||||||
if (err) {
|
|
||||||
throw new Error('Cannot apply ' + fileName + ': ' + err);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
function applyCustomConfig(target, config) {
|
|
||||||
for (var key in config) {
|
|
||||||
var value = config[key];
|
|
||||||
if (typeof value === 'object') {
|
|
||||||
return 'override for the option `' + key + '` is not a value type.';
|
|
||||||
}
|
|
||||||
target[key] = value;
|
|
||||||
}
|
|
||||||
return null; // no error
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Try to read a config file with .json extension
|
|
||||||
* @param cwd Dirname of the file
|
|
||||||
* @param fileName Name of the file without extension
|
|
||||||
* @returns {Object|undefined} Content of the file, undefined if not found.
|
|
||||||
*/
|
|
||||||
function tryReadJsonConfig(cwd, fileName) {
|
|
||||||
try {
|
|
||||||
return require(path.join(cwd, fileName + '.json'));
|
|
||||||
} catch(e) {
|
|
||||||
if(e.code !== 'MODULE_NOT_FOUND') {
|
|
||||||
throw e;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
167
lib/executor.js
167
lib/executor.js
|
@ -1,167 +0,0 @@
|
||||||
var assert = require('assert');
|
|
||||||
var _ = require('underscore');
|
|
||||||
var loopback = require('loopback');
|
|
||||||
var debug = require('debug')('loopback:boot:executor');
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Execute bootstrap instructions gathered by `boot.compile`.
|
|
||||||
*
|
|
||||||
* @options {Object} app The loopback app to boot.
|
|
||||||
* @options {Object} instructions Boot instructions.
|
|
||||||
*
|
|
||||||
* @header boot.execute(instructions)
|
|
||||||
*/
|
|
||||||
|
|
||||||
module.exports = function execute(app, instructions) {
|
|
||||||
setHost(app, instructions);
|
|
||||||
setPort(app, instructions);
|
|
||||||
setApiRoot(app, instructions);
|
|
||||||
applyAppConfig(app, instructions);
|
|
||||||
|
|
||||||
setupDataSources(app, instructions);
|
|
||||||
setupModels(app, instructions);
|
|
||||||
autoAttach();
|
|
||||||
|
|
||||||
runBootScripts(app, instructions);
|
|
||||||
|
|
||||||
enableAnonymousSwagger(app, instructions);
|
|
||||||
};
|
|
||||||
|
|
||||||
function setHost(app, instructions) {
|
|
||||||
//jshint camelcase:false
|
|
||||||
var host =
|
|
||||||
process.env.npm_config_host ||
|
|
||||||
process.env.OPENSHIFT_SLS_IP ||
|
|
||||||
process.env.OPENSHIFT_NODEJS_IP ||
|
|
||||||
process.env.HOST ||
|
|
||||||
instructions.app.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, instructions) {
|
|
||||||
//jshint camelcase:false
|
|
||||||
var port = _.find([
|
|
||||||
process.env.npm_config_port,
|
|
||||||
process.env.OPENSHIFT_SLS_PORT,
|
|
||||||
process.env.OPENSHIFT_NODEJS_PORT,
|
|
||||||
process.env.PORT,
|
|
||||||
instructions.app.port,
|
|
||||||
process.env.npm_package_config_port,
|
|
||||||
app.get('port'),
|
|
||||||
3000
|
|
||||||
], _.isFinite);
|
|
||||||
|
|
||||||
if(port !== undefined) {
|
|
||||||
var portType = typeof port;
|
|
||||||
assert(portType === 'string' || portType === 'number',
|
|
||||||
'app.port must be a string or number');
|
|
||||||
app.set('port', port);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
function setApiRoot(app, instructions) {
|
|
||||||
var restApiRoot =
|
|
||||||
instructions.app.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, instructions) {
|
|
||||||
var appConfig = instructions.app;
|
|
||||||
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) {
|
|
||||||
app.dataSource(key, obj);
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
function setupModels(app, instructions) {
|
|
||||||
forEachKeyedObject(instructions.models, function(key, obj) {
|
|
||||||
app.model(key, obj);
|
|
||||||
});
|
|
||||||
|
|
||||||
runScripts(app, instructions.files.models);
|
|
||||||
}
|
|
||||||
|
|
||||||
function forEachKeyedObject(obj, fn) {
|
|
||||||
if(typeof obj !== 'object') return;
|
|
||||||
|
|
||||||
Object.keys(obj).forEach(function(key) {
|
|
||||||
fn(key, obj[key]);
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
function runScripts(app, list) {
|
|
||||||
if (!list || !list.length) return;
|
|
||||||
list.forEach(function(filepath) {
|
|
||||||
var exports = tryRequire(filepath);
|
|
||||||
if (isFunctionNotModelCtor(exports))
|
|
||||||
exports(app);
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
function isFunctionNotModelCtor(fn) {
|
|
||||||
return typeof fn === 'function' &&
|
|
||||||
!(fn.prototype instanceof loopback.Model);
|
|
||||||
}
|
|
||||||
|
|
||||||
function tryRequire(modulePath) {
|
|
||||||
try {
|
|
||||||
return require.apply(this, arguments);
|
|
||||||
} catch(e) {
|
|
||||||
if(e.code === 'MODULE_NOT_FOUND') {
|
|
||||||
debug('Warning: cannot require %s - module not found.', modulePath);
|
|
||||||
return undefined;
|
|
||||||
}
|
|
||||||
console.error('failed to require "%s"', modulePath);
|
|
||||||
throw e;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Deprecated, will be removed soon
|
|
||||||
function autoAttach() {
|
|
||||||
try {
|
|
||||||
loopback.autoAttach();
|
|
||||||
} catch(e) {
|
|
||||||
if(e.name === 'AssertionError') {
|
|
||||||
console.warn(e);
|
|
||||||
} else {
|
|
||||||
throw e;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
function runBootScripts(app, instructions) {
|
|
||||||
runScripts(app, instructions.files.boot);
|
|
||||||
}
|
|
||||||
|
|
||||||
function enableAnonymousSwagger(app, instructions) {
|
|
||||||
// disable token requirement for swagger, if available
|
|
||||||
var swagger = app.remotes().exports.swagger;
|
|
||||||
if (!swagger) return;
|
|
||||||
|
|
||||||
var appConfig = instructions.app;
|
|
||||||
var requireTokenForSwagger = appConfig.swagger &&
|
|
||||||
appConfig.swagger.requireToken;
|
|
||||||
swagger.requireToken = requireTokenForSwagger || false;
|
|
||||||
}
|
|
|
@ -0,0 +1,11 @@
|
||||||
|
// Copyright IBM Corp. 2016,2019. All Rights Reserved.
|
||||||
|
// Node module: loopback-boot
|
||||||
|
// This file is licensed under the MIT License.
|
||||||
|
// License text available at https://opensource.org/licenses/MIT
|
||||||
|
|
||||||
|
'use strict';
|
||||||
|
|
||||||
|
const path = require('path');
|
||||||
|
const SG = require('strong-globalize');
|
||||||
|
SG.SetRootDir(path.join(__dirname, '..'), {autonomousMsgLoading: 'all'});
|
||||||
|
module.exports = SG();
|
|
@ -0,0 +1,338 @@
|
||||||
|
// Copyright IBM Corp. 2016,2019. All Rights Reserved.
|
||||||
|
// Node module: loopback-boot
|
||||||
|
// This file is licensed under the MIT License.
|
||||||
|
// License text available at https://opensource.org/licenses/MIT
|
||||||
|
|
||||||
|
'use strict';
|
||||||
|
|
||||||
|
const fs = require('fs');
|
||||||
|
const path = require('path');
|
||||||
|
const debug = require('debug')('loopback:boot:plugin');
|
||||||
|
const assert = require('assert');
|
||||||
|
const _ = require('lodash');
|
||||||
|
const util = require('./utils');
|
||||||
|
const g = require('./globalize');
|
||||||
|
|
||||||
|
module.exports = PluginBase;
|
||||||
|
|
||||||
|
function PluginBase(options, name, artifact) {
|
||||||
|
this.options = options || {};
|
||||||
|
this.name = name || options.name;
|
||||||
|
this.artifact = artifact || options.artifact;
|
||||||
|
}
|
||||||
|
|
||||||
|
PluginBase.prototype.getRootDir = function() {
|
||||||
|
return this.options.rootDir;
|
||||||
|
};
|
||||||
|
|
||||||
|
PluginBase.prototype.load = function(context) {
|
||||||
|
const rootDir = this.getRootDir() || this.options.rootDir;
|
||||||
|
const env = this.options.env;
|
||||||
|
assert(this.name, 'Plugin name must to be set');
|
||||||
|
debug('Root dir: %s, env: %s, artifact: %s', rootDir, env, this.artifact);
|
||||||
|
let config = {};
|
||||||
|
if (this.options[this.name]) {
|
||||||
|
// First check if options have the corresponding config object
|
||||||
|
debug('Artifact: %s is using provided config obj instead' +
|
||||||
|
' of config file');
|
||||||
|
config = this.options[this.name];
|
||||||
|
} else {
|
||||||
|
if (this.artifact) {
|
||||||
|
config = this.loadNamed(rootDir, env, this.artifact);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// Register as context.configurations.<plugin-name>
|
||||||
|
return this.configure(context, config);
|
||||||
|
};
|
||||||
|
|
||||||
|
PluginBase.prototype.configure = function(context, config) {
|
||||||
|
config = config || {};
|
||||||
|
// Register as context.configurations.<plugin-name>
|
||||||
|
if (!context.configurations) {
|
||||||
|
context.configurations = {};
|
||||||
|
}
|
||||||
|
context.configurations[this.name] = config;
|
||||||
|
return config;
|
||||||
|
};
|
||||||
|
|
||||||
|
PluginBase.prototype.merge = function(target, config, keyPrefix) {
|
||||||
|
return this._mergeObjects(target, config, keyPrefix);
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Load named configuration.
|
||||||
|
* @param {String} rootDir Directory where to look for files.
|
||||||
|
* @param {String} env Environment, usually `process.env.NODE_ENV`
|
||||||
|
* @param {String} name
|
||||||
|
* @returns {Object}
|
||||||
|
*/
|
||||||
|
PluginBase.prototype.loadNamed = function(rootDir, env, name) {
|
||||||
|
const files = this.findConfigFiles(rootDir, env, name);
|
||||||
|
debug('Looking in dir %s for %s configs', rootDir, this.name);
|
||||||
|
if (files.length) {
|
||||||
|
debug('found %s %s files: %j', env, name, files);
|
||||||
|
files.forEach(function(f) {
|
||||||
|
debug(' %s', f);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
const configs = this._loadConfigFiles(files);
|
||||||
|
const merged = this._mergeConfigurations(configs);
|
||||||
|
|
||||||
|
debug('merged %s %s configuration %j', env, name, merged);
|
||||||
|
|
||||||
|
return merged;
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Search `rootDir` for all files containing configuration for `name`.
|
||||||
|
* @param {String} rootDir Root directory
|
||||||
|
* @param {String} env Environment, usually `process.env.NODE_ENV`
|
||||||
|
* @param {String} name Name
|
||||||
|
* @param {Array.<String>} exts An array of extension names
|
||||||
|
* @returns {Array.<String>} Array of absolute file paths.
|
||||||
|
*/
|
||||||
|
PluginBase.prototype.findConfigFiles = function(rootDir, env, name, exts) {
|
||||||
|
const master = ifExists(name + '.json');
|
||||||
|
if (!master && (ifExistsWithAnyExt(name + '.local') ||
|
||||||
|
ifExistsWithAnyExt(name + '.' + env))) {
|
||||||
|
g.warn('WARNING: Main config file "%s{{.json}}" is missing', name);
|
||||||
|
}
|
||||||
|
if (!master) return [];
|
||||||
|
|
||||||
|
const candidates = [
|
||||||
|
master,
|
||||||
|
ifExistsWithAnyExt(name + '.local'),
|
||||||
|
ifExistsWithAnyExt(name + '.' + env),
|
||||||
|
];
|
||||||
|
|
||||||
|
return candidates.filter(function(c) {
|
||||||
|
return c !== undefined;
|
||||||
|
});
|
||||||
|
|
||||||
|
function ifExists(fileName) {
|
||||||
|
const filePath = path.resolve(rootDir, fileName);
|
||||||
|
return util.fileExistsSync(filePath) ? filePath : undefined;
|
||||||
|
}
|
||||||
|
|
||||||
|
function ifExistsWithAnyExt(fileName) {
|
||||||
|
const extensions = exts || ['js', 'json'];
|
||||||
|
let file;
|
||||||
|
for (let i = 0, n = extensions.length; i < n; i++) {
|
||||||
|
file = ifExists(fileName + '.' + extensions[i]);
|
||||||
|
if (file) {
|
||||||
|
return file;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Load configuration files into an array of objects.
|
||||||
|
* Attach non-enumerable `_filename` property to each object.
|
||||||
|
* @param {Array.<String>} files
|
||||||
|
* @returns {Array.<Object>}
|
||||||
|
*/
|
||||||
|
PluginBase.prototype._loadConfigFiles = function(files) {
|
||||||
|
return files.map(function(f) {
|
||||||
|
let config = require(f);
|
||||||
|
config = _.cloneDeep(config);
|
||||||
|
Object.defineProperty(config, '_filename', {
|
||||||
|
enumerable: false,
|
||||||
|
value: f,
|
||||||
|
});
|
||||||
|
return config;
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Merge multiple configuration objects into a single one.
|
||||||
|
* @param {Array.<Object>} configObjects
|
||||||
|
*/
|
||||||
|
PluginBase.prototype._mergeConfigurations = function(configObjects) {
|
||||||
|
const result = configObjects.shift() || {};
|
||||||
|
while (configObjects.length) {
|
||||||
|
const next = configObjects.shift();
|
||||||
|
this.merge(result, next, next._filename);
|
||||||
|
}
|
||||||
|
return result;
|
||||||
|
};
|
||||||
|
|
||||||
|
PluginBase.prototype._mergeObjects = function(target, config, keyPrefix) {
|
||||||
|
for (const key in config) {
|
||||||
|
const fullKey = keyPrefix ? keyPrefix + '.' + key : key;
|
||||||
|
const err = this._mergeSingleItemOrProperty(target, config, key, fullKey);
|
||||||
|
if (err) throw err;
|
||||||
|
}
|
||||||
|
return null; // no error
|
||||||
|
};
|
||||||
|
|
||||||
|
PluginBase.prototype._mergeNamedItems = function(arr1, arr2, key) {
|
||||||
|
assert(Array.isArray(arr1), 'invalid array: ' + arr1);
|
||||||
|
assert(Array.isArray(arr2), 'invalid array: ' + arr2);
|
||||||
|
key = key || 'name';
|
||||||
|
const result = [].concat(arr1);
|
||||||
|
for (let i = 0, n = arr2.length; i < n; i++) {
|
||||||
|
const item = arr2[i];
|
||||||
|
let found = false;
|
||||||
|
if (item[key]) {
|
||||||
|
for (let j = 0, k = result.length; j < k; j++) {
|
||||||
|
if (result[j][key] === item[key]) {
|
||||||
|
this._mergeObjects(result[j], item);
|
||||||
|
found = true;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (!found) {
|
||||||
|
result.push(item);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return result;
|
||||||
|
};
|
||||||
|
|
||||||
|
PluginBase.prototype._mergeSingleItemOrProperty =
|
||||||
|
function(target, config, key, fullKey) {
|
||||||
|
const origValue = target[key];
|
||||||
|
const newValue = config[key];
|
||||||
|
|
||||||
|
if (!hasCompatibleType(origValue, newValue)) {
|
||||||
|
return 'Cannot merge values of incompatible types for the option `' +
|
||||||
|
fullKey + '`.';
|
||||||
|
}
|
||||||
|
|
||||||
|
if (Array.isArray(origValue)) {
|
||||||
|
return this._mergeArrays(origValue, newValue, fullKey);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (newValue !== null && typeof origValue === 'object') {
|
||||||
|
return this._mergeObjects(origValue, newValue, fullKey);
|
||||||
|
}
|
||||||
|
|
||||||
|
target[key] = newValue;
|
||||||
|
return null; // no error
|
||||||
|
};
|
||||||
|
|
||||||
|
PluginBase.prototype._mergeArrays = function(target, config, keyPrefix) {
|
||||||
|
if (target.length !== config.length) {
|
||||||
|
return 'Cannot merge array values of different length' +
|
||||||
|
' for the option `' + keyPrefix + '`.';
|
||||||
|
}
|
||||||
|
|
||||||
|
// Use for(;;) to iterate over undefined items, for(in) would skip them.
|
||||||
|
for (let ix = 0; ix < target.length; ix++) {
|
||||||
|
const fullKey = keyPrefix + '[' + ix + ']';
|
||||||
|
const err = this._mergeSingleItemOrProperty(target, config, ix, fullKey);
|
||||||
|
if (err) return err;
|
||||||
|
}
|
||||||
|
|
||||||
|
return null; // no error
|
||||||
|
};
|
||||||
|
|
||||||
|
function hasCompatibleType(origValue, newValue) {
|
||||||
|
if (origValue === null || origValue === undefined)
|
||||||
|
return true;
|
||||||
|
|
||||||
|
if (Array.isArray(origValue))
|
||||||
|
return Array.isArray(newValue);
|
||||||
|
|
||||||
|
if (typeof origValue === 'object')
|
||||||
|
return typeof newValue === 'object';
|
||||||
|
|
||||||
|
// Note: typeof Array() is 'object' too,
|
||||||
|
// we don't need to explicitly check array types
|
||||||
|
return typeof newValue !== 'object';
|
||||||
|
}
|
||||||
|
|
||||||
|
PluginBase.prototype.compile = function(context) {
|
||||||
|
let instructions;
|
||||||
|
if (typeof this.buildInstructions === 'function') {
|
||||||
|
const rootDir = this.options.rootDir;
|
||||||
|
const config = context.configurations[this.name] || {};
|
||||||
|
instructions = this.buildInstructions(context, rootDir, config);
|
||||||
|
} else {
|
||||||
|
instructions = context.configurations[this.name];
|
||||||
|
}
|
||||||
|
|
||||||
|
// Register as context.instructions.<plugin-name>
|
||||||
|
if (!context.instructions) {
|
||||||
|
context.instructions = {};
|
||||||
|
if (this.options.appId) {
|
||||||
|
context.instructions.appId = this.options.appId;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
context.instructions[this.name] = instructions;
|
||||||
|
|
||||||
|
return undefined;
|
||||||
|
};
|
||||||
|
|
||||||
|
const DYNAMIC_CONFIG_PARAM = /\$\{(\w+)\}$/;
|
||||||
|
function getConfigVariable(app, param, useEnvVars) {
|
||||||
|
let configVariable = param;
|
||||||
|
const match = configVariable.match(DYNAMIC_CONFIG_PARAM);
|
||||||
|
if (match) {
|
||||||
|
const varName = match[1];
|
||||||
|
if (useEnvVars && process.env[varName] !== undefined) {
|
||||||
|
debug('Dynamic Configuration: Resolved via process.env: %s as %s',
|
||||||
|
process.env[varName], param);
|
||||||
|
configVariable = process.env[varName];
|
||||||
|
} else if (app.get(varName) !== undefined) {
|
||||||
|
debug('Dynamic Configuration: Resolved via app.get(): %s as %s',
|
||||||
|
app.get(varName), param);
|
||||||
|
const appValue = app.get(varName);
|
||||||
|
configVariable = appValue;
|
||||||
|
} else {
|
||||||
|
// previously it returns the original string such as "${restApiRoot}"
|
||||||
|
// it will now return `undefined`, for the use case of
|
||||||
|
// dynamic datasources url:`undefined` to fallback to other parameters
|
||||||
|
configVariable = undefined;
|
||||||
|
g.warn('%s does not resolve to a valid value, returned as %s. ' +
|
||||||
|
'"%s" must be resolvable in Environment variable or by {{app.get()}}.',
|
||||||
|
param, configVariable, varName);
|
||||||
|
debug('Dynamic Configuration: Cannot resolve variable for `%s`, ' +
|
||||||
|
'returned as %s', varName, configVariable);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return configVariable;
|
||||||
|
}
|
||||||
|
|
||||||
|
PluginBase.prototype.getUpdatedConfigObject = function(context, config, opts) {
|
||||||
|
const app = context.app;
|
||||||
|
const useEnvVars = opts && opts.useEnvVars;
|
||||||
|
|
||||||
|
function interpolateVariables(config) {
|
||||||
|
// config is a string and contains a config variable ('${var}')
|
||||||
|
if (typeof config === 'string')
|
||||||
|
return getConfigVariable(app, config, useEnvVars);
|
||||||
|
|
||||||
|
// anything but an array or object
|
||||||
|
if (typeof config !== 'object' || config == null)
|
||||||
|
return config;
|
||||||
|
|
||||||
|
// recurse into array elements
|
||||||
|
if (Array.isArray(config))
|
||||||
|
return config.map(interpolateVariables);
|
||||||
|
|
||||||
|
// Not a plain object. Examples: RegExp, Date,
|
||||||
|
if (!config.constructor || config.constructor !== Object)
|
||||||
|
return config;
|
||||||
|
|
||||||
|
// recurse into object props
|
||||||
|
const interpolated = {};
|
||||||
|
Object.keys(config).forEach(function(configKey) {
|
||||||
|
const value = config[configKey];
|
||||||
|
if (Array.isArray(value)) {
|
||||||
|
interpolated[configKey] = value.map(interpolateVariables);
|
||||||
|
} else if (typeof value === 'string') {
|
||||||
|
interpolated[configKey] = getConfigVariable(app, value, useEnvVars);
|
||||||
|
} else if (value === null) {
|
||||||
|
interpolated[configKey] = value;
|
||||||
|
} else if (typeof value === 'object' && Object.keys(value).length) {
|
||||||
|
interpolated[configKey] = interpolateVariables(value);
|
||||||
|
} else {
|
||||||
|
interpolated[configKey] = value;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
return interpolated;
|
||||||
|
}
|
||||||
|
return interpolateVariables(config);
|
||||||
|
};
|
|
@ -0,0 +1,66 @@
|
||||||
|
// Copyright IBM Corp. 2016,2019. All Rights Reserved.
|
||||||
|
// Node module: loopback-boot
|
||||||
|
// This file is licensed under the MIT License.
|
||||||
|
// License text available at https://opensource.org/licenses/MIT
|
||||||
|
|
||||||
|
'use strict';
|
||||||
|
|
||||||
|
const util = require('util');
|
||||||
|
const utils = require('./utils');
|
||||||
|
const path = require('path');
|
||||||
|
const async = require('async');
|
||||||
|
const debug = require('debug')('loopback:boot:plugin-loader');
|
||||||
|
const PluginBase = require('./plugin-base');
|
||||||
|
const _ = require('lodash');
|
||||||
|
|
||||||
|
module.exports = function(options) {
|
||||||
|
return new PluginScript(options);
|
||||||
|
};
|
||||||
|
|
||||||
|
function PluginScript(options) {
|
||||||
|
PluginBase.call(this, options, 'pluginScripts', null);
|
||||||
|
}
|
||||||
|
|
||||||
|
util.inherits(PluginScript, PluginBase);
|
||||||
|
|
||||||
|
PluginScript.prototype.load = function(context) {
|
||||||
|
const options = this.options;
|
||||||
|
const appRootDir = options.rootDir;
|
||||||
|
// require directories
|
||||||
|
let pluginDirs = options.pluginDirs || []; // precedence
|
||||||
|
pluginDirs = pluginDirs.concat(path.join(appRootDir, 'plugins'));
|
||||||
|
utils.resolveRelativePaths(pluginDirs, appRootDir);
|
||||||
|
|
||||||
|
let pluginScripts = options.pluginScripts || [];
|
||||||
|
utils.resolveRelativePaths(pluginScripts, appRootDir);
|
||||||
|
|
||||||
|
pluginDirs.forEach(function(dir) {
|
||||||
|
pluginScripts = pluginScripts.concat(
|
||||||
|
utils.findScripts(dir, options.scriptExtensions),
|
||||||
|
);
|
||||||
|
const envdir = dir + '/' + options.env;
|
||||||
|
pluginScripts = pluginScripts.concat(
|
||||||
|
utils.findScripts(envdir, options.scriptExtensions),
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
|
pluginScripts = _.uniq(pluginScripts);
|
||||||
|
debug('Plugin scripts: %j', pluginScripts);
|
||||||
|
this.configure(context, pluginScripts);
|
||||||
|
return pluginScripts;
|
||||||
|
};
|
||||||
|
|
||||||
|
PluginScript.prototype.compile = function(context) {
|
||||||
|
const pluginScripts = context.configurations.pluginScripts;
|
||||||
|
context.instructions = context.instructions || {};
|
||||||
|
const plugins = context.instructions.pluginScripts = {};
|
||||||
|
const self = this;
|
||||||
|
pluginScripts.forEach(function(ps) {
|
||||||
|
debug('Loading %s', ps);
|
||||||
|
const factory = require(ps);
|
||||||
|
const handler = factory(self.options);
|
||||||
|
const name = handler.name || path.basename(ps, '.js');
|
||||||
|
debug('Loaded plugin name: %s', name);
|
||||||
|
plugins[name] = handler;
|
||||||
|
});
|
||||||
|
};
|
|
@ -0,0 +1,135 @@
|
||||||
|
// Copyright IBM Corp. 2016,2019. All Rights Reserved.
|
||||||
|
// Node module: loopback-boot
|
||||||
|
// This file is licensed under the MIT License.
|
||||||
|
// License text available at https://opensource.org/licenses/MIT
|
||||||
|
|
||||||
|
'use strict';
|
||||||
|
|
||||||
|
const util = require('util');
|
||||||
|
const assert = require('assert');
|
||||||
|
const semver = require('semver');
|
||||||
|
const PluginBase = require('../plugin-base');
|
||||||
|
const g = require('../globalize');
|
||||||
|
|
||||||
|
module.exports = function(options) {
|
||||||
|
return new Application(options);
|
||||||
|
};
|
||||||
|
|
||||||
|
function Application(options) {
|
||||||
|
PluginBase.call(this, options, 'application', 'config');
|
||||||
|
}
|
||||||
|
|
||||||
|
util.inherits(Application, PluginBase);
|
||||||
|
|
||||||
|
function assertLoopBackVersion(app) {
|
||||||
|
const RANGE = '2.x || 3.x';
|
||||||
|
|
||||||
|
const loopback = app.loopback;
|
||||||
|
// remove any pre-release tag from the version string,
|
||||||
|
// because semver has special treatment of pre-release versions,
|
||||||
|
// while loopback-boot treats pre-releases the same way as regular versions
|
||||||
|
const version = (loopback.version || '1.0.0').replace(/-.*$/, '');
|
||||||
|
if (!semver.satisfies(version, RANGE)) {
|
||||||
|
const msg = g.f(
|
||||||
|
'The `app` is powered by an incompatible loopback version %s. ' +
|
||||||
|
'Supported versions: %s',
|
||||||
|
loopback.version || '(unknown)',
|
||||||
|
RANGE,
|
||||||
|
);
|
||||||
|
throw new Error(msg);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function setEnv(app, env) {
|
||||||
|
if (env !== undefined)
|
||||||
|
app.set('env', env);
|
||||||
|
}
|
||||||
|
|
||||||
|
function setHost(app, appConfig) {
|
||||||
|
// jscs:disable requireCamelCaseOrUpperCaseIdentifiers
|
||||||
|
const host =
|
||||||
|
process.env.npm_config_host ||
|
||||||
|
process.env.OPENSHIFT_SLS_IP ||
|
||||||
|
process.env.OPENSHIFT_NODEJS_IP ||
|
||||||
|
process.env.HOST ||
|
||||||
|
process.env.VCAP_APP_HOST ||
|
||||||
|
appConfig.host ||
|
||||||
|
process.env.npm_package_config_host ||
|
||||||
|
app.get('host');
|
||||||
|
|
||||||
|
if (host !== undefined) {
|
||||||
|
assert(typeof host === 'string', 'app.host must be a string');
|
||||||
|
app.set('host', host);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function setPort(app, appConfig) {
|
||||||
|
// jscs:disable requireCamelCaseOrUpperCaseIdentifiers
|
||||||
|
const port = find([
|
||||||
|
process.env.npm_config_port,
|
||||||
|
process.env.OPENSHIFT_SLS_PORT,
|
||||||
|
process.env.OPENSHIFT_NODEJS_PORT,
|
||||||
|
process.env.PORT,
|
||||||
|
process.env.VCAP_APP_PORT,
|
||||||
|
appConfig.port,
|
||||||
|
process.env.npm_package_config_port,
|
||||||
|
app.get('port'),
|
||||||
|
3000,
|
||||||
|
], function(p) {
|
||||||
|
return p != null;
|
||||||
|
});
|
||||||
|
|
||||||
|
if (port !== undefined) {
|
||||||
|
const portType = typeof port;
|
||||||
|
assert(portType === 'string' || portType === 'number',
|
||||||
|
'app.port must be a string or number');
|
||||||
|
app.set('port', port);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function find(array, predicate) {
|
||||||
|
return array.filter(predicate)[0];
|
||||||
|
}
|
||||||
|
|
||||||
|
function setApiRoot(app, appConfig) {
|
||||||
|
const restApiRoot =
|
||||||
|
appConfig.restApiRoot ||
|
||||||
|
app.get('restApiRoot') ||
|
||||||
|
'/api';
|
||||||
|
|
||||||
|
assert(restApiRoot !== undefined, 'app.restBasePath is required');
|
||||||
|
assert(typeof restApiRoot === 'string',
|
||||||
|
'app.restApiRoot must be a string');
|
||||||
|
assert(/^\//.test(restApiRoot),
|
||||||
|
'app.restApiRoot must start with "/"');
|
||||||
|
app.set('restApiRoot', restApiRoot);
|
||||||
|
}
|
||||||
|
|
||||||
|
function applyAppConfig(app, appConfig) {
|
||||||
|
for (const configKey in appConfig) {
|
||||||
|
const cur = app.get(configKey);
|
||||||
|
if (cur === undefined || cur === null) {
|
||||||
|
app.set(configKey, appConfig[configKey]);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Application.prototype.starting = function(context) {
|
||||||
|
const app = context.app;
|
||||||
|
assertLoopBackVersion(app);
|
||||||
|
|
||||||
|
const appConfig = context.instructions.application;
|
||||||
|
setEnv(app, context.instructions.env || this.options.env);
|
||||||
|
setHost(app, appConfig);
|
||||||
|
setPort(app, appConfig);
|
||||||
|
setApiRoot(app, appConfig);
|
||||||
|
applyAppConfig(app, appConfig);
|
||||||
|
};
|
||||||
|
|
||||||
|
Application.prototype.started = function(context, done) {
|
||||||
|
const app = context.app;
|
||||||
|
process.nextTick(function() {
|
||||||
|
app.emit('booted');
|
||||||
|
done();
|
||||||
|
});
|
||||||
|
};
|
|
@ -0,0 +1,105 @@
|
||||||
|
// Copyright IBM Corp. 2016,2019. All Rights Reserved.
|
||||||
|
// Node module: loopback-boot
|
||||||
|
// This file is licensed under the MIT License.
|
||||||
|
// License text available at https://opensource.org/licenses/MIT
|
||||||
|
|
||||||
|
'use strict';
|
||||||
|
|
||||||
|
const util = require('util');
|
||||||
|
const utils = require('../utils');
|
||||||
|
const path = require('path');
|
||||||
|
const async = require('async');
|
||||||
|
const debug = require('debug')('loopback:boot:script');
|
||||||
|
const PluginBase = require('../plugin-base');
|
||||||
|
const _ = require('lodash');
|
||||||
|
const g = require('../globalize');
|
||||||
|
|
||||||
|
module.exports = function(options) {
|
||||||
|
return new Script(options);
|
||||||
|
};
|
||||||
|
|
||||||
|
function Script(options) {
|
||||||
|
PluginBase.call(this, options, 'bootScripts', null);
|
||||||
|
}
|
||||||
|
|
||||||
|
util.inherits(Script, PluginBase);
|
||||||
|
|
||||||
|
Script.prototype.load = function(context) {
|
||||||
|
const options = this.options;
|
||||||
|
const appRootDir = options.rootDir;
|
||||||
|
// require directories
|
||||||
|
let bootDirs = options.bootDirs || []; // precedence
|
||||||
|
bootDirs = bootDirs.concat(path.join(appRootDir, 'boot'));
|
||||||
|
utils.resolveRelativePaths(bootDirs, appRootDir);
|
||||||
|
|
||||||
|
let bootScripts = options.bootScripts || [];
|
||||||
|
utils.resolveRelativePaths(bootScripts, appRootDir);
|
||||||
|
|
||||||
|
bootDirs.forEach(function(dir) {
|
||||||
|
bootScripts = bootScripts.concat(
|
||||||
|
utils.findScripts(dir, options.scriptExtensions),
|
||||||
|
);
|
||||||
|
const envdir = dir + '/' + options.env;
|
||||||
|
bootScripts = bootScripts.concat(
|
||||||
|
utils.findScripts(envdir, options.scriptExtensions),
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
|
// de-dedup boot scripts -ERS
|
||||||
|
// https://github.com/strongloop/loopback-boot/issues/64
|
||||||
|
bootScripts = _.uniq(bootScripts);
|
||||||
|
debug('Boot scripts: %j', bootScripts);
|
||||||
|
this.configure(context, bootScripts);
|
||||||
|
return bootScripts;
|
||||||
|
};
|
||||||
|
|
||||||
|
Script.prototype.start = function(context, done) {
|
||||||
|
const app = context.app;
|
||||||
|
const instructions = context.instructions[this.name];
|
||||||
|
runScripts(app, instructions, done);
|
||||||
|
};
|
||||||
|
|
||||||
|
function runScripts(app, list, callback) {
|
||||||
|
list = list || [];
|
||||||
|
const functions = [];
|
||||||
|
list.forEach(function(filepath) {
|
||||||
|
debug('Requiring script %s', filepath);
|
||||||
|
try {
|
||||||
|
let exports = require(filepath);
|
||||||
|
if (exports.__esModule) exports = exports.default;
|
||||||
|
if (typeof exports === 'function') {
|
||||||
|
debug('Exported function detected %s', filepath);
|
||||||
|
functions.push({
|
||||||
|
path: filepath,
|
||||||
|
func: exports,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
} catch (err) {
|
||||||
|
g.error('Failed loading boot script: %s\n%s', filepath, err.stack);
|
||||||
|
throw err;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
async.eachSeries(functions, function(f, done) {
|
||||||
|
debug('Running script %s', f.path);
|
||||||
|
let cb = function(err) {
|
||||||
|
debug('Async function %s %s', err ? 'failed' : 'finished', f.path);
|
||||||
|
done(err);
|
||||||
|
// Make sure done() isn't called twice, e.g. if a script returns a
|
||||||
|
// thenable object and also calls the passed callback.
|
||||||
|
cb = function() {};
|
||||||
|
};
|
||||||
|
try {
|
||||||
|
const result = f.func(app, cb);
|
||||||
|
if (result && typeof result.then === 'function') {
|
||||||
|
result.then(function() { cb(); }, cb);
|
||||||
|
} else if (f.func.length < 2) {
|
||||||
|
debug('Sync function finished %s', f.path);
|
||||||
|
done();
|
||||||
|
}
|
||||||
|
} catch (err) {
|
||||||
|
debug('Sync function failed %s', f.path, err);
|
||||||
|
done(err);
|
||||||
|
}
|
||||||
|
}, callback);
|
||||||
|
}
|
|
@ -0,0 +1,52 @@
|
||||||
|
// Copyright IBM Corp. 2016,2019. All Rights Reserved.
|
||||||
|
// Node module: loopback-boot
|
||||||
|
// This file is licensed under the MIT License.
|
||||||
|
// License text available at https://opensource.org/licenses/MIT
|
||||||
|
|
||||||
|
'use strict';
|
||||||
|
|
||||||
|
const util = require('util');
|
||||||
|
const debug = require('debug')('loopback:boot:component');
|
||||||
|
const PluginBase = require('../plugin-base');
|
||||||
|
|
||||||
|
const utils = require('../utils');
|
||||||
|
|
||||||
|
const resolveAppScriptPath = utils.resolveAppScriptPath;
|
||||||
|
|
||||||
|
module.exports = function(options) {
|
||||||
|
return new Component(options);
|
||||||
|
};
|
||||||
|
|
||||||
|
function Component(options) {
|
||||||
|
PluginBase.call(this, options, 'components', 'component-config');
|
||||||
|
}
|
||||||
|
|
||||||
|
util.inherits(Component, PluginBase);
|
||||||
|
|
||||||
|
Component.prototype.getRootDir = function() {
|
||||||
|
return this.options.componentRootDir || this.options.rootDir;
|
||||||
|
};
|
||||||
|
|
||||||
|
Component.prototype.buildInstructions = function(context, rootDir, config) {
|
||||||
|
return Object.keys(config)
|
||||||
|
.filter(function(name) {
|
||||||
|
return !!config[name];
|
||||||
|
}).map(function(name) {
|
||||||
|
return {
|
||||||
|
sourceFile: resolveAppScriptPath(rootDir, name, {strict: true}),
|
||||||
|
config: config[name],
|
||||||
|
};
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
Component.prototype.start = function(context) {
|
||||||
|
const app = context.app;
|
||||||
|
const self = this;
|
||||||
|
context.instructions[this.name].forEach(function(data) {
|
||||||
|
debug('Configuring component %j', data.sourceFile);
|
||||||
|
const configFn = require(data.sourceFile);
|
||||||
|
data.config = self.getUpdatedConfigObject(context, data.config,
|
||||||
|
{useEnvVars: true});
|
||||||
|
configFn(app, data.config);
|
||||||
|
});
|
||||||
|
};
|
|
@ -0,0 +1,41 @@
|
||||||
|
// Copyright IBM Corp. 2016,2019. All Rights Reserved.
|
||||||
|
// Node module: loopback-boot
|
||||||
|
// This file is licensed under the MIT License.
|
||||||
|
// License text available at https://opensource.org/licenses/MIT
|
||||||
|
|
||||||
|
'use strict';
|
||||||
|
|
||||||
|
const util = require('util');
|
||||||
|
const utils = require('../utils');
|
||||||
|
const PluginBase = require('../plugin-base');
|
||||||
|
const debug = require('debug')('loopback:boot:datasource');
|
||||||
|
|
||||||
|
module.exports = function(options) {
|
||||||
|
return new DataSource(options);
|
||||||
|
};
|
||||||
|
|
||||||
|
function DataSource(options) {
|
||||||
|
PluginBase.call(this, options, 'dataSources', 'datasources');
|
||||||
|
}
|
||||||
|
|
||||||
|
util.inherits(DataSource, PluginBase);
|
||||||
|
|
||||||
|
DataSource.prototype.getRootDir = function() {
|
||||||
|
return this.options.dsRootDir;
|
||||||
|
};
|
||||||
|
|
||||||
|
DataSource.prototype.start = function(context) {
|
||||||
|
const app = context.app;
|
||||||
|
const self = this;
|
||||||
|
const lazyConnect = process.env.LB_LAZYCONNECT_DATASOURCES;
|
||||||
|
utils.forEachKeyedObject(context.instructions[this.name], function(key, obj) {
|
||||||
|
obj = self.getUpdatedConfigObject(context, obj, {useEnvVars: true});
|
||||||
|
debug('Registering data source %s %j', key, obj);
|
||||||
|
if (lazyConnect) {
|
||||||
|
obj.lazyConnect =
|
||||||
|
lazyConnect === 'false' || lazyConnect === '0' ? false : true;
|
||||||
|
}
|
||||||
|
app.dataSource(key, obj);
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
|
@ -0,0 +1,266 @@
|
||||||
|
// Copyright IBM Corp. 2016,2019. All Rights Reserved.
|
||||||
|
// Node module: loopback-boot
|
||||||
|
// This file is licensed under the MIT License.
|
||||||
|
// License text available at https://opensource.org/licenses/MIT
|
||||||
|
|
||||||
|
'use strict';
|
||||||
|
|
||||||
|
const util = require('util');
|
||||||
|
const assert = require('assert');
|
||||||
|
const path = require('path');
|
||||||
|
const _ = require('lodash');
|
||||||
|
const cloneDeepWith = _.cloneDeepWith;
|
||||||
|
const cloneDeep = _.cloneDeep;
|
||||||
|
const debug = require('debug')('loopback:boot:middleware');
|
||||||
|
const PluginBase = require('../plugin-base');
|
||||||
|
const utils = require('../utils');
|
||||||
|
const g = require('../globalize');
|
||||||
|
|
||||||
|
const resolveAppScriptPath = utils.resolveAppScriptPath;
|
||||||
|
|
||||||
|
module.exports = function(options) {
|
||||||
|
return new Middleware(options);
|
||||||
|
};
|
||||||
|
|
||||||
|
function Middleware(options) {
|
||||||
|
PluginBase.call(this, options, 'middleware', 'middleware');
|
||||||
|
}
|
||||||
|
|
||||||
|
util.inherits(Middleware, PluginBase);
|
||||||
|
|
||||||
|
Middleware.prototype.getRootDir = function() {
|
||||||
|
return this.options.middlewareRootDir || this.options.rootDir;
|
||||||
|
};
|
||||||
|
|
||||||
|
Middleware.prototype.merge = function(target, config, fileName) {
|
||||||
|
let err, phase;
|
||||||
|
for (phase in config) {
|
||||||
|
if (phase in target) {
|
||||||
|
err = this.mergePhaseConfig(target[phase], config[phase], phase);
|
||||||
|
} else {
|
||||||
|
err = g.f('The {{phase}} "%s" is not defined in the main config.', phase);
|
||||||
|
}
|
||||||
|
if (err)
|
||||||
|
throw new Error(g.f('Cannot apply %s: ', fileName) + err);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
Middleware.prototype.mergePhaseConfig = function(target, config, phase) {
|
||||||
|
let err, mw;
|
||||||
|
for (mw in config) {
|
||||||
|
if (mw in target) {
|
||||||
|
const targetMiddleware = target[mw];
|
||||||
|
const configMiddleware = config[mw];
|
||||||
|
if (Array.isArray(targetMiddleware) && Array.isArray(configMiddleware)) {
|
||||||
|
// Both are arrays, combine them
|
||||||
|
target[mw] = this._mergeNamedItems(targetMiddleware, configMiddleware);
|
||||||
|
} else if (Array.isArray(targetMiddleware)) {
|
||||||
|
if (typeof configMiddleware === 'object' &&
|
||||||
|
Object.keys(configMiddleware).length) {
|
||||||
|
// Config side is an non-empty object
|
||||||
|
target[mw] = this._mergeNamedItems(targetMiddleware,
|
||||||
|
[configMiddleware]);
|
||||||
|
}
|
||||||
|
} else if (Array.isArray(configMiddleware)) {
|
||||||
|
if (typeof targetMiddleware === 'object' &&
|
||||||
|
Object.keys(targetMiddleware).length) {
|
||||||
|
// Target side is an non-empty object
|
||||||
|
target[mw] = this._mergeNamedItems([targetMiddleware],
|
||||||
|
configMiddleware);
|
||||||
|
} else {
|
||||||
|
// Target side is empty
|
||||||
|
target[mw] = configMiddleware;
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
err = this._mergeObjects(targetMiddleware, configMiddleware);
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
err = g.f('The {{middleware}} "%s" in {{phase}} "%s"' +
|
||||||
|
'is not defined in the main config.', mw, phase);
|
||||||
|
}
|
||||||
|
if (err) return err;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
Middleware.prototype.buildInstructions = function(context, rootDir, config) {
|
||||||
|
const phasesNames = Object.keys(config);
|
||||||
|
const middlewareList = [];
|
||||||
|
|
||||||
|
phasesNames.forEach(function(phase) {
|
||||||
|
const phaseConfig = config[phase];
|
||||||
|
Object.keys(phaseConfig).forEach(function(middleware) {
|
||||||
|
let allConfigs = phaseConfig[middleware];
|
||||||
|
if (!Array.isArray(allConfigs))
|
||||||
|
allConfigs = [allConfigs];
|
||||||
|
|
||||||
|
allConfigs.forEach(function(config) {
|
||||||
|
const resolved = resolveMiddlewarePath(rootDir, middleware, config);
|
||||||
|
// resolved.sourceFile will be false-y if an optional middleware
|
||||||
|
// is not resolvable.
|
||||||
|
// if a non-optional middleware is not resolvable, it will throw
|
||||||
|
// at resolveAppPath() and not reach here
|
||||||
|
if (!resolved.sourceFile) {
|
||||||
|
return g.log('Middleware "%s" not found: %s',
|
||||||
|
middleware,
|
||||||
|
resolved.optional);
|
||||||
|
}
|
||||||
|
|
||||||
|
const middlewareConfig = cloneDeep(config);
|
||||||
|
middlewareConfig.phase = phase;
|
||||||
|
|
||||||
|
if (middlewareConfig.params) {
|
||||||
|
middlewareConfig.params = resolveMiddlewareParams(
|
||||||
|
rootDir, middlewareConfig.params,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
const item = {
|
||||||
|
sourceFile: resolved.sourceFile,
|
||||||
|
config: middlewareConfig,
|
||||||
|
};
|
||||||
|
if (resolved.fragment) {
|
||||||
|
item.fragment = resolved.fragment;
|
||||||
|
}
|
||||||
|
middlewareList.push(item);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
const flattenedPhaseNames = phasesNames
|
||||||
|
.map(function getBaseName(name) {
|
||||||
|
return name.replace(/:[^:]+$/, '');
|
||||||
|
})
|
||||||
|
.filter(function differsFromPreviousItem(value, ix, source) {
|
||||||
|
// Skip duplicate entries. That happens when
|
||||||
|
// `name:before` and `name:after` are both translated to `name`
|
||||||
|
return ix === 0 || value !== source[ix - 1];
|
||||||
|
});
|
||||||
|
|
||||||
|
return {
|
||||||
|
phases: flattenedPhaseNames,
|
||||||
|
middleware: middlewareList,
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
|
function resolveMiddlewarePath(rootDir, middleware, config) {
|
||||||
|
const resolved = {
|
||||||
|
optional: !!config.optional,
|
||||||
|
};
|
||||||
|
|
||||||
|
const segments = middleware.split('#');
|
||||||
|
let pathName = segments[0];
|
||||||
|
const fragment = segments[1];
|
||||||
|
const middlewarePath = pathName;
|
||||||
|
const opts = {
|
||||||
|
strict: true,
|
||||||
|
optional: !!config.optional,
|
||||||
|
};
|
||||||
|
|
||||||
|
if (fragment) {
|
||||||
|
resolved.fragment = fragment;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (pathName.indexOf('./') === 0 || pathName.indexOf('../') === 0) {
|
||||||
|
// Relative path
|
||||||
|
pathName = path.resolve(rootDir, pathName);
|
||||||
|
}
|
||||||
|
|
||||||
|
const resolveOpts = _.extend(opts, {
|
||||||
|
// Workaround for strong-agent to allow probes to detect that
|
||||||
|
// strong-express-middleware was loaded: exclude the path to the
|
||||||
|
// module main file from the source file path.
|
||||||
|
// For example, return
|
||||||
|
// node_modules/strong-express-metrics
|
||||||
|
// instead of
|
||||||
|
// node_modules/strong-express-metrics/index.js
|
||||||
|
fullResolve: false,
|
||||||
|
});
|
||||||
|
const sourceFile = resolveAppScriptPath(rootDir, middlewarePath, resolveOpts);
|
||||||
|
|
||||||
|
if (!fragment) {
|
||||||
|
resolved.sourceFile = sourceFile;
|
||||||
|
return resolved;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Try to require the module and check if <module>.<fragment> is a valid
|
||||||
|
// function
|
||||||
|
const m = require(sourceFile);
|
||||||
|
if (typeof m[fragment] === 'function') {
|
||||||
|
resolved.sourceFile = sourceFile;
|
||||||
|
return resolved;
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
* module/server/middleware/fragment
|
||||||
|
* module/middleware/fragment
|
||||||
|
*/
|
||||||
|
const candidates = [
|
||||||
|
pathName + '/server/middleware/' + fragment,
|
||||||
|
pathName + '/middleware/' + fragment,
|
||||||
|
// TODO: [rfeng] Should we support the following flavors?
|
||||||
|
// pathName + '/lib/' + fragment,
|
||||||
|
// pathName + '/' + fragment
|
||||||
|
];
|
||||||
|
|
||||||
|
let err, ix;
|
||||||
|
for (ix in candidates) {
|
||||||
|
try {
|
||||||
|
resolved.sourceFile = resolveAppScriptPath(rootDir, candidates[ix], opts);
|
||||||
|
delete resolved.fragment;
|
||||||
|
return resolved;
|
||||||
|
} catch (e) {
|
||||||
|
// Report the error for the first candidate when no candidate matches
|
||||||
|
if (!err) err = e;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
throw err;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Match values starting with `$!./` or `$!../`
|
||||||
|
const MIDDLEWARE_PATH_PARAM_REGEX = /^\$!(\.\/|\.\.\/)/;
|
||||||
|
|
||||||
|
function resolveMiddlewareParams(rootDir, params) {
|
||||||
|
return cloneDeepWith(params, function resolvePathParam(value) {
|
||||||
|
if (typeof value === 'string' && MIDDLEWARE_PATH_PARAM_REGEX.test(value)) {
|
||||||
|
return path.resolve(rootDir, value.slice(2));
|
||||||
|
} else {
|
||||||
|
return undefined; // no change
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
Middleware.prototype.start = function(context) {
|
||||||
|
const self = this;
|
||||||
|
const app = context.app;
|
||||||
|
const instructions = context.instructions.middleware;
|
||||||
|
if (!instructions) {
|
||||||
|
// the browserified client does not support middleware
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Phases can be empty
|
||||||
|
const phases = instructions.phases || [];
|
||||||
|
assert(Array.isArray(phases),
|
||||||
|
'Middleware phases must be an array');
|
||||||
|
|
||||||
|
const middleware = instructions.middleware;
|
||||||
|
assert(Array.isArray(middleware),
|
||||||
|
'Middleware must be an array');
|
||||||
|
|
||||||
|
debug('Defining middleware phases %j', phases);
|
||||||
|
app.defineMiddlewarePhases(phases);
|
||||||
|
|
||||||
|
middleware.forEach(function(data) {
|
||||||
|
debug('Configuring middleware %j%s', data.sourceFile,
|
||||||
|
data.fragment ? ('#' + data.fragment) : '');
|
||||||
|
let factory = require(data.sourceFile);
|
||||||
|
if (data.fragment) {
|
||||||
|
factory = factory[data.fragment].bind(factory);
|
||||||
|
}
|
||||||
|
assert(typeof factory === 'function',
|
||||||
|
'Middleware factory must be a function');
|
||||||
|
data.config = self.getUpdatedConfigObject(context, data.config,
|
||||||
|
{useEnvVars: true});
|
||||||
|
app.middlewareFromConfig(factory, data.config);
|
||||||
|
});
|
||||||
|
};
|
|
@ -0,0 +1,200 @@
|
||||||
|
// Copyright IBM Corp. 2016,2019. All Rights Reserved.
|
||||||
|
// Node module: loopback-boot
|
||||||
|
// This file is licensed under the MIT License.
|
||||||
|
// License text available at https://opensource.org/licenses/MIT
|
||||||
|
|
||||||
|
'use strict';
|
||||||
|
|
||||||
|
const util = require('util');
|
||||||
|
const fs = require('fs');
|
||||||
|
const path = require('path');
|
||||||
|
const PluginBase = require('../plugin-base');
|
||||||
|
const _ = require('lodash');
|
||||||
|
const debug = require('debug')('loopback:boot:mixin');
|
||||||
|
const utils = require('../utils');
|
||||||
|
const g = require('../globalize');
|
||||||
|
|
||||||
|
const tryResolveAppPath = utils.tryResolveAppPath;
|
||||||
|
const getExcludedExtensions = utils.getExcludedExtensions;
|
||||||
|
const findScripts = utils.findScripts;
|
||||||
|
const FILE_EXTENSION_JSON = utils.FILE_EXTENSION_JSON;
|
||||||
|
|
||||||
|
module.exports = function(options) {
|
||||||
|
return new Mixin(options);
|
||||||
|
};
|
||||||
|
|
||||||
|
function Mixin(options) {
|
||||||
|
PluginBase.call(this, options, 'mixins', null);
|
||||||
|
}
|
||||||
|
|
||||||
|
util.inherits(Mixin, PluginBase);
|
||||||
|
|
||||||
|
Mixin.prototype.buildInstructions = function(context, rootDir, config) {
|
||||||
|
const modelsMeta = context.configurations.mixins._meta || {};
|
||||||
|
const modelInstructions = context.instructions.models;
|
||||||
|
const mixinSources = this.options.mixinSources || modelsMeta.mixins ||
|
||||||
|
['./mixins'];
|
||||||
|
const scriptExtensions = this.options.scriptExtensions || require.extensions;
|
||||||
|
|
||||||
|
const mixinInstructions = buildAllMixinInstructions(
|
||||||
|
rootDir, this.options, mixinSources, scriptExtensions, modelInstructions,
|
||||||
|
);
|
||||||
|
|
||||||
|
return mixinInstructions;
|
||||||
|
};
|
||||||
|
|
||||||
|
function buildAllMixinInstructions(appRootDir, options, mixinSources,
|
||||||
|
scriptExtensions, modelInstructions) {
|
||||||
|
// load mixins from `options.mixins`
|
||||||
|
let sourceFiles = options.mixins || [];
|
||||||
|
const mixinDirs = options.mixinDirs || [];
|
||||||
|
const instructionsFromMixins = loadMixins(sourceFiles, options.normalization);
|
||||||
|
|
||||||
|
// load mixins from `options.mixinDirs`
|
||||||
|
sourceFiles = findMixinDefinitions(appRootDir, mixinDirs, scriptExtensions);
|
||||||
|
if (sourceFiles === undefined) return;
|
||||||
|
const instructionsFromMixinDirs = loadMixins(sourceFiles,
|
||||||
|
options.normalization);
|
||||||
|
|
||||||
|
/* If `mixinDirs` and `mixinSources` have any directories in common,
|
||||||
|
* then remove the common directories from `mixinSources` */
|
||||||
|
mixinSources = _.difference(mixinSources, mixinDirs);
|
||||||
|
|
||||||
|
// load mixins from `options.mixinSources`
|
||||||
|
sourceFiles = findMixinDefinitions(appRootDir, mixinSources,
|
||||||
|
scriptExtensions);
|
||||||
|
if (sourceFiles === undefined) return;
|
||||||
|
let instructionsFromMixinSources = loadMixins(sourceFiles,
|
||||||
|
options.normalization);
|
||||||
|
|
||||||
|
// Fetch unique list of mixin names, used in models
|
||||||
|
let modelMixins = fetchMixinNamesUsedInModelInstructions(modelInstructions);
|
||||||
|
modelMixins = _.uniq(modelMixins);
|
||||||
|
|
||||||
|
// Filter-in only mixins, that are used in models
|
||||||
|
instructionsFromMixinSources = filterMixinInstructionsUsingWhitelist(
|
||||||
|
instructionsFromMixinSources, modelMixins,
|
||||||
|
);
|
||||||
|
|
||||||
|
const mixins = _.assign(
|
||||||
|
instructionsFromMixins,
|
||||||
|
instructionsFromMixinDirs,
|
||||||
|
instructionsFromMixinSources,
|
||||||
|
);
|
||||||
|
|
||||||
|
return _.values(mixins);
|
||||||
|
}
|
||||||
|
|
||||||
|
function findMixinDefinitions(appRootDir, sourceDirs, scriptExtensions) {
|
||||||
|
let files = [];
|
||||||
|
sourceDirs.forEach(function(dir) {
|
||||||
|
const path = tryResolveAppPath(appRootDir, dir);
|
||||||
|
if (!path) {
|
||||||
|
debug('Skipping unknown module source dir %j', dir);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
files = files.concat(findScripts(path, scriptExtensions));
|
||||||
|
});
|
||||||
|
return files;
|
||||||
|
}
|
||||||
|
|
||||||
|
function loadMixins(sourceFiles, normalization) {
|
||||||
|
const mixinInstructions = {};
|
||||||
|
sourceFiles.forEach(function(filepath) {
|
||||||
|
const dir = path.dirname(filepath);
|
||||||
|
const ext = path.extname(filepath);
|
||||||
|
let name = path.basename(filepath, ext);
|
||||||
|
const metafile = path.join(dir, name + FILE_EXTENSION_JSON);
|
||||||
|
|
||||||
|
name = normalizeMixinName(name, normalization);
|
||||||
|
const meta = {};
|
||||||
|
meta.name = name;
|
||||||
|
if (utils.fileExistsSync(metafile)) {
|
||||||
|
// May overwrite name, not sourceFile
|
||||||
|
_.extend(meta, require(metafile));
|
||||||
|
}
|
||||||
|
meta.sourceFile = filepath;
|
||||||
|
mixinInstructions[meta.name] = meta;
|
||||||
|
});
|
||||||
|
|
||||||
|
return mixinInstructions;
|
||||||
|
}
|
||||||
|
|
||||||
|
function fetchMixinNamesUsedInModelInstructions(modelInstructions) {
|
||||||
|
return _.flatten(modelInstructions
|
||||||
|
.map(function(model) {
|
||||||
|
return model.definition && model.definition.mixins ?
|
||||||
|
Object.keys(model.definition.mixins) : [];
|
||||||
|
}));
|
||||||
|
}
|
||||||
|
|
||||||
|
function filterMixinInstructionsUsingWhitelist(instructions, includeMixins) {
|
||||||
|
const instructionKeys = Object.keys(instructions);
|
||||||
|
includeMixins = _.intersection(instructionKeys, includeMixins);
|
||||||
|
|
||||||
|
const filteredInstructions = {};
|
||||||
|
instructionKeys.forEach(function(mixinName) {
|
||||||
|
if (includeMixins.indexOf(mixinName) !== -1) {
|
||||||
|
filteredInstructions[mixinName] = instructions[mixinName];
|
||||||
|
}
|
||||||
|
});
|
||||||
|
return filteredInstructions;
|
||||||
|
}
|
||||||
|
|
||||||
|
function normalizeMixinName(str, normalization) {
|
||||||
|
switch (normalization) {
|
||||||
|
case false:
|
||||||
|
case 'none':
|
||||||
|
return str;
|
||||||
|
|
||||||
|
case undefined:
|
||||||
|
case 'classify':
|
||||||
|
str = String(str).replace(/([A-Z]+)/g, ' $1').trim();
|
||||||
|
str = String(str).replace(/[\W_]/g, ' ').toLowerCase();
|
||||||
|
str = str.replace(/(?:^|\s|-)\S/g, function(c) {
|
||||||
|
return c.toUpperCase();
|
||||||
|
});
|
||||||
|
str = str.replace(/\s+/g, '');
|
||||||
|
return str;
|
||||||
|
|
||||||
|
case 'dasherize':
|
||||||
|
str = String(str).replace(/([A-Z]+)/g, ' $1').trim();
|
||||||
|
str = String(str).replace(/[\W_]/g, ' ').toLowerCase();
|
||||||
|
str = str.replace(/\s+/g, '-');
|
||||||
|
return str;
|
||||||
|
|
||||||
|
default:
|
||||||
|
if (typeof normalization === 'function') {
|
||||||
|
return normalization(str);
|
||||||
|
}
|
||||||
|
|
||||||
|
const err = new Error(g.f('Invalid normalization format - "%s"',
|
||||||
|
normalization));
|
||||||
|
err.code = 'INVALID_NORMALIZATION_FORMAT';
|
||||||
|
throw err;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Mixin.prototype.starting = function(context) {
|
||||||
|
const app = context.app;
|
||||||
|
const instructions = context.instructions.mixins;
|
||||||
|
|
||||||
|
const modelBuilder = (app.registry || app.loopback).modelBuilder;
|
||||||
|
const BaseClass = app.loopback.Model;
|
||||||
|
const mixins = instructions || [];
|
||||||
|
|
||||||
|
if (!modelBuilder.mixins || !mixins.length) return;
|
||||||
|
|
||||||
|
mixins.forEach(function(obj) {
|
||||||
|
debug('Requiring mixin %s', obj.sourceFile);
|
||||||
|
const mixin = require(obj.sourceFile);
|
||||||
|
|
||||||
|
if (typeof mixin === 'function' || mixin.prototype instanceof BaseClass) {
|
||||||
|
debug('Defining mixin %s', obj.name);
|
||||||
|
modelBuilder.mixins.define(obj.name, mixin); // TODO (name, mixin, meta)
|
||||||
|
} else {
|
||||||
|
debug('Skipping mixin file %s - `module.exports` is not a function' +
|
||||||
|
' or Loopback model', obj);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
};
|
|
@ -0,0 +1,319 @@
|
||||||
|
// Copyright IBM Corp. 2016,2019. All Rights Reserved.
|
||||||
|
// Node module: loopback-boot
|
||||||
|
// This file is licensed under the MIT License.
|
||||||
|
// License text available at https://opensource.org/licenses/MIT
|
||||||
|
|
||||||
|
'use strict';
|
||||||
|
|
||||||
|
const assert = require('assert');
|
||||||
|
const util = require('util');
|
||||||
|
const PluginBase = require('../plugin-base');
|
||||||
|
const path = require('path');
|
||||||
|
const debug = require('debug')('loopback:boot:model');
|
||||||
|
const _ = require('lodash');
|
||||||
|
const toposort = require('toposort');
|
||||||
|
const utils = require('../utils');
|
||||||
|
|
||||||
|
const tryReadDir = utils.tryReadDir;
|
||||||
|
const assertIsValidConfig = utils.assertIsValidConfig;
|
||||||
|
const tryResolveAppPath = utils.tryResolveAppPath;
|
||||||
|
const fixFileExtension = utils.fixFileExtension;
|
||||||
|
const g = require('../globalize');
|
||||||
|
|
||||||
|
module.exports = function(options) {
|
||||||
|
return new Model(options);
|
||||||
|
};
|
||||||
|
|
||||||
|
function Model(options) {
|
||||||
|
PluginBase.call(this, options, 'models', 'model-config');
|
||||||
|
}
|
||||||
|
|
||||||
|
util.inherits(Model, PluginBase);
|
||||||
|
|
||||||
|
Model.prototype.getRootDir = function() {
|
||||||
|
return this.options.modelsRootDir;
|
||||||
|
};
|
||||||
|
|
||||||
|
Model.prototype.load = function(context) {
|
||||||
|
const config = PluginBase.prototype.load.apply(this, arguments);
|
||||||
|
assertIsValidModelConfig(config);
|
||||||
|
return config;
|
||||||
|
};
|
||||||
|
|
||||||
|
Model.prototype.buildInstructions = function(context, rootDir, modelsConfig) {
|
||||||
|
const modelsMeta = modelsConfig._meta || {};
|
||||||
|
delete modelsConfig._meta;
|
||||||
|
context.configurations.mixins._meta = modelsMeta;
|
||||||
|
|
||||||
|
const modelSources = this.options.modelSources || modelsMeta.sources ||
|
||||||
|
['./models'];
|
||||||
|
const modelInstructions = buildAllModelInstructions(
|
||||||
|
rootDir, modelsConfig, modelSources, this.options.modelDefinitions,
|
||||||
|
this.options.scriptExtensions,
|
||||||
|
);
|
||||||
|
return modelInstructions;
|
||||||
|
};
|
||||||
|
|
||||||
|
function buildAllModelInstructions(rootDir, modelsConfig, sources,
|
||||||
|
modelDefinitions, scriptExtensions) {
|
||||||
|
let registry = verifyModelDefinitions(rootDir, modelDefinitions,
|
||||||
|
scriptExtensions);
|
||||||
|
if (!registry) {
|
||||||
|
registry = findModelDefinitions(rootDir, sources, scriptExtensions);
|
||||||
|
}
|
||||||
|
|
||||||
|
const modelNamesToBuild = addAllBaseModels(
|
||||||
|
registry,
|
||||||
|
Object.keys(modelsConfig),
|
||||||
|
);
|
||||||
|
|
||||||
|
const instructions = modelNamesToBuild
|
||||||
|
.map(function createModelInstructions(name) {
|
||||||
|
const config = modelsConfig[name];
|
||||||
|
const definition = registry[name] || {};
|
||||||
|
|
||||||
|
debug('Using model "%s"\nConfiguration: %j\nDefinition %j',
|
||||||
|
name, config, definition.definition);
|
||||||
|
|
||||||
|
return {
|
||||||
|
name: name,
|
||||||
|
config: config,
|
||||||
|
definition: definition.definition,
|
||||||
|
sourceFile: definition.sourceFile,
|
||||||
|
};
|
||||||
|
});
|
||||||
|
|
||||||
|
return sortByInheritance(instructions);
|
||||||
|
}
|
||||||
|
|
||||||
|
function addAllBaseModels(registry, modelNames) {
|
||||||
|
const result = [];
|
||||||
|
const visited = {};
|
||||||
|
|
||||||
|
while (modelNames.length) {
|
||||||
|
const name = modelNames.shift();
|
||||||
|
|
||||||
|
if (visited[name]) continue;
|
||||||
|
visited[name] = true;
|
||||||
|
result.push(name);
|
||||||
|
|
||||||
|
const definition = registry[name] && registry[name].definition;
|
||||||
|
if (!definition) continue;
|
||||||
|
|
||||||
|
const base = getBaseModelName(definition);
|
||||||
|
|
||||||
|
// ignore built-in models like User
|
||||||
|
if (!registry[base]) continue;
|
||||||
|
|
||||||
|
modelNames.push(base);
|
||||||
|
}
|
||||||
|
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
function getBaseModelName(modelDefinition) {
|
||||||
|
if (!modelDefinition)
|
||||||
|
return undefined;
|
||||||
|
|
||||||
|
return modelDefinition.base ||
|
||||||
|
modelDefinition.options && modelDefinition.options.base;
|
||||||
|
}
|
||||||
|
|
||||||
|
function sortByInheritance(instructions) {
|
||||||
|
// create edges Base name -> Model name
|
||||||
|
const edges = instructions
|
||||||
|
.map(function(inst) {
|
||||||
|
return [getBaseModelName(inst.definition), inst.name];
|
||||||
|
});
|
||||||
|
|
||||||
|
const sortedNames = toposort(edges);
|
||||||
|
|
||||||
|
const instructionsByModelName = {};
|
||||||
|
instructions.forEach(function(inst) {
|
||||||
|
instructionsByModelName[inst.name] = inst;
|
||||||
|
});
|
||||||
|
|
||||||
|
return sortedNames
|
||||||
|
// convert to instructions
|
||||||
|
.map(function(name) {
|
||||||
|
return instructionsByModelName[name];
|
||||||
|
})
|
||||||
|
// remove built-in models
|
||||||
|
.filter(function(inst) {
|
||||||
|
return !!inst;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
function verifyModelDefinitions(rootDir, modelDefinitions, scriptExtensions) {
|
||||||
|
if (!modelDefinitions || modelDefinitions.length < 1) {
|
||||||
|
return undefined;
|
||||||
|
}
|
||||||
|
|
||||||
|
const registry = {};
|
||||||
|
modelDefinitions.forEach(function(definition, idx) {
|
||||||
|
if (definition.sourceFile) {
|
||||||
|
const fullPath = path.resolve(rootDir, definition.sourceFile);
|
||||||
|
definition.sourceFile = fixFileExtension(
|
||||||
|
fullPath,
|
||||||
|
tryReadDir(path.dirname(fullPath)),
|
||||||
|
scriptExtensions,
|
||||||
|
);
|
||||||
|
|
||||||
|
if (!definition.sourceFile) {
|
||||||
|
debug('Model source code not found: %s - %s', definition.sourceFile);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
debug('Found model "%s" - %s %s',
|
||||||
|
definition.definition.name,
|
||||||
|
'from options',
|
||||||
|
definition.sourceFile ?
|
||||||
|
path.relative(rootDir, definition.sourceFile) :
|
||||||
|
'(no source file)');
|
||||||
|
|
||||||
|
const modelName = definition.definition.name;
|
||||||
|
if (!modelName) {
|
||||||
|
debug('Skipping model definition without Model name ' +
|
||||||
|
'(from options.modelDefinitions @ index %s)',
|
||||||
|
idx);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
registry[modelName] = definition;
|
||||||
|
});
|
||||||
|
|
||||||
|
return registry;
|
||||||
|
}
|
||||||
|
|
||||||
|
function findModelDefinitions(rootDir, sources, scriptExtensions) {
|
||||||
|
const registry = {};
|
||||||
|
|
||||||
|
sources.forEach(function(src) {
|
||||||
|
const srcDir = tryResolveAppPath(rootDir, src, {strict: false});
|
||||||
|
if (!srcDir) {
|
||||||
|
debug('Skipping unknown module source dir %j', src);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const files = tryReadDir(srcDir);
|
||||||
|
|
||||||
|
files
|
||||||
|
.filter(function(f) {
|
||||||
|
return f[0] !== '_' && path.extname(f) === '.json';
|
||||||
|
})
|
||||||
|
.forEach(function(f) {
|
||||||
|
const fullPath = path.resolve(srcDir, f);
|
||||||
|
const entry = loadModelDefinition(rootDir, fullPath, files,
|
||||||
|
scriptExtensions);
|
||||||
|
const modelName = entry.definition.name;
|
||||||
|
if (!modelName) {
|
||||||
|
debug('Skipping model definition without Model name: %s',
|
||||||
|
path.relative(srcDir, fullPath));
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
registry[modelName] = entry;
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
return registry;
|
||||||
|
}
|
||||||
|
|
||||||
|
function loadModelDefinition(rootDir, jsonFile, allFiles, scriptExtensions) {
|
||||||
|
const definition = require(jsonFile);
|
||||||
|
const basename = path.basename(jsonFile, path.extname(jsonFile));
|
||||||
|
definition.name = definition.name || _.upperFirst(_.camelCase(basename));
|
||||||
|
|
||||||
|
// find a matching file with a supported extension like `.js` or `.coffee`
|
||||||
|
const sourceFile = fixFileExtension(jsonFile, allFiles, scriptExtensions);
|
||||||
|
|
||||||
|
if (sourceFile === undefined) {
|
||||||
|
debug('Model source code not found: %s', sourceFile);
|
||||||
|
}
|
||||||
|
|
||||||
|
debug('Found model "%s" - %s %s', definition.name,
|
||||||
|
path.relative(rootDir, jsonFile),
|
||||||
|
sourceFile ? path.relative(rootDir, sourceFile) : '(no source file)');
|
||||||
|
|
||||||
|
return {
|
||||||
|
definition: definition,
|
||||||
|
sourceFile: sourceFile,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
function assertIsValidModelConfig(config) {
|
||||||
|
assertIsValidConfig('model', config);
|
||||||
|
for (const name in config) {
|
||||||
|
const entry = config[name];
|
||||||
|
const options = entry.options || {};
|
||||||
|
const unsupported = entry.properties ||
|
||||||
|
entry.base || options.base ||
|
||||||
|
entry.plural || options.plural;
|
||||||
|
|
||||||
|
if (unsupported) {
|
||||||
|
throw new Error(g.f(
|
||||||
|
'The data in {{model-config.json}} ' +
|
||||||
|
'is in the unsupported {{1.x}} format.',
|
||||||
|
));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Regular expression to match built-in loopback models
|
||||||
|
const LOOPBACK_MODEL_REGEXP = new RegExp(
|
||||||
|
['', 'node_modules', 'loopback', '[^\\/\\\\]+', 'models', '[^\\/\\\\]+\\.js$']
|
||||||
|
.join('\\' + path.sep),
|
||||||
|
);
|
||||||
|
|
||||||
|
function isBuiltinLoopBackModel(app, data) {
|
||||||
|
// 1. Built-in models are exposed on the loopback object
|
||||||
|
if (!app.loopback[data.name]) return false;
|
||||||
|
|
||||||
|
// 2. Built-in models have a script file `loopback/{facet}/models/{name}.js`
|
||||||
|
const srcFile = data.sourceFile;
|
||||||
|
return srcFile &&
|
||||||
|
LOOPBACK_MODEL_REGEXP.test(srcFile);
|
||||||
|
}
|
||||||
|
|
||||||
|
Model.prototype.start = function(context) {
|
||||||
|
const app = context.app;
|
||||||
|
const instructions = context.instructions[this.name];
|
||||||
|
|
||||||
|
const registry = app.registry || app.loopback;
|
||||||
|
instructions.forEach(function(data) {
|
||||||
|
const name = data.name;
|
||||||
|
let model;
|
||||||
|
|
||||||
|
if (!data.definition) {
|
||||||
|
model = registry.getModel(name);
|
||||||
|
if (!model) {
|
||||||
|
throw new Error(g.f('Cannot configure unknown model %s', name));
|
||||||
|
}
|
||||||
|
debug('Configuring existing model %s', name);
|
||||||
|
} else if (isBuiltinLoopBackModel(app, data)) {
|
||||||
|
model = registry.getModel(name);
|
||||||
|
assert(model, 'Built-in model ' + name + ' should have been defined');
|
||||||
|
debug('Configuring built-in LoopBack model %s', name);
|
||||||
|
} else {
|
||||||
|
debug('Creating new model %s %j', name, data.definition);
|
||||||
|
model = registry.createModel(data.definition);
|
||||||
|
if (data.sourceFile) {
|
||||||
|
debug('Loading customization script %s', data.sourceFile);
|
||||||
|
const code = require(data.sourceFile);
|
||||||
|
if (typeof code === 'function') {
|
||||||
|
debug('Customizing model %s', name);
|
||||||
|
code(model);
|
||||||
|
} else {
|
||||||
|
debug('Skipping model file %s - `module.exports` is not a function',
|
||||||
|
data.sourceFile);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
data._model = model;
|
||||||
|
});
|
||||||
|
|
||||||
|
instructions.forEach(function(data) {
|
||||||
|
// Skip base models that are not exported to the app
|
||||||
|
if (!data.config) return;
|
||||||
|
|
||||||
|
app.model(data._model, data.config);
|
||||||
|
});
|
||||||
|
};
|
|
@ -0,0 +1,31 @@
|
||||||
|
// Copyright IBM Corp. 2016,2019. All Rights Reserved.
|
||||||
|
// Node module: loopback-boot
|
||||||
|
// This file is licensed under the MIT License.
|
||||||
|
// License text available at https://opensource.org/licenses/MIT
|
||||||
|
|
||||||
|
'use strict';
|
||||||
|
|
||||||
|
const util = require('util');
|
||||||
|
const PluginBase = require('../plugin-base');
|
||||||
|
|
||||||
|
module.exports = function(options) {
|
||||||
|
return new Swagger(options);
|
||||||
|
};
|
||||||
|
|
||||||
|
function Swagger(options) {
|
||||||
|
PluginBase.call(this, options, 'apis', null);
|
||||||
|
}
|
||||||
|
|
||||||
|
util.inherits(Swagger, PluginBase);
|
||||||
|
|
||||||
|
Swagger.prototype.start = function(context) {
|
||||||
|
const app = context.app;
|
||||||
|
const appConfig = context.instructions.application;
|
||||||
|
// disable token requirement for swagger, if available
|
||||||
|
const swagger = app.remotes().exports.swagger;
|
||||||
|
if (!swagger) return;
|
||||||
|
|
||||||
|
const requireTokenForSwagger = appConfig.swagger &&
|
||||||
|
appConfig.swagger.requireToken;
|
||||||
|
swagger.requireToken = requireTokenForSwagger || false;
|
||||||
|
};
|
|
@ -0,0 +1,368 @@
|
||||||
|
// 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 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.
|
||||||
|
* Replaces calls to fs.existsSync, which is deprecated (see:
|
||||||
|
* https://github.com/nodejs/node/pull/166).
|
||||||
|
*
|
||||||
|
* @param {String} filepath The absolute path to check
|
||||||
|
* @returns {Boolean} True if the file exists
|
||||||
|
*/
|
||||||
|
function fileExistsSync(filepath) {
|
||||||
|
try {
|
||||||
|
fs.statSync(filepath);
|
||||||
|
return true;
|
||||||
|
} catch (e) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
50
package.json
50
package.json
|
@ -1,6 +1,6 @@
|
||||||
{
|
{
|
||||||
"name": "loopback-boot",
|
"name": "loopback-boot",
|
||||||
"version": "1.0.0",
|
"version": "3.3.1",
|
||||||
"description": "Convention-based bootstrapper for LoopBack applications",
|
"description": "Convention-based bootstrapper for LoopBack applications",
|
||||||
"keywords": [
|
"keywords": [
|
||||||
"StrongLoop",
|
"StrongLoop",
|
||||||
|
@ -10,32 +10,42 @@
|
||||||
],
|
],
|
||||||
"repository": {
|
"repository": {
|
||||||
"type": "git",
|
"type": "git",
|
||||||
"url": "https://github.com/loobpack/loopback-boot"
|
"url": "https://github.com/strongloop/loopback-boot"
|
||||||
|
},
|
||||||
|
"engines": {
|
||||||
|
"node": ">=8"
|
||||||
},
|
},
|
||||||
"main": "index.js",
|
"main": "index.js",
|
||||||
"browser": "browser.js",
|
"browser": "browser.js",
|
||||||
"scripts": {
|
"scripts": {
|
||||||
"pretest": "jshint .",
|
"test": "mocha",
|
||||||
"test": "mocha"
|
"posttest": "npm run lint",
|
||||||
},
|
"lint": "eslint .",
|
||||||
"license": {
|
"lint:fix": "eslint . --fix"
|
||||||
"name": "Dual MIT/StrongLoop",
|
|
||||||
"url": "https://github.com/strongloop/loopback-boot/blob/master/LICENSE"
|
|
||||||
},
|
},
|
||||||
|
"license": "MIT",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"underscore": "^1.6.0",
|
"async": "^2.4.0",
|
||||||
"debug": "^0.8.1",
|
"bluebird": "^3.5.3",
|
||||||
"commondir": "0.0.1"
|
"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": {
|
"devDependencies": {
|
||||||
"loopback": "^1.5.0",
|
"browserify": "^16.2.3",
|
||||||
"mocha": "^1.19.0",
|
"chai": "^4.2.0",
|
||||||
"must": "^0.11.0",
|
"coffeeify": "^3.0.1",
|
||||||
"supertest": "^0.13.0",
|
"coffeescript": "^2.3.1",
|
||||||
"fs-extra": "^0.9.1",
|
"dirty-chai": "^2.0.1",
|
||||||
"browserify": "^4.1.8"
|
"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"
|
||||||
},
|
},
|
||||||
"peerDependencies": {
|
"author": "IBM Corp."
|
||||||
"loopback": "1.x || 2.x"
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -0,0 +1,40 @@
|
||||||
|
// Copyright IBM Corp. 2019. All Rights Reserved.
|
||||||
|
// Node module: loopback-boot
|
||||||
|
// This file is licensed under the MIT License.
|
||||||
|
// License text available at https://opensource.org/licenses/MIT
|
||||||
|
|
||||||
|
'use strict';
|
||||||
|
|
||||||
|
const path = require('path');
|
||||||
|
const loopback = require('loopback');
|
||||||
|
|
||||||
|
const chai = require('chai');
|
||||||
|
const dirtyChai = require('dirty-chai');
|
||||||
|
const expect = chai.expect;
|
||||||
|
chai.use(dirtyChai);
|
||||||
|
|
||||||
|
const bootLoopBackApp = require('..');
|
||||||
|
|
||||||
|
describe('bootLoopBackApp', function() {
|
||||||
|
let app;
|
||||||
|
beforeEach(function() {
|
||||||
|
app = loopback();
|
||||||
|
});
|
||||||
|
|
||||||
|
it('sets app.booting immediately', function() {
|
||||||
|
const appDir = path.join(__dirname, './fixtures/empty-app');
|
||||||
|
|
||||||
|
// Start the bootstrapper
|
||||||
|
const promise = bootLoopBackApp(app, appDir);
|
||||||
|
|
||||||
|
// Still in the original turn of the event loop,
|
||||||
|
// verify that the app is signalling "boot in progress"
|
||||||
|
expect(app.booting).to.equal(true);
|
||||||
|
|
||||||
|
// Wait for bootstrapper to finish
|
||||||
|
return promise.then(() => {
|
||||||
|
// Verify that app is signalling "boot has finished"
|
||||||
|
expect(app.booting).to.equal(false);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
|
@ -0,0 +1,122 @@
|
||||||
|
// Copyright IBM Corp. 2016,2019. All Rights Reserved.
|
||||||
|
// Node module: loopback-boot
|
||||||
|
// This file is licensed under the MIT License.
|
||||||
|
// License text available at https://opensource.org/licenses/MIT
|
||||||
|
|
||||||
|
'use strict';
|
||||||
|
|
||||||
|
const path = require('path');
|
||||||
|
const loopback = require('loopback');
|
||||||
|
|
||||||
|
const chai = require('chai');
|
||||||
|
const dirtyChai = require('dirty-chai');
|
||||||
|
const expect = chai.expect;
|
||||||
|
chai.use(dirtyChai);
|
||||||
|
|
||||||
|
const Bootstrapper = require('../lib/bootstrapper');
|
||||||
|
|
||||||
|
describe('Bootstrapper', function() {
|
||||||
|
let app;
|
||||||
|
beforeEach(function() {
|
||||||
|
app = loopback();
|
||||||
|
process.bootFlags = [];
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should honor options.phases', function(done) {
|
||||||
|
const options = {
|
||||||
|
app: app,
|
||||||
|
appRootDir: path.join(__dirname, './fixtures/simple-app'),
|
||||||
|
phases: ['load'],
|
||||||
|
};
|
||||||
|
|
||||||
|
const bootstrapper = new Bootstrapper(options);
|
||||||
|
|
||||||
|
const context = {
|
||||||
|
app: app,
|
||||||
|
};
|
||||||
|
|
||||||
|
bootstrapper.run(context, function(err) {
|
||||||
|
if (err) return done(err);
|
||||||
|
const configs = context.configurations;
|
||||||
|
expect(configs.application, 'application').to.be.an('object');
|
||||||
|
expect(configs.bootScripts, 'bootScripts').to.be.an('array');
|
||||||
|
expect(configs.middleware, 'middleware').to.be.an('object');
|
||||||
|
expect(configs.models, 'models').to.be.an('object');
|
||||||
|
expect(configs.tracker, 'tracker').to.eql('load');
|
||||||
|
expect(context.instructions, 'instructions').to.be.undefined();
|
||||||
|
expect(process.bootFlags.length).to.eql(0);
|
||||||
|
done();
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should honor options.plugins', function(done) {
|
||||||
|
const options = {
|
||||||
|
app: app,
|
||||||
|
appRootDir: path.join(__dirname, './fixtures/simple-app'),
|
||||||
|
plugins: ['application', 'boot-script'],
|
||||||
|
};
|
||||||
|
|
||||||
|
const bootstrapper = new Bootstrapper(options);
|
||||||
|
|
||||||
|
const context = {
|
||||||
|
app: app,
|
||||||
|
};
|
||||||
|
|
||||||
|
bootstrapper.run(context, function(err) {
|
||||||
|
if (err) return done(err);
|
||||||
|
const configs = context.configurations;
|
||||||
|
const instructions = context.instructions;
|
||||||
|
expect(configs.application, 'application').to.be.an('object');
|
||||||
|
expect(configs.middleware, 'middleware').to.be.undefined();
|
||||||
|
expect(configs.models, 'models').to.be.undefined();
|
||||||
|
expect(configs.bootScripts, 'bootScripts').to.be.an('array');
|
||||||
|
expect(instructions.application, 'application').to.be.an('object');
|
||||||
|
expect(instructions.tracker, 'instruction: tracker').to.eql('compile');
|
||||||
|
expect(context.executions.tracker, 'execution: tracker').to.eql('start');
|
||||||
|
expect(process.bootFlags, 'process: bootFlags').to.eql([
|
||||||
|
'barLoaded',
|
||||||
|
'barSyncLoaded',
|
||||||
|
'fooLoaded',
|
||||||
|
'promiseLoaded',
|
||||||
|
'thenableLoaded',
|
||||||
|
'barStarted',
|
||||||
|
'barFinished',
|
||||||
|
'barSyncExecuted',
|
||||||
|
'promiseStarted',
|
||||||
|
'promiseFinished',
|
||||||
|
'thenableStarted',
|
||||||
|
'thenableFinished',
|
||||||
|
'umdLoaded',
|
||||||
|
]);
|
||||||
|
done();
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
it('searches boot file extensions specified in options.scriptExtensions',
|
||||||
|
function(done) {
|
||||||
|
const options = {
|
||||||
|
app: app,
|
||||||
|
appRootDir: path.join(__dirname, './fixtures/simple-app'),
|
||||||
|
scriptExtensions: ['.customjs', '.customjs2'],
|
||||||
|
};
|
||||||
|
|
||||||
|
const bootstrapper = new Bootstrapper(options);
|
||||||
|
|
||||||
|
const context = {
|
||||||
|
app: app,
|
||||||
|
};
|
||||||
|
|
||||||
|
bootstrapper.run(context, function(err) {
|
||||||
|
if (err) return done(err);
|
||||||
|
expect(process.bootFlags, 'process: bootFlags').to.eql([
|
||||||
|
'customjs',
|
||||||
|
'customjs2',
|
||||||
|
]);
|
||||||
|
done();
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
afterEach(function() {
|
||||||
|
delete process.bootFlags;
|
||||||
|
});
|
||||||
|
});
|
|
@ -0,0 +1,125 @@
|
||||||
|
// 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';
|
||||||
|
|
||||||
|
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
|
||||||
|
|
||||||
|
beforeEach(sandbox.reset);
|
||||||
|
|
||||||
|
it('has API for bundling and booting multiple apps', function(done) {
|
||||||
|
const app1Dir = path.resolve(__dirname, './fixtures/browser-app');
|
||||||
|
const app2Dir = path.resolve(__dirname, './fixtures/browser-app-2');
|
||||||
|
|
||||||
|
const apps = [
|
||||||
|
{
|
||||||
|
appDir: app1Dir,
|
||||||
|
appFile: './app.js',
|
||||||
|
moduleName: 'browser-app',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
appDir: app2Dir,
|
||||||
|
appFile: './app.js',
|
||||||
|
moduleName: 'browser-app2',
|
||||||
|
appId: 'browserApp2',
|
||||||
|
},
|
||||||
|
];
|
||||||
|
|
||||||
|
browserifyTestApps(apps, function(err, bundlePath) {
|
||||||
|
if (err) return done(err);
|
||||||
|
|
||||||
|
const bundledApps = executeBundledApps(bundlePath, apps, function(err) {
|
||||||
|
const app1 = bundledApps.defaultApp;
|
||||||
|
const app2 = bundledApps.browserApp2;
|
||||||
|
|
||||||
|
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');
|
||||||
|
|
||||||
|
done();
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
function browserifyTestApps(apps, next) {
|
||||||
|
const b = browserify({
|
||||||
|
debug: true,
|
||||||
|
basedir: path.resolve(__dirname, './fixtures'),
|
||||||
|
packageFilter,
|
||||||
|
});
|
||||||
|
|
||||||
|
const bundles = [];
|
||||||
|
for (const i in apps) {
|
||||||
|
const appDir = apps[i].appDir;
|
||||||
|
let appFile = apps[i].appFile;
|
||||||
|
const moduleName = apps[i].moduleName;
|
||||||
|
const appId = apps[i].appId;
|
||||||
|
|
||||||
|
appFile = path.join(appDir, appFile);
|
||||||
|
b.require(appFile, {expose: moduleName});
|
||||||
|
|
||||||
|
let opts = appDir;
|
||||||
|
if (appId) {
|
||||||
|
opts = {
|
||||||
|
appId: appId,
|
||||||
|
appRootDir: appDir,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
bundles.push(opts);
|
||||||
|
}
|
||||||
|
async.eachSeries(bundles, function(opts, done) {
|
||||||
|
boot.compileToBrowserify(opts, b, done);
|
||||||
|
}, function(err) {
|
||||||
|
exportBrowserifyToFile(b, 'browser-app-bundle.js', next);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
function executeBundledApps(bundlePath, apps, done) {
|
||||||
|
const code = fs.readFileSync(bundlePath);
|
||||||
|
const context = createBrowserLikeContext();
|
||||||
|
vm.runInContext(code, context, bundlePath);
|
||||||
|
|
||||||
|
const ids = [];
|
||||||
|
let script = 'var apps = {};\n';
|
||||||
|
for (const i in apps) {
|
||||||
|
const moduleName = apps[i].moduleName;
|
||||||
|
const id = apps[i].appId || 'defaultApp';
|
||||||
|
ids.push(id);
|
||||||
|
script += 'apps.' + id + ' = require("' + moduleName + '");\n';
|
||||||
|
}
|
||||||
|
script += 'apps;\n';
|
||||||
|
|
||||||
|
const appsInContext = vm.runInContext(script, context);
|
||||||
|
async.each(ids, function(id, done) {
|
||||||
|
appsInContext[id].once('booted', function() {
|
||||||
|
done();
|
||||||
|
});
|
||||||
|
}, function(err) {
|
||||||
|
printContextLogs(context);
|
||||||
|
done(err, appsInContext);
|
||||||
|
});
|
||||||
|
|
||||||
|
return appsInContext;
|
||||||
|
}
|
|
@ -1,91 +1,143 @@
|
||||||
var boot = require('../');
|
// Copyright IBM Corp. 2014,2019. All Rights Reserved.
|
||||||
var fs = require('fs');
|
// Node module: loopback-boot
|
||||||
var path = require('path');
|
// This file is licensed under the MIT License.
|
||||||
var expect = require('must');
|
// License text available at https://opensource.org/licenses/MIT
|
||||||
var browserify = require('browserify');
|
|
||||||
var sandbox = require('./helpers/sandbox');
|
'use strict';
|
||||||
var vm = require('vm');
|
|
||||||
|
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'});
|
||||||
|
return b;
|
||||||
|
},
|
||||||
|
|
||||||
|
coffee: function(appDir) {
|
||||||
|
const b = browserify({
|
||||||
|
basedir: appDir,
|
||||||
|
extensions: ['.coffee'],
|
||||||
|
debug: true,
|
||||||
|
packageFilter,
|
||||||
|
});
|
||||||
|
b.transform('coffeeify');
|
||||||
|
|
||||||
|
b.require('./app.coffee', {expose: 'browser-app'});
|
||||||
|
return b;
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
describe('browser support', function() {
|
describe('browser support', function() {
|
||||||
|
this.timeout(60000); // 60s to give browserify enough time to finish
|
||||||
|
|
||||||
|
beforeEach(sandbox.reset);
|
||||||
|
|
||||||
it('has API for bundling and executing boot instructions', function(done) {
|
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) {
|
browserifyTestApp(appDir, function(err, bundlePath) {
|
||||||
if (err) return done(err);
|
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
|
// configured in fixtures/browser-app/component-config.json
|
||||||
expect(app.settings).to.have.property('custom-key', 'custom-value');
|
// and fixtures/browser-app/components/dummy-component.js
|
||||||
|
expect(app.dummyComponentOptions).to.eql({option: 'value'});
|
||||||
|
done();
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
done();
|
it('loads mixins', function(done) {
|
||||||
|
const appDir = path.resolve(__dirname, './fixtures/browser-app');
|
||||||
|
const options = {
|
||||||
|
appRootDir: appDir,
|
||||||
|
};
|
||||||
|
|
||||||
|
browserifyTestApp(options, function(err, bundlePath) {
|
||||||
|
if (err) return done(err);
|
||||||
|
|
||||||
|
const app = executeBundledApp(bundlePath, function(err) {
|
||||||
|
const modelBuilder = app.registry.modelBuilder;
|
||||||
|
const registry = modelBuilder.mixins.mixins;
|
||||||
|
expect(Object.keys(registry)).to.eql(['TimeStamps']);
|
||||||
|
expect(app.models.Customer.timeStampsMixin).to.eql(true);
|
||||||
|
|
||||||
|
done();
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
it('supports coffee-script files', function(done) {
|
||||||
|
// add coffee-script to require.extensions
|
||||||
|
require('coffeescript/register');
|
||||||
|
|
||||||
|
const appDir = path.resolve(__dirname, './fixtures/coffee-app');
|
||||||
|
|
||||||
|
browserifyTestApp(appDir, 'coffee', function(err, bundlePath) {
|
||||||
|
if (err) return done(err);
|
||||||
|
|
||||||
|
const app = executeBundledApp(bundlePath, function(err) {
|
||||||
|
// configured in fixtures/browser-app/boot/configure.coffee
|
||||||
|
expect(app.settings).to.have.property('custom-key', 'custom-value');
|
||||||
|
expect(Object.keys(app.models)).to.include('Customer');
|
||||||
|
expect(app.models.Customer.settings).to.have.property(
|
||||||
|
'_customized',
|
||||||
|
'Customer',
|
||||||
|
);
|
||||||
|
done();
|
||||||
|
});
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
function browserifyTestApp(appDir, next) {
|
function browserifyTestApp(options, strategy, next) {
|
||||||
var b = browserify({
|
// set default args
|
||||||
basedir: appDir,
|
if (typeof strategy === 'function' && !next) {
|
||||||
});
|
next = strategy;
|
||||||
b.require('./app.js', { expose: 'browser-app' });
|
strategy = undefined;
|
||||||
|
}
|
||||||
|
if (!strategy) strategy = 'default';
|
||||||
|
|
||||||
boot.compileToBrowserify(appDir, b);
|
const appDir = typeof options === 'object' ? options.appRootDir : options;
|
||||||
|
const b = compileStrategies[strategy](appDir);
|
||||||
|
|
||||||
var bundlePath = sandbox.resolve('browser-app-bundle.js');
|
boot.compileToBrowserify(options, b, function(err) {
|
||||||
var out = fs.createWriteStream(bundlePath);
|
exportBrowserifyToFile(b, 'browser-app-bundle.js', next);
|
||||||
b.bundle({ debug: true }).pipe(out);
|
|
||||||
|
|
||||||
out.on('error', function(err) { return next(err); });
|
|
||||||
out.on('close', function() {
|
|
||||||
next(null, bundlePath);
|
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
function executeBundledApp(bundlePath) {
|
function executeBundledApp(bundlePath, done) {
|
||||||
var code = fs.readFileSync(bundlePath);
|
const code = fs.readFileSync(bundlePath);
|
||||||
var context = createBrowserLikeContext();
|
const context = createBrowserLikeContext();
|
||||||
vm.runInContext(code, context, bundlePath);
|
vm.runInContext(code, context, bundlePath);
|
||||||
var app = vm.runInContext('require("browser-app")', context);
|
const app = vm.runInContext('require("browser-app")', context);
|
||||||
|
app.once('booted', function(err) {
|
||||||
printContextLogs(context);
|
printContextLogs(context);
|
||||||
|
done(err, app);
|
||||||
|
});
|
||||||
return app;
|
return app;
|
||||||
}
|
}
|
||||||
|
|
||||||
function createBrowserLikeContext() {
|
|
||||||
return vm.createContext({
|
|
||||||
// required by browserify
|
|
||||||
XMLHttpRequest: function() { throw new Error('not implemented'); },
|
|
||||||
|
|
||||||
// used by loopback to detect browser runtime
|
|
||||||
window: {},
|
|
||||||
|
|
||||||
// allow the browserified code to log messages
|
|
||||||
// call `printContextLogs(context)` to print the accumulated messages
|
|
||||||
console: {
|
|
||||||
log: function() {
|
|
||||||
this._logs.log.push(Array.prototype.slice.call(arguments));
|
|
||||||
},
|
|
||||||
warn: function() {
|
|
||||||
this._logs.warn.push(Array.prototype.slice.call(arguments));
|
|
||||||
},
|
|
||||||
error: function() {
|
|
||||||
this._logs.error.push(Array.prototype.slice.call(arguments));
|
|
||||||
},
|
|
||||||
_logs: {
|
|
||||||
log: [],
|
|
||||||
warn: [],
|
|
||||||
error: []
|
|
||||||
},
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
function printContextLogs(context) {
|
|
||||||
for (var k in context.console._logs) {
|
|
||||||
var items = context.console._logs[k];
|
|
||||||
for (var ix in items) {
|
|
||||||
console[k].apply(console, items[ix]);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
|
@ -0,0 +1,15 @@
|
||||||
|
// 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';
|
||||||
|
|
||||||
|
const loopback = require('loopback');
|
||||||
|
const boot = require('../../../');
|
||||||
|
|
||||||
|
const app = module.exports = loopback();
|
||||||
|
boot(app, {
|
||||||
|
appId: 'browserApp2',
|
||||||
|
appRootDir: __dirname,
|
||||||
|
});
|
|
@ -0,0 +1,5 @@
|
||||||
|
{
|
||||||
|
"db": {
|
||||||
|
"connector": "remote"
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,11 @@
|
||||||
|
{
|
||||||
|
"_meta": {
|
||||||
|
"sources": [
|
||||||
|
"./models",
|
||||||
|
"loopback/common/models"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"Robot": {
|
||||||
|
"dataSource": "db"
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,11 @@
|
||||||
|
// 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';
|
||||||
|
};
|
|
@ -0,0 +1,4 @@
|
||||||
|
{
|
||||||
|
"name": "Robot",
|
||||||
|
"base": "PersistedModel"
|
||||||
|
}
|
|
@ -1,5 +1,12 @@
|
||||||
var loopback = require('loopback');
|
// Copyright IBM Corp. 2014,2019. All Rights Reserved.
|
||||||
var boot = require('../../../');
|
// Node module: loopback-boot
|
||||||
|
// This file is licensed under the MIT License.
|
||||||
|
// License text available at https://opensource.org/licenses/MIT
|
||||||
|
|
||||||
var app = module.exports = loopback();
|
'use strict';
|
||||||
boot(app);
|
|
||||||
|
const loopback = require('loopback');
|
||||||
|
const boot = require('../../../');
|
||||||
|
|
||||||
|
const app = module.exports = loopback();
|
||||||
|
boot(app, __dirname);
|
||||||
|
|
|
@ -1,3 +1,10 @@
|
||||||
|
// 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) {
|
module.exports = function(app) {
|
||||||
app.set('custom-key', 'custom-value');
|
app.set('custom-key', 'custom-value');
|
||||||
};
|
};
|
||||||
|
|
|
@ -0,0 +1,5 @@
|
||||||
|
{
|
||||||
|
"./components/dummy-component": {
|
||||||
|
"option": "value"
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,10 @@
|
||||||
|
// 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;
|
||||||
|
};
|
|
@ -0,0 +1,5 @@
|
||||||
|
{
|
||||||
|
"db": {
|
||||||
|
"connector": "remote"
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,10 @@
|
||||||
|
// 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;
|
||||||
|
};
|
|
@ -0,0 +1,11 @@
|
||||||
|
{
|
||||||
|
"_meta": {
|
||||||
|
"sources": [
|
||||||
|
"./models",
|
||||||
|
"loopback/common/models"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"Customer": {
|
||||||
|
"dataSource": "db"
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,11 @@
|
||||||
|
// 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';
|
||||||
|
};
|
|
@ -0,0 +1,5 @@
|
||||||
|
{
|
||||||
|
"name": "Customer",
|
||||||
|
"base": "User",
|
||||||
|
"mixins": {"TimeStamps": {} }
|
||||||
|
}
|
|
@ -0,0 +1,5 @@
|
||||||
|
loopback = require 'loopback'
|
||||||
|
boot = require '../../../'
|
||||||
|
|
||||||
|
module.exports = client = loopback()
|
||||||
|
boot(client, __dirname)
|
|
@ -0,0 +1,2 @@
|
||||||
|
module.exports = (app) ->
|
||||||
|
app.set 'custom-key', 'custom-value'
|
|
@ -0,0 +1,5 @@
|
||||||
|
{
|
||||||
|
"db": {
|
||||||
|
"connector": "remote"
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,5 @@
|
||||||
|
{
|
||||||
|
"Customer": {
|
||||||
|
"dataSource": "db"
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,3 @@
|
||||||
|
module.exports = (Customer) ->
|
||||||
|
Customer.settings._customized = 'Customer'
|
||||||
|
Customer.base.settings._customized = 'Base'
|
|
@ -0,0 +1,4 @@
|
||||||
|
{
|
||||||
|
"name": "Customer",
|
||||||
|
"base": "User"
|
||||||
|
}
|
|
@ -0,0 +1,2 @@
|
||||||
|
{
|
||||||
|
}
|
|
@ -0,0 +1,11 @@
|
||||||
|
// Copyright IBM Corp. 2015,2019. All Rights Reserved.
|
||||||
|
// Node module: loopback-boot
|
||||||
|
// This file is licensed under the MIT License.
|
||||||
|
// License text available at https://opensource.org/licenses/MIT
|
||||||
|
|
||||||
|
'use strict';
|
||||||
|
|
||||||
|
process.bootFlags.push('barLoadedInTest');
|
||||||
|
module.exports = function(app, callback) {
|
||||||
|
callback();
|
||||||
|
};
|
|
@ -1,4 +1,4 @@
|
||||||
{
|
{
|
||||||
"port": 3000,
|
"port": 0,
|
||||||
"host": "127.0.0.1"
|
"host": "127.0.0.1"
|
||||||
}
|
}
|
|
@ -0,0 +1,5 @@
|
||||||
|
{
|
||||||
|
"db": {
|
||||||
|
"connector": "memory"
|
||||||
|
}
|
||||||
|
}
|
|
@ -1,5 +1,5 @@
|
||||||
{
|
{
|
||||||
"foo": {
|
"User": {
|
||||||
"dataSource": "db"
|
"dataSource": "db"
|
||||||
}
|
}
|
||||||
}
|
}
|
|
@ -0,0 +1,26 @@
|
||||||
|
// 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';
|
||||||
|
|
||||||
|
const framework = {
|
||||||
|
initialize: function(passport) {
|
||||||
|
return function(req, res, next) {
|
||||||
|
req._passport = passport;
|
||||||
|
res.setHeader('passport', 'initialized');
|
||||||
|
next();
|
||||||
|
};
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
const Passport = function() {
|
||||||
|
this._framework = framework;
|
||||||
|
};
|
||||||
|
|
||||||
|
Passport.prototype.initialize = function() {
|
||||||
|
return this._framework.initialize(this);
|
||||||
|
};
|
||||||
|
|
||||||
|
module.exports = new Passport();
|
|
@ -0,0 +1,15 @@
|
||||||
|
// 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');
|
||||||
|
process.nextTick(function() {
|
||||||
|
process.bootFlags.push('barFinished');
|
||||||
|
callback();
|
||||||
|
});
|
||||||
|
};
|
|
@ -0,0 +1,11 @@
|
||||||
|
// 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');
|
||||||
|
};
|
|
@ -0,0 +1,13 @@
|
||||||
|
// 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;
|
||||||
|
|
||||||
|
process.nextTick(cb);
|
||||||
|
};
|
|
@ -0,0 +1,6 @@
|
||||||
|
'use strict';
|
||||||
|
|
||||||
|
module.exports = function(app, callback) {
|
||||||
|
process.bootFlags.push('customjs');
|
||||||
|
callback();
|
||||||
|
};
|
|
@ -0,0 +1,6 @@
|
||||||
|
'use strict';
|
||||||
|
|
||||||
|
module.exports = function(app, callback) {
|
||||||
|
process.bootFlags.push('customjs2');
|
||||||
|
callback();
|
||||||
|
};
|
|
@ -1 +1,8 @@
|
||||||
process.loadedFooJS = true;
|
// Copyright IBM Corp. 2014,2019. All Rights Reserved.
|
||||||
|
// Node module: loopback-boot
|
||||||
|
// This file is licensed under the MIT License.
|
||||||
|
// License text available at https://opensource.org/licenses/MIT
|
||||||
|
|
||||||
|
'use strict';
|
||||||
|
|
||||||
|
process.bootFlags.push('fooLoaded');
|
||||||
|
|
|
@ -0,0 +1,15 @@
|
||||||
|
// Copyright IBM Corp. 2017,2019. All Rights Reserved.
|
||||||
|
// Node module: loopback-boot
|
||||||
|
// This file is licensed under the MIT License.
|
||||||
|
// License text available at https://opensource.org/licenses/MIT
|
||||||
|
|
||||||
|
'use strict';
|
||||||
|
|
||||||
|
const Promise = require('bluebird');
|
||||||
|
|
||||||
|
module.exports = function(app, callback) {
|
||||||
|
callback();
|
||||||
|
if (process.promiseAndCallback) {
|
||||||
|
return Promise.reject();
|
||||||
|
}
|
||||||
|
};
|
|
@ -0,0 +1,21 @@
|
||||||
|
// Copyright IBM Corp. 2017,2019. All Rights Reserved.
|
||||||
|
// Node module: loopback-boot
|
||||||
|
// This file is licensed under the MIT License.
|
||||||
|
// License text available at https://opensource.org/licenses/MIT
|
||||||
|
|
||||||
|
'use strict';
|
||||||
|
|
||||||
|
const Promise = require('bluebird');
|
||||||
|
|
||||||
|
process.bootFlags.push('promiseLoaded');
|
||||||
|
module.exports = function(app) {
|
||||||
|
process.bootFlags.push('promiseStarted');
|
||||||
|
return Promise.resolve({
|
||||||
|
then: function(onFulfill, onReject) {
|
||||||
|
process.nextTick(function() {
|
||||||
|
process.bootFlags.push('promiseFinished');
|
||||||
|
onFulfill();
|
||||||
|
});
|
||||||
|
},
|
||||||
|
});
|
||||||
|
};
|
Some files were not shown because too many files have changed in this diff Show More
Loading…
Reference in New Issue