Compare commits
7 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
39d97d6e14
|
|||
|
f496046461
|
|||
|
b3b5b169e8
|
|||
|
5d960e6186
|
|||
|
cf6d14abca
|
|||
|
faa8a31102
|
|||
|
7506d6567d
|
16
package.json
16
package.json
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@extollo/lib",
|
||||
"version": "0.3.1",
|
||||
"version": "0.4.0",
|
||||
"description": "The framework library that lifts up your code.",
|
||||
"main": "lib/index.js",
|
||||
"types": "lib/index.d.ts",
|
||||
@@ -8,12 +8,14 @@
|
||||
"lib": "lib"
|
||||
},
|
||||
"dependencies": {
|
||||
"@atao60/fse-cli": "^0.1.6",
|
||||
"@types/bcrypt": "^5.0.0",
|
||||
"@types/busboy": "^0.2.3",
|
||||
"@types/cli-table": "^0.3.0",
|
||||
"@types/mime-types": "^2.1.0",
|
||||
"@types/mkdirp": "^1.0.1",
|
||||
"@types/negotiator": "^0.6.1",
|
||||
"@types/node": "^14.14.37",
|
||||
"@types/node": "^14.17.4",
|
||||
"@types/pg": "^8.6.0",
|
||||
"@types/pluralize": "^0.0.29",
|
||||
"@types/pug": "^2.0.4",
|
||||
@@ -25,6 +27,7 @@
|
||||
"cli-table": "^0.3.6",
|
||||
"colors": "^1.4.0",
|
||||
"dotenv": "^8.2.0",
|
||||
"mime-types": "^2.1.31",
|
||||
"mkdirp": "^1.0.4",
|
||||
"negotiator": "^0.6.2",
|
||||
"pg": "^8.6.0",
|
||||
@@ -42,8 +45,9 @@
|
||||
},
|
||||
"scripts": {
|
||||
"test": "echo \"Error: no test specified\" && exit 1",
|
||||
"prebuild": "pnpm run lint",
|
||||
"prebuild": "pnpm run lint && rimraf lib",
|
||||
"build": "tsc",
|
||||
"postbuild": "fse copy --all --dereference --preserveTimestamps --keepExisting=false --quiet --errorOnExist=false src/resources lib/resources",
|
||||
"app": "tsc && node lib/index.js",
|
||||
"prepare": "pnpm run build",
|
||||
"docs:build": "typedoc --options typedoc.json",
|
||||
@@ -65,5 +69,11 @@
|
||||
"@typescript-eslint/eslint-plugin": "^4.26.0",
|
||||
"@typescript-eslint/parser": "^4.26.0",
|
||||
"eslint": "^7.27.0"
|
||||
},
|
||||
"extollo": {
|
||||
"discover": true,
|
||||
"units": {
|
||||
"discover": false
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
354
pnpm-lock.yaml
generated
354
pnpm-lock.yaml
generated
@@ -1,10 +1,12 @@
|
||||
dependencies:
|
||||
'@atao60/fse-cli': 0.1.6
|
||||
'@types/bcrypt': 5.0.0
|
||||
'@types/busboy': 0.2.3
|
||||
'@types/cli-table': 0.3.0
|
||||
'@types/mime-types': 2.1.0
|
||||
'@types/mkdirp': 1.0.1
|
||||
'@types/negotiator': 0.6.1
|
||||
'@types/node': 14.14.37
|
||||
'@types/node': 14.17.4
|
||||
'@types/pg': 8.6.0
|
||||
'@types/pluralize': 0.0.29
|
||||
'@types/pug': 2.0.4
|
||||
@@ -16,6 +18,7 @@ dependencies:
|
||||
cli-table: 0.3.6
|
||||
colors: 1.4.0
|
||||
dotenv: 8.2.0
|
||||
mime-types: 2.1.31
|
||||
mkdirp: 1.0.4
|
||||
negotiator: 0.6.2
|
||||
pg: 8.6.0
|
||||
@@ -36,6 +39,26 @@ devDependencies:
|
||||
eslint: 7.27.0
|
||||
lockfileVersion: 5.2
|
||||
packages:
|
||||
/@atao60/fse-cli/0.1.6:
|
||||
dependencies:
|
||||
'@babel/runtime': 7.14.6
|
||||
arg: 5.0.0
|
||||
chalk: 4.1.1
|
||||
core-js: 3.15.2
|
||||
fs-extra: 10.0.0
|
||||
graceful-fs: 4.2.6
|
||||
inquirer: 8.1.1
|
||||
regenerator-runtime: 0.13.7
|
||||
source-map-support: 0.5.19
|
||||
terminal-link: 3.0.0
|
||||
tslib: 2.3.0
|
||||
dev: false
|
||||
engines:
|
||||
node: ^12.20.0 || ^14.13.1 || >=16.0.0
|
||||
npm: '>=6.14.11'
|
||||
hasBin: true
|
||||
resolution:
|
||||
integrity: sha512-BtUemvHc16zevepkJGjM2vuRVkyU3zIJEHpw/gvvxJRRSWppblw9uqXeR7y72kJ88VcTOnzz/1wNGWHsBHVpKQ==
|
||||
/@babel/code-frame/7.12.11:
|
||||
dependencies:
|
||||
'@babel/highlight': 7.14.0
|
||||
@@ -65,6 +88,14 @@ packages:
|
||||
hasBin: true
|
||||
resolution:
|
||||
integrity: sha512-OhsyMrqygfk5v8HmWwOzlYjJrtLaFhF34MrfG/Z73DgYCI6ojNUTUp2TYbtnjo8PegeJp12eamsNettCQjKjVw==
|
||||
/@babel/runtime/7.14.6:
|
||||
dependencies:
|
||||
regenerator-runtime: 0.13.7
|
||||
dev: false
|
||||
engines:
|
||||
node: '>=6.9.0'
|
||||
resolution:
|
||||
integrity: sha512-/PCB2uJ7oM44tz8YhC4Z/6PeOKXp4K588f+5M3clr1M4zbqztlo0XEfJ2LEzj/FgwfgGcIdl8n7YYjTCI0BYwg==
|
||||
/@babel/types/7.13.14:
|
||||
dependencies:
|
||||
'@babel/helper-validator-identifier': 7.12.11
|
||||
@@ -155,6 +186,10 @@ packages:
|
||||
dev: true
|
||||
resolution:
|
||||
integrity: sha512-cxWFQVseBm6O9Gbw1IWb8r6OS4OhSt3hPZLkFApLjM8TEXROBuQGLAH2i2gZpcXdLBIrpXuTDhH7Vbm1iXmNGA==
|
||||
/@types/mime-types/2.1.0:
|
||||
dev: false
|
||||
resolution:
|
||||
integrity: sha1-nKUs2jY/aZxpRmwqbM2q2RPqenM=
|
||||
/@types/minimatch/3.0.4:
|
||||
dev: false
|
||||
resolution:
|
||||
@@ -181,6 +216,10 @@ packages:
|
||||
dev: false
|
||||
resolution:
|
||||
integrity: sha512-sld7b/xmFum66AAKuz/rp/CUO8+98fMpyQ3SBfzzBNGMd/1iHBTAg9oyAvcYlAj46bpc74r91jSw2iFdnx29nw==
|
||||
/@types/node/14.17.4:
|
||||
dev: false
|
||||
resolution:
|
||||
integrity: sha512-8kQ3+wKGRNN0ghtEn7EGps/B8CzuBz1nXZEIGGLP2GnwbqYn4dbTs7k+VKLTq1HvZLRCIDtN3Snx1Ege8B7L5A==
|
||||
/@types/pg/8.6.0:
|
||||
dependencies:
|
||||
'@types/node': 14.17.1
|
||||
@@ -376,6 +415,22 @@ packages:
|
||||
node: '>=6'
|
||||
resolution:
|
||||
integrity: sha512-JoX0apGbHaUJBNl6yF+p6JAFYZ666/hhCGKN5t9QFjbJQKUU/g8MNbFDbvfrgKXvI1QpZplPOnwIo99lX/AAmA==
|
||||
/ansi-escapes/4.3.2:
|
||||
dependencies:
|
||||
type-fest: 0.21.3
|
||||
dev: false
|
||||
engines:
|
||||
node: '>=8'
|
||||
resolution:
|
||||
integrity: sha512-gKXj5ALrKWQLsYG9jlTRmR/xKluxHV+Z9QEwNIgCfM1/uwPMCuzVVnh5mwTd+OuBZcwSIMbqssNWRm1lE51QaQ==
|
||||
/ansi-escapes/5.0.0:
|
||||
dependencies:
|
||||
type-fest: 1.2.1
|
||||
dev: false
|
||||
engines:
|
||||
node: '>=12'
|
||||
resolution:
|
||||
integrity: sha512-5GFMVX8HqE/TB+FuBJGuO5XG0WrsA6ptUqoODaT/n9mmUaZFkqnBueB4leqGBCmrUHnCnC4PCZTCd0E7QQ83bA==
|
||||
/ansi-regex/2.1.1:
|
||||
dev: false
|
||||
engines:
|
||||
@@ -383,7 +438,6 @@ packages:
|
||||
resolution:
|
||||
integrity: sha1-w7M6te42DYbg5ijwRorn7yfWVN8=
|
||||
/ansi-regex/5.0.0:
|
||||
dev: true
|
||||
engines:
|
||||
node: '>=8'
|
||||
resolution:
|
||||
@@ -399,7 +453,6 @@ packages:
|
||||
/ansi-styles/4.3.0:
|
||||
dependencies:
|
||||
color-convert: 2.0.1
|
||||
dev: true
|
||||
engines:
|
||||
node: '>=8'
|
||||
resolution:
|
||||
@@ -419,6 +472,10 @@ packages:
|
||||
dev: false
|
||||
resolution:
|
||||
integrity: sha512-58S9QDqG0Xx27YwPSt9fJxivjYl432YCwfDMfZ+71RAqUrZef7LrKQZ3LHLOwCS4FLNBplP533Zx895SeOCHvA==
|
||||
/arg/5.0.0:
|
||||
dev: false
|
||||
resolution:
|
||||
integrity: sha512-4P8Zm2H+BRS+c/xX1LrHw0qKpEhdlZjLCgWy+d78T9vqa2Z2SiD2wMrYuWIAFy5IZUD7nnNXroRttz+0RzlrzQ==
|
||||
/argparse/1.0.10:
|
||||
dependencies:
|
||||
sprintf-js: 1.0.3
|
||||
@@ -468,6 +525,10 @@ packages:
|
||||
/balanced-match/1.0.2:
|
||||
resolution:
|
||||
integrity: sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==
|
||||
/base64-js/1.5.1:
|
||||
dev: false
|
||||
resolution:
|
||||
integrity: sha512-AKpaYlHn8t4SVbOHCy+b5+KKgvR4vrsD8vbvrbiQJps7fKDTkjkDry6ji0rUJjC0kzbNePLwzxq8iypo41qeWA==
|
||||
/bcrypt-pbkdf/1.0.2:
|
||||
dependencies:
|
||||
tweetnacl: 0.14.5
|
||||
@@ -484,6 +545,14 @@ packages:
|
||||
requiresBuild: true
|
||||
resolution:
|
||||
integrity: sha512-9BTgmrhZM2t1bNuDtrtIMVSmmxZBrJ71n8Wg+YgdjHuIWYF7SjjmCPZFB+/5i/o/PIeRpwVJR3P+NrpIItUjqw==
|
||||
/bl/4.1.0:
|
||||
dependencies:
|
||||
buffer: 5.7.1
|
||||
inherits: 2.0.4
|
||||
readable-stream: 3.6.0
|
||||
dev: false
|
||||
resolution:
|
||||
integrity: sha512-1W07cM9gS6DcLperZfFSj+bWLtaPGSOHWhPiGzXmvVJbRLdG82sH/Kn8EtW1VqWVA54AKf2h5k5BbnIbwF3h6w==
|
||||
/brace-expansion/1.1.11:
|
||||
dependencies:
|
||||
balanced-match: 1.0.2
|
||||
@@ -508,6 +577,13 @@ packages:
|
||||
node: '>=4'
|
||||
resolution:
|
||||
integrity: sha512-a7ZpuTZU1TRtnwyCNW3I5dc0wWNC3VR9S++Ewyk2HHZdrO3CQJqSpd+95Us590V6AL7JqUAH2IwZ/398PmNFgw==
|
||||
/buffer/5.7.1:
|
||||
dependencies:
|
||||
base64-js: 1.5.1
|
||||
ieee754: 1.2.1
|
||||
dev: false
|
||||
resolution:
|
||||
integrity: sha512-EHcyIPBQ4BSGlvjB16k5KgAJ27CIsHY/2JBmCRReo48y9rQ3MaUzWX3KVlBa4U7MyX02HdVj0K7C3WaB3ju7FQ==
|
||||
/busboy/0.3.1:
|
||||
dependencies:
|
||||
dicer: 0.3.0
|
||||
@@ -543,7 +619,6 @@ packages:
|
||||
dependencies:
|
||||
ansi-styles: 4.3.0
|
||||
supports-color: 7.2.0
|
||||
dev: true
|
||||
engines:
|
||||
node: '>=10'
|
||||
resolution:
|
||||
@@ -554,12 +629,30 @@ packages:
|
||||
dev: false
|
||||
resolution:
|
||||
integrity: sha1-x84o821LzZdE5f/CxfzeHHMmH8A=
|
||||
/chardet/0.7.0:
|
||||
dev: false
|
||||
resolution:
|
||||
integrity: sha512-mT8iDcrh03qDGRRmoA2hmBJnxpllMR+0/0qlzjqZES6NdiWDcZkCNAk4rPFZ9Q85r27unkiNNg8ZOiwZXBHwcA==
|
||||
/chownr/2.0.0:
|
||||
dev: false
|
||||
engines:
|
||||
node: '>=10'
|
||||
resolution:
|
||||
integrity: sha512-bIomtDF5KGpdogkLd9VspvFzk9KfpyyGlS8YFVZl7TGPBHL5snIOnxeshwVgPteQ9b4Eydl+pVbIyE1DcvCWgQ==
|
||||
/cli-cursor/3.1.0:
|
||||
dependencies:
|
||||
restore-cursor: 3.1.0
|
||||
dev: false
|
||||
engines:
|
||||
node: '>=8'
|
||||
resolution:
|
||||
integrity: sha512-I/zHAwsKf9FqGoXM4WWRACob9+SNukZTd94DWF57E4toouRulbCxcUh6RKUEOQlYTHJnzkPMySvPNaaSLNfLZw==
|
||||
/cli-spinners/2.6.0:
|
||||
dev: false
|
||||
engines:
|
||||
node: '>=6'
|
||||
resolution:
|
||||
integrity: sha512-t+4/y50K/+4xcCRosKkA7W4gTr1MySvLV0q+PxmG7FJ5g+66ChKurYjxBCjHggHH3HA5Hh9cy+lcUGWDqVH+4Q==
|
||||
/cli-table/0.3.6:
|
||||
dependencies:
|
||||
colors: 1.0.3
|
||||
@@ -568,6 +661,18 @@ packages:
|
||||
node: '>= 0.2.0'
|
||||
resolution:
|
||||
integrity: sha512-ZkNZbnZjKERTY5NwC2SeMeLeifSPq/pubeRoTpdr3WchLlnZg6hEgvHkK5zL7KNFdd9PmHN8lxrENUwI3cE8vQ==
|
||||
/cli-width/3.0.0:
|
||||
dev: false
|
||||
engines:
|
||||
node: '>= 10'
|
||||
resolution:
|
||||
integrity: sha512-FxqpkPPwu1HjuN93Omfm4h8uIanXofW0RxVEW3k5RKx+mJJYSthzNhp32Kzxxy3YAEZ/Dc/EWN1vZRY0+kOhbw==
|
||||
/clone/1.0.4:
|
||||
dev: false
|
||||
engines:
|
||||
node: '>=0.8'
|
||||
resolution:
|
||||
integrity: sha1-2jCcwmPfFZlMaIypAheco8fNfH4=
|
||||
/code-point-at/1.1.0:
|
||||
dev: false
|
||||
engines:
|
||||
@@ -583,7 +688,6 @@ packages:
|
||||
/color-convert/2.0.1:
|
||||
dependencies:
|
||||
color-name: 1.1.4
|
||||
dev: true
|
||||
engines:
|
||||
node: '>=7.0.0'
|
||||
resolution:
|
||||
@@ -593,7 +697,6 @@ packages:
|
||||
resolution:
|
||||
integrity: sha1-p9BVi9icQveV3UIyj3QIMcpTvCU=
|
||||
/color-name/1.1.4:
|
||||
dev: true
|
||||
resolution:
|
||||
integrity: sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==
|
||||
/colors/1.0.3:
|
||||
@@ -626,6 +729,11 @@ packages:
|
||||
dev: false
|
||||
resolution:
|
||||
integrity: sha512-vCrqcSIq4//Gx74TXXCGnHpulY1dskqLTFGDmhrGxzeXL8lF8kvXv6mpNWlJj1uD4DW23D4ljAqbY4RRaaUZIw==
|
||||
/core-js/3.15.2:
|
||||
dev: false
|
||||
requiresBuild: true
|
||||
resolution:
|
||||
integrity: sha512-tKs41J7NJVuaya8DxIOCnl8QuPHx5/ZVbFo1oKgVl1qHFBBrDctzQGtuLjPpRdNTWmKPH6oEvgN/MUID+l485Q==
|
||||
/core-util-is/1.0.2:
|
||||
dev: false
|
||||
resolution:
|
||||
@@ -670,6 +778,12 @@ packages:
|
||||
dev: true
|
||||
resolution:
|
||||
integrity: sha1-s2nW+128E+7PUk+RsHD+7cNXzzQ=
|
||||
/defaults/1.0.3:
|
||||
dependencies:
|
||||
clone: 1.0.4
|
||||
dev: false
|
||||
resolution:
|
||||
integrity: sha1-xlYFHpgX2f8I7YgUd/P+QBnz730=
|
||||
/delegates/1.0.0:
|
||||
dev: false
|
||||
resolution:
|
||||
@@ -722,7 +836,6 @@ packages:
|
||||
resolution:
|
||||
integrity: sha512-8sJ78ElpbDJBHNeBzUbUVLsqKdccaa/BXF1uPTw3GrvQTBgrQrtObr2mUrE38vzYd8cEv+m/JBfDLioYcfXoaw==
|
||||
/emoji-regex/8.0.0:
|
||||
dev: true
|
||||
resolution:
|
||||
integrity: sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==
|
||||
/enquirer/2.3.6:
|
||||
@@ -734,7 +847,6 @@ packages:
|
||||
resolution:
|
||||
integrity: sha512-yjNnPr315/FjS4zIsUxYguYUPP2e1NK4d7E7ZOLiyYCcbFBiTMyID+2wvm2w6+pZ/odMA7cRkjhsPbltwBOrLg==
|
||||
/escape-string-regexp/1.0.5:
|
||||
dev: true
|
||||
engines:
|
||||
node: '>=0.8.0'
|
||||
resolution:
|
||||
@@ -883,6 +995,16 @@ packages:
|
||||
node: '>=0.10.0'
|
||||
resolution:
|
||||
integrity: sha512-kVscqXk4OCp68SZ0dkgEKVi6/8ij300KBWTJq32P/dYeWTSwK41WyTxalN1eRmA5Z9UU/LX9D7FWSmV9SAYx6g==
|
||||
/external-editor/3.1.0:
|
||||
dependencies:
|
||||
chardet: 0.7.0
|
||||
iconv-lite: 0.4.24
|
||||
tmp: 0.0.33
|
||||
dev: false
|
||||
engines:
|
||||
node: '>=4'
|
||||
resolution:
|
||||
integrity: sha512-hMQ4CX1p1izmuLYyZqLMO/qGNw10wSv9QDCPfzXfyFrOaCSSoRfqE1Kf1s5an66J5JZC62NewG+mK49jOCtQew==
|
||||
/fast-deep-equal/3.1.3:
|
||||
dev: true
|
||||
resolution:
|
||||
@@ -914,6 +1036,14 @@ packages:
|
||||
dev: true
|
||||
resolution:
|
||||
integrity: sha512-7Eczs8gIPDrVzT+EksYBcupqMyxSHXXrHOLRRxU2/DicV8789MRBRR8+Hc2uWzUupOs4YS4JzBmBxjjCVBxD/g==
|
||||
/figures/3.2.0:
|
||||
dependencies:
|
||||
escape-string-regexp: 1.0.5
|
||||
dev: false
|
||||
engines:
|
||||
node: '>=8'
|
||||
resolution:
|
||||
integrity: sha512-yaduQFRKLXYOGgEn6AZau90j3ggSOyiqXU0F9JZfeXYhNa+Jk4X+s45A2zg5jns87GAFa34BBm2kXw4XpNcbdg==
|
||||
/file-entry-cache/6.0.1:
|
||||
dependencies:
|
||||
flat-cache: 3.0.4
|
||||
@@ -943,6 +1073,16 @@ packages:
|
||||
dev: true
|
||||
resolution:
|
||||
integrity: sha512-zAoAQiudy+r5SvnSw3KJy5os/oRJYHzrzja/tBDqrZtNhUw8bt6y8OBzMWcjWr+8liV8Eb6yOhw8WZ7VFZ5ZzA==
|
||||
/fs-extra/10.0.0:
|
||||
dependencies:
|
||||
graceful-fs: 4.2.6
|
||||
jsonfile: 6.1.0
|
||||
universalify: 2.0.0
|
||||
dev: false
|
||||
engines:
|
||||
node: '>=12'
|
||||
resolution:
|
||||
integrity: sha512-C5owb14u9eJwizKGdchcDUQeFtlSHHthBk8pbX9Vc1PFZrLombudjDnNns88aYslCyF6IY5SUw3Roz6xShcEIQ==
|
||||
/fs-extra/9.1.0:
|
||||
dependencies:
|
||||
at-least-node: 1.0.0
|
||||
@@ -1066,7 +1206,6 @@ packages:
|
||||
resolution:
|
||||
integrity: sha1-tdRU3CGZriJWmfNGfloH87lVuv0=
|
||||
/has-flag/4.0.0:
|
||||
dev: true
|
||||
engines:
|
||||
node: '>=8'
|
||||
resolution:
|
||||
@@ -1098,6 +1237,18 @@ packages:
|
||||
node: '>= 6'
|
||||
resolution:
|
||||
integrity: sha512-EkYm5BcKUGiduxzSt3Eppko+PiNWNEpa4ySk9vTC6wDsQJW9rHSa+UhGNJoRYp7bz6Ht1eaRIa6QaJqO5rCFbA==
|
||||
/iconv-lite/0.4.24:
|
||||
dependencies:
|
||||
safer-buffer: 2.1.2
|
||||
dev: false
|
||||
engines:
|
||||
node: '>=0.10.0'
|
||||
resolution:
|
||||
integrity: sha512-v3MXnZAcvnywkTUEZomIActle7RXXeedOR31wwl7VlyoXO4Qi9arvSenNQWne1TcRwhCL1HwLI21bEqdpj8/rA==
|
||||
/ieee754/1.2.1:
|
||||
dev: false
|
||||
resolution:
|
||||
integrity: sha512-dcyqhDvX1C46lXZcVqCpK+FtMRQVdIMN6/Df5js2zouUsqG7I6sFxitIC+7KYK29KdXOLHdu9zL4sFnoVQnqaA==
|
||||
/ignore/4.0.6:
|
||||
dev: true
|
||||
engines:
|
||||
@@ -1134,6 +1285,27 @@ packages:
|
||||
/inherits/2.0.4:
|
||||
resolution:
|
||||
integrity: sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==
|
||||
/inquirer/8.1.1:
|
||||
dependencies:
|
||||
ansi-escapes: 4.3.2
|
||||
chalk: 4.1.1
|
||||
cli-cursor: 3.1.0
|
||||
cli-width: 3.0.0
|
||||
external-editor: 3.1.0
|
||||
figures: 3.2.0
|
||||
lodash: 4.17.21
|
||||
mute-stream: 0.0.8
|
||||
ora: 5.4.1
|
||||
run-async: 2.4.1
|
||||
rxjs: 6.6.7
|
||||
string-width: 4.2.2
|
||||
strip-ansi: 6.0.0
|
||||
through: 2.3.8
|
||||
dev: false
|
||||
engines:
|
||||
node: '>=8.0.0'
|
||||
resolution:
|
||||
integrity: sha512-hUDjc3vBkh/uk1gPfMAD/7Z188Q8cvTGl0nxwaCdwSbzFh6ZKkZh+s2ozVxbE5G9ZNRyeY0+lgbAIOUFsFf98w==
|
||||
/interpret/1.4.0:
|
||||
dev: false
|
||||
engines:
|
||||
@@ -1168,7 +1340,6 @@ packages:
|
||||
resolution:
|
||||
integrity: sha1-754xOG8DGn8NZDr4L95QxFfvAMs=
|
||||
/is-fullwidth-code-point/3.0.0:
|
||||
dev: true
|
||||
engines:
|
||||
node: '>=8'
|
||||
resolution:
|
||||
@@ -1181,6 +1352,12 @@ packages:
|
||||
node: '>=0.10.0'
|
||||
resolution:
|
||||
integrity: sha512-5G0tKtBTFImOqDnLB2hG6Bp2qcKEFduo4tZu9MT/H6NQv/ghhy30o55ufafxJ/LdH79LLs2Kfrn85TLKyA7BUg==
|
||||
/is-interactive/1.0.0:
|
||||
dev: false
|
||||
engines:
|
||||
node: '>=8'
|
||||
resolution:
|
||||
integrity: sha512-2HvIEKRoqS62guEC+qBjpvRubdX910WCMuJTZ+I9yvqKU2/12eSL549HMwtabb4oupdj2sMP50k+XJfB/8JE6w==
|
||||
/is-number/7.0.0:
|
||||
dev: true
|
||||
engines:
|
||||
@@ -1200,6 +1377,12 @@ packages:
|
||||
node: '>= 0.4'
|
||||
resolution:
|
||||
integrity: sha512-axvdhb5pdhEVThqJzYXwMlVuZwC+FF2DpcOhTS+y/8jVq4trxyPgfcwIxIKiyeuLlSQYKkmUaPQJ8ZE4yNKXDg==
|
||||
/is-unicode-supported/0.1.0:
|
||||
dev: false
|
||||
engines:
|
||||
node: '>=10'
|
||||
resolution:
|
||||
integrity: sha512-knxG2q4UC3u8stRGyAVJCOdxFmv5DZiRcdlIaAQXAbSfJya+OhopNotLQrstBhququ4ZpuKbDc/8S6mgXgPFPw==
|
||||
/isarray/1.0.0:
|
||||
dev: false
|
||||
resolution:
|
||||
@@ -1275,6 +1458,15 @@ packages:
|
||||
/lodash/4.17.21:
|
||||
resolution:
|
||||
integrity: sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg==
|
||||
/log-symbols/4.1.0:
|
||||
dependencies:
|
||||
chalk: 4.1.1
|
||||
is-unicode-supported: 0.1.0
|
||||
dev: false
|
||||
engines:
|
||||
node: '>=10'
|
||||
resolution:
|
||||
integrity: sha512-8XPvpAA8uyhfteu8pIvQxpJZ7SYYdpUivZpGy6sFsBuKRY/7rQGavedeB8aK+Zkyq6upMFVL/9AW6vOYzfRyLg==
|
||||
/lru-cache/5.1.1:
|
||||
dependencies:
|
||||
yallist: 3.1.1
|
||||
@@ -1326,6 +1518,26 @@ packages:
|
||||
node: '>=8.6'
|
||||
resolution:
|
||||
integrity: sha512-pRmzw/XUcwXGpD9aI9q/0XOwLNygjETJ8y0ao0wdqprrzDa4YnxLcz7fQRZr8voh8V10kGhABbNcHVk5wHgWwg==
|
||||
/mime-db/1.48.0:
|
||||
dev: false
|
||||
engines:
|
||||
node: '>= 0.6'
|
||||
resolution:
|
||||
integrity: sha512-FM3QwxV+TnZYQ2aRqhlKBMHxk10lTbMt3bBkMAp54ddrNeVSfcQYOOKuGuy3Ddrm38I04If834fOUSq1yzslJQ==
|
||||
/mime-types/2.1.31:
|
||||
dependencies:
|
||||
mime-db: 1.48.0
|
||||
dev: false
|
||||
engines:
|
||||
node: '>= 0.6'
|
||||
resolution:
|
||||
integrity: sha512-XGZnNzm3QvgKxa8dpzyhFTHmpP3l5YNusmne07VUOXxou9CqUqYa/HBy124RqtVh/O2pECas/MOcsDgpilPOPg==
|
||||
/mimic-fn/2.1.0:
|
||||
dev: false
|
||||
engines:
|
||||
node: '>=6'
|
||||
resolution:
|
||||
integrity: sha512-OqbOk5oEQeAZ8WXWydlu9HJjz9WVdEIvamMCcXmuqUYjTknH/sqsWvhQ3vgwKFRR1HpjvNBKQ37nbJgYzGqGcg==
|
||||
/minimatch/3.0.4:
|
||||
dependencies:
|
||||
brace-expansion: 1.1.11
|
||||
@@ -1362,6 +1574,10 @@ packages:
|
||||
/ms/2.1.2:
|
||||
resolution:
|
||||
integrity: sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==
|
||||
/mute-stream/0.0.8:
|
||||
dev: false
|
||||
resolution:
|
||||
integrity: sha512-nnbWWOkoWyUsTjKrhgD0dcz22mdkSnpYqbEjIm2nhwhuxlSkpywJmBo8h0ZqJdkp73mb90SssHkN4rsRaBAfAA==
|
||||
/nan/2.14.2:
|
||||
dev: false
|
||||
optional: true
|
||||
@@ -1426,6 +1642,14 @@ packages:
|
||||
wrappy: 1.0.2
|
||||
resolution:
|
||||
integrity: sha1-WDsap3WWHUsROsF9nFC6753Xa9E=
|
||||
/onetime/5.1.2:
|
||||
dependencies:
|
||||
mimic-fn: 2.1.0
|
||||
dev: false
|
||||
engines:
|
||||
node: '>=6'
|
||||
resolution:
|
||||
integrity: sha512-kbpaSSGJTWdAY5KPVeMOKXSrPtr8C8C7wodJbcsd51jRnmD+GZu8Y0VoU6Dm5Z4vWr0Ig/1NKuWRKf7j5aaYSg==
|
||||
/onigasm/2.2.5:
|
||||
dependencies:
|
||||
lru-cache: 5.1.1
|
||||
@@ -1445,6 +1669,28 @@ packages:
|
||||
node: '>= 0.8.0'
|
||||
resolution:
|
||||
integrity: sha512-74RlY5FCnhq4jRxVUPKDaRwrVNXMqsGsiW6AJw4XK8hmtm10wC0ypZBLw5IIp85NZMr91+qd1RvvENwg7jjRFw==
|
||||
/ora/5.4.1:
|
||||
dependencies:
|
||||
bl: 4.1.0
|
||||
chalk: 4.1.1
|
||||
cli-cursor: 3.1.0
|
||||
cli-spinners: 2.6.0
|
||||
is-interactive: 1.0.0
|
||||
is-unicode-supported: 0.1.0
|
||||
log-symbols: 4.1.0
|
||||
strip-ansi: 6.0.0
|
||||
wcwidth: 1.0.1
|
||||
dev: false
|
||||
engines:
|
||||
node: '>=10'
|
||||
resolution:
|
||||
integrity: sha512-5b6Y85tPxZZ7QytO+BQzysW31HJku27cRIlkbAXaNx+BdcVi+LlRFmVXzeF6a7JCwJpyw5c4b+YSVImQIrBpuQ==
|
||||
/os-tmpdir/1.0.2:
|
||||
dev: false
|
||||
engines:
|
||||
node: '>=0.10.0'
|
||||
resolution:
|
||||
integrity: sha1-u+Z0BseaqFxc/sdm/lc0VV36EnQ=
|
||||
/packet-reader/1.0.0:
|
||||
dev: false
|
||||
resolution:
|
||||
@@ -1731,6 +1977,10 @@ packages:
|
||||
dev: false
|
||||
resolution:
|
||||
integrity: sha512-Ts1Y/anZELhSsjMcU605fU9RE4Oi3p5ORujwbIKXfWa+0Zxs510Qrmrce5/Jowq3cHSZSJqBjypxmHarc+vEWg==
|
||||
/regenerator-runtime/0.13.7:
|
||||
dev: false
|
||||
resolution:
|
||||
integrity: sha512-a54FxoJDIr27pgf7IgeQGxmqUNYrcV338lf/6gH456HZ/PhX+5BcwHXG9ajESmwe6WRO0tAzRUrRmNONWgkrew==
|
||||
/regexpp/3.1.0:
|
||||
dev: true
|
||||
engines:
|
||||
@@ -1756,6 +2006,15 @@ packages:
|
||||
dev: false
|
||||
resolution:
|
||||
integrity: sha512-wENBPt4ySzg4ybFQW2TT1zMQucPK95HSh/nq2CFTZVOGut2+pQvSsgtda4d26YrYcr067wjbmzOG8byDPBX63A==
|
||||
/restore-cursor/3.1.0:
|
||||
dependencies:
|
||||
onetime: 5.1.2
|
||||
signal-exit: 3.0.3
|
||||
dev: false
|
||||
engines:
|
||||
node: '>=8'
|
||||
resolution:
|
||||
integrity: sha512-l+sSefzHpj5qimhFSE5a8nufZYAM3sBSVMAPtYkmC+4EH2anSGaEMXSD0izRQbu9nfyQ9y5JrVmp7E8oZrUjvA==
|
||||
/reusify/1.0.4:
|
||||
dev: true
|
||||
engines:
|
||||
@@ -1769,12 +2028,26 @@ packages:
|
||||
hasBin: true
|
||||
resolution:
|
||||
integrity: sha512-JZkJMZkAGFFPP2YqXZXPbMlMBgsxzE8ILs4lMIX/2o0L9UBw9O/Y3o6wFw/i9YLapcUJWwqbi3kdxIPdC62TIA==
|
||||
/run-async/2.4.1:
|
||||
dev: false
|
||||
engines:
|
||||
node: '>=0.12.0'
|
||||
resolution:
|
||||
integrity: sha512-tvVnVv01b8c1RrA6Ep7JkStj85Guv/YrMcwqYQnwjsAS2cTmmPGBBjAjpCW7RrSodNSoE2/qg9O4bceNvUuDgQ==
|
||||
/run-parallel/1.2.0:
|
||||
dependencies:
|
||||
queue-microtask: 1.2.3
|
||||
dev: true
|
||||
resolution:
|
||||
integrity: sha512-5l4VyZR86LZ/lDxZTR6jqL8AFE2S0IFLMP26AbjsLVADxHdhB/c0GUsH+y39UfCi3dzz8OlQuPmnaJOMoDHQBA==
|
||||
/rxjs/6.6.7:
|
||||
dependencies:
|
||||
tslib: 1.14.1
|
||||
dev: false
|
||||
engines:
|
||||
npm: '>=2.0.0'
|
||||
resolution:
|
||||
integrity: sha512-hTdwr+7yYNIT5n4AMYp85KA6yw2Va0FLa3Rguvbpa4W3I5xynaBZo41cM3XM+4Q6fRMj3sBYIR1VAmZMXYJvRQ==
|
||||
/safe-buffer/5.1.2:
|
||||
dev: false
|
||||
resolution:
|
||||
@@ -1913,7 +2186,6 @@ packages:
|
||||
emoji-regex: 8.0.0
|
||||
is-fullwidth-code-point: 3.0.0
|
||||
strip-ansi: 6.0.0
|
||||
dev: true
|
||||
engines:
|
||||
node: '>=8'
|
||||
resolution:
|
||||
@@ -1941,7 +2213,6 @@ packages:
|
||||
/strip-ansi/6.0.0:
|
||||
dependencies:
|
||||
ansi-regex: 5.0.0
|
||||
dev: true
|
||||
engines:
|
||||
node: '>=8'
|
||||
resolution:
|
||||
@@ -1963,11 +2234,19 @@ packages:
|
||||
/supports-color/7.2.0:
|
||||
dependencies:
|
||||
has-flag: 4.0.0
|
||||
dev: true
|
||||
engines:
|
||||
node: '>=8'
|
||||
resolution:
|
||||
integrity: sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==
|
||||
/supports-hyperlinks/2.2.0:
|
||||
dependencies:
|
||||
has-flag: 4.0.0
|
||||
supports-color: 7.2.0
|
||||
dev: false
|
||||
engines:
|
||||
node: '>=8'
|
||||
resolution:
|
||||
integrity: sha512-6sXEzV5+I5j8Bmq9/vUphGRM/RJNT9SCURJLjwfOg51heRtguGWDzcaBlgAzKhQa0EVNpPEKzQuBwZ8S8WaCeQ==
|
||||
/table/6.7.1:
|
||||
dependencies:
|
||||
ajv: 8.5.0
|
||||
@@ -1994,10 +2273,31 @@ packages:
|
||||
node: '>= 10'
|
||||
resolution:
|
||||
integrity: sha512-DUCttfhsnLCjwoDoFcI+B2iJgYa93vBnDUATYEeRx6sntCTdN01VnqsIuTlALXla/LWooNg0yEGeB+Y8WdFxGA==
|
||||
/terminal-link/3.0.0:
|
||||
dependencies:
|
||||
ansi-escapes: 5.0.0
|
||||
supports-hyperlinks: 2.2.0
|
||||
dev: false
|
||||
engines:
|
||||
node: '>=12'
|
||||
resolution:
|
||||
integrity: sha512-flFL3m4wuixmf6IfhFJd1YPiLiMuxEc8uHRM1buzIeZPm22Au2pDqBJQgdo7n1WfPU1ONFGv7YDwpFBmHGF6lg==
|
||||
/text-table/0.2.0:
|
||||
dev: true
|
||||
resolution:
|
||||
integrity: sha1-f17oI66AUgfACvLfSoTsP8+lcLQ=
|
||||
/through/2.3.8:
|
||||
dev: false
|
||||
resolution:
|
||||
integrity: sha1-DdTJ/6q8NXlgsbckEV1+Doai4fU=
|
||||
/tmp/0.0.33:
|
||||
dependencies:
|
||||
os-tmpdir: 1.0.2
|
||||
dev: false
|
||||
engines:
|
||||
node: '>=0.6.0'
|
||||
resolution:
|
||||
integrity: sha512-jRCJlojKnZ3addtTOjdIqoRuPEKBvNXcGYqzO6zWZX8KfKEpnGY5jfggJQ3EjKuu8D4bJRr0y+cYJFmYbImXGw==
|
||||
/to-fast-properties/2.0.0:
|
||||
dev: false
|
||||
engines:
|
||||
@@ -2034,9 +2334,12 @@ packages:
|
||||
resolution:
|
||||
integrity: sha512-hPlt7ZACERQGf03M253ytLY3dHbGNGrAq9qIHWUY9XHYl1z7wYngSr3OQ5xmui8o2AaxsONxIzjafLUiWBo1Fg==
|
||||
/tslib/1.14.1:
|
||||
dev: true
|
||||
resolution:
|
||||
integrity: sha512-Xni35NKzjgMrwevysHTCArtLDpPvye8zV/0E4EyYn43P7/7qvQwPh9BGkHewbMulVntbigmcT7rdX3BNo9wRJg==
|
||||
/tslib/2.3.0:
|
||||
dev: false
|
||||
resolution:
|
||||
integrity: sha512-N82ooyxVNm6h1riLCoyS9e3fuJ3AMG2zIZs2Gd1ATcSFjSA23Q0fzjjZeh0jbJvWVDZ0cJT8yaNNaaXHzueNjg==
|
||||
/tsutils/3.21.0_typescript@4.2.3:
|
||||
dependencies:
|
||||
tslib: 1.14.1
|
||||
@@ -2066,12 +2369,24 @@ packages:
|
||||
node: '>=10'
|
||||
resolution:
|
||||
integrity: sha512-Ne+eE4r0/iWnpAxD852z3A+N0Bt5RN//NjJwRd2VFHEmrywxf5vsZlh4R6lixl6B+wz/8d+maTSAkN1FIkI3LQ==
|
||||
/type-fest/0.21.3:
|
||||
dev: false
|
||||
engines:
|
||||
node: '>=10'
|
||||
resolution:
|
||||
integrity: sha512-t0rzBq87m3fVcduHDUFhKmyyX+9eo6WQjZvf51Ea/M0Q7+T374Jp1aUiyUl0GKxp8M/OETVHSDvmkyPgvX+X2w==
|
||||
/type-fest/0.8.1:
|
||||
dev: true
|
||||
engines:
|
||||
node: '>=8'
|
||||
resolution:
|
||||
integrity: sha512-4dbzIzqvjtgiM5rw1k5rEHtBANKmdudhGyBEajN01fEyhaAIhsoKNy6y7+IN93IfpFtwY9iqi7kD+xwKhQsNJA==
|
||||
/type-fest/1.2.1:
|
||||
dev: false
|
||||
engines:
|
||||
node: '>=10'
|
||||
resolution:
|
||||
integrity: sha512-SbmIRuXhJs8KTneu77Ecylt9zuqL683tuiLYpTRil4H++eIhqCmx6ko6KAFem9dty8sOdnEiX7j4K1nRE628fQ==
|
||||
/typedoc-default-themes/0.10.2:
|
||||
dependencies:
|
||||
lunr: 2.3.9
|
||||
@@ -2173,6 +2488,12 @@ packages:
|
||||
dev: false
|
||||
resolution:
|
||||
integrity: sha512-c0Q4zYZkcLizeYJ3hNyaVUM2AA8KDhNCA3JvXY8CeZSJuBdAy3bAvSbv46RClC4P3dSO9BdwhnKEx2zOo6vP/w==
|
||||
/wcwidth/1.0.1:
|
||||
dependencies:
|
||||
defaults: 1.0.3
|
||||
dev: false
|
||||
resolution:
|
||||
integrity: sha1-8LDc+RW8X/FSivrbLA4XtTLaL+g=
|
||||
/which/2.0.2:
|
||||
dependencies:
|
||||
isexe: 2.0.0
|
||||
@@ -2232,12 +2553,14 @@ packages:
|
||||
resolution:
|
||||
integrity: sha512-Ux4ygGWsu2c7isFWe8Yu1YluJmqVhxqK2cLXNQA5AcC3QfbGNpM7fu0Y8b/z16pXLnFxZYvWhd3fhBY9DLmC6Q==
|
||||
specifiers:
|
||||
'@atao60/fse-cli': ^0.1.6
|
||||
'@types/bcrypt': ^5.0.0
|
||||
'@types/busboy': ^0.2.3
|
||||
'@types/cli-table': ^0.3.0
|
||||
'@types/mime-types': ^2.1.0
|
||||
'@types/mkdirp': ^1.0.1
|
||||
'@types/negotiator': ^0.6.1
|
||||
'@types/node': ^14.14.37
|
||||
'@types/node': ^14.17.4
|
||||
'@types/pg': ^8.6.0
|
||||
'@types/pluralize': ^0.0.29
|
||||
'@types/pug': ^2.0.4
|
||||
@@ -2252,6 +2575,7 @@ specifiers:
|
||||
colors: ^1.4.0
|
||||
dotenv: ^8.2.0
|
||||
eslint: ^7.27.0
|
||||
mime-types: ^2.1.31
|
||||
mkdirp: ^1.0.4
|
||||
negotiator: ^0.6.2
|
||||
pg: ^8.6.0
|
||||
|
||||
@@ -7,6 +7,7 @@ import {SessionSecurityContext} from '../contexts/SessionSecurityContext'
|
||||
import {SecurityContext} from '../SecurityContext'
|
||||
import {ORMUserRepository} from '../orm/ORMUserRepository'
|
||||
import {AuthConfig, AuthenticatableRepositories} from '../config'
|
||||
import {Logging} from '../../service/Logging'
|
||||
|
||||
/**
|
||||
* Injects a SessionSecurityContext into the request and attempts to
|
||||
@@ -17,7 +18,11 @@ export class SessionAuthMiddleware extends Middleware {
|
||||
@Inject()
|
||||
protected readonly config!: Config
|
||||
|
||||
@Inject()
|
||||
protected readonly logging!: Logging
|
||||
|
||||
async apply(): Promise<ResponseObject> {
|
||||
this.logging.debug('Applying session auth middleware...')
|
||||
const context = <SessionSecurityContext> this.make(SessionSecurityContext, this.getRepository())
|
||||
this.request.registerSingletonInstance(SecurityContext, context)
|
||||
await context.resume()
|
||||
|
||||
@@ -1,7 +1,8 @@
|
||||
import {Instantiable} from './types'
|
||||
import {DependencyKey, Instantiable} from './types'
|
||||
import NamedFactory from './factory/NamedFactory'
|
||||
import {AbstractFactory} from './factory/AbstractFactory'
|
||||
import {Factory} from './factory/Factory'
|
||||
import {ClosureFactory} from './factory/ClosureFactory'
|
||||
|
||||
export class ContainerBlueprint {
|
||||
private static instance?: ContainerBlueprint
|
||||
@@ -36,6 +37,16 @@ export class ContainerBlueprint {
|
||||
return this
|
||||
}
|
||||
|
||||
/**
|
||||
* Register a producer function as a ClosureFactory with this container.
|
||||
* @param key
|
||||
* @param producer
|
||||
*/
|
||||
registerProducer(key: DependencyKey, producer: () => any): this {
|
||||
this.factories.push(() => new ClosureFactory(key, producer))
|
||||
return this
|
||||
}
|
||||
|
||||
resolve(): AbstractFactory<any>[] {
|
||||
return this.factories.map(x => x())
|
||||
}
|
||||
|
||||
@@ -1,11 +1,13 @@
|
||||
import {Dispatchable} from './types'
|
||||
import {JSONState} from '../util'
|
||||
import {Awaitable, JSONState} from '../util'
|
||||
|
||||
/**
|
||||
* Abstract class representing an event that may be fired.
|
||||
*/
|
||||
export abstract class Event implements Dispatchable {
|
||||
abstract dehydrate(): Promise<JSONState>
|
||||
|
||||
abstract rehydrate(state: JSONState): void | Promise<void>
|
||||
|
||||
abstract dehydrate(): Awaitable<JSONState>
|
||||
|
||||
abstract rehydrate(state: JSONState): Awaitable<void>
|
||||
}
|
||||
|
||||
@@ -10,8 +10,9 @@ export class HTTPError extends ErrorWithContext {
|
||||
constructor(
|
||||
public readonly status: HTTPStatus = 500,
|
||||
public readonly message: string = '',
|
||||
context?: {[key: string]: any},
|
||||
) {
|
||||
super('HTTP ERROR')
|
||||
super('HTTP ERROR', context)
|
||||
this.message = message || HTTPMessage[status]
|
||||
}
|
||||
}
|
||||
|
||||
@@ -4,6 +4,8 @@ import {Request} from '../../lifecycle/Request'
|
||||
import {plaintext} from '../../response/StringResponseFactory'
|
||||
import {ResponseFactory} from '../../response/ResponseFactory'
|
||||
import {json} from '../../response/JSONResponseFactory'
|
||||
import {UniversalPath} from '../../../util'
|
||||
import {file} from '../../response/FileResponseFactory'
|
||||
|
||||
/**
|
||||
* Base class for HTTP kernel modules that apply some response from a route handler to the request.
|
||||
@@ -22,6 +24,8 @@ export abstract class AbstractResolvedRouteHandlerHTTPModule extends HTTPKernelM
|
||||
|
||||
if ( object instanceof ResponseFactory ) {
|
||||
await object.write(request)
|
||||
} else if ( object instanceof UniversalPath ) {
|
||||
await file(object).write(request)
|
||||
} else if ( typeof object !== 'undefined' ) {
|
||||
await json(object).write(request)
|
||||
} else {
|
||||
|
||||
@@ -28,7 +28,7 @@ export class MountActivatedRouteHTTPModule extends HTTPKernelModule {
|
||||
const route = this.routing.match(request.method, request.path)
|
||||
if ( route ) {
|
||||
this.logging.verbose(`Mounting activated route: ${request.path} -> ${route}`)
|
||||
const activated = new ActivatedRoute(route, request.path)
|
||||
const activated = <ActivatedRoute> request.make(ActivatedRoute, route, request.path)
|
||||
request.registerSingletonInstance<ActivatedRoute>(ActivatedRoute, activated)
|
||||
} else {
|
||||
this.logging.debug(`No matching route found for: ${request.method} -> ${request.path}`)
|
||||
|
||||
@@ -2,6 +2,7 @@ import {Request} from './Request'
|
||||
import {ErrorWithContext, HTTPStatus, BehaviorSubject} from '../../util'
|
||||
import {ServerResponse} from 'http'
|
||||
import {HTTPCookieJar} from '../kernel/HTTPCookieJar'
|
||||
import {Readable} from 'stream'
|
||||
|
||||
/**
|
||||
* Error thrown when the server tries to re-send headers after they have been sent once.
|
||||
@@ -47,7 +48,7 @@ export class Response {
|
||||
private isBlockingWriteback = false
|
||||
|
||||
/** The body contents that should be written to the response. */
|
||||
public body = ''
|
||||
public body: string | Buffer | Uint8Array | Readable = ''
|
||||
|
||||
/**
|
||||
* Behavior subject fired right before the response content is written.
|
||||
@@ -192,11 +193,21 @@ export class Response {
|
||||
* Write the headers and specified data to the client.
|
||||
* @param data
|
||||
*/
|
||||
public async write(data: unknown): Promise<void> {
|
||||
public async write(data: string | Buffer | Uint8Array | Readable): Promise<void> {
|
||||
return new Promise<void>((res, rej) => {
|
||||
if ( !this.sentHeaders ) {
|
||||
this.sendHeaders()
|
||||
}
|
||||
|
||||
if ( data instanceof Readable ) {
|
||||
data.pipe(this.serverResponse)
|
||||
.on('finish', () => {
|
||||
res()
|
||||
})
|
||||
.on('error', error => {
|
||||
rej(error)
|
||||
})
|
||||
} else {
|
||||
this.serverResponse.write(data, error => {
|
||||
if ( error ) {
|
||||
rej(error)
|
||||
@@ -204,6 +215,7 @@ export class Response {
|
||||
res()
|
||||
}
|
||||
})
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
@@ -212,9 +224,14 @@ export class Response {
|
||||
*/
|
||||
public async send(): Promise<void> {
|
||||
await this.sending$.next(this)
|
||||
|
||||
if ( !(this.body instanceof Readable) ) {
|
||||
this.setHeader('Content-Length', String(this.body?.length ?? 0))
|
||||
}
|
||||
|
||||
await this.write(this.body ?? '')
|
||||
this.end()
|
||||
|
||||
await this.sent$.next(this)
|
||||
}
|
||||
|
||||
|
||||
36
src/http/response/FileResponseFactory.ts
Normal file
36
src/http/response/FileResponseFactory.ts
Normal file
@@ -0,0 +1,36 @@
|
||||
import {ResponseFactory} from './ResponseFactory'
|
||||
import {Request} from '../lifecycle/Request'
|
||||
import {ErrorWithContext, UniversalPath} from '../../util'
|
||||
|
||||
/**
|
||||
* Helper function that creates a FileResponseFactory for the given path.
|
||||
* @param path
|
||||
*/
|
||||
export function file(path: UniversalPath): FileResponseFactory {
|
||||
return new FileResponseFactory(path)
|
||||
}
|
||||
|
||||
/**
|
||||
* HTTP response factory that sends a file referenced by a given UniversalPath.
|
||||
*/
|
||||
export class FileResponseFactory extends ResponseFactory {
|
||||
constructor(
|
||||
/** The file to be sent. */
|
||||
public readonly path: UniversalPath,
|
||||
) {
|
||||
super()
|
||||
}
|
||||
|
||||
public async write(request: Request): Promise<Request> {
|
||||
if ( !(await this.path.isFile()) ) {
|
||||
throw new ErrorWithContext(`Cannot write non-file resource as response: ${this.path}`, {
|
||||
path: this.path,
|
||||
})
|
||||
}
|
||||
|
||||
request.response.setHeader('Content-Type', this.path.contentType || 'application/octet-stream')
|
||||
request.response.setHeader('Content-Length', String(await this.path.sizeInBytes()))
|
||||
request.response.body = await this.path.readStream()
|
||||
return request
|
||||
}
|
||||
}
|
||||
@@ -1,9 +1,11 @@
|
||||
import {ErrorWithContext} from '../../util'
|
||||
import {ResolvedRouteHandler, Route} from './Route'
|
||||
import {Injectable} from '../../di'
|
||||
|
||||
/**
|
||||
* Class representing a resolved route that a request is mounted to.
|
||||
*/
|
||||
@Injectable()
|
||||
export class ActivatedRoute {
|
||||
/**
|
||||
* The parsed params from the route definition.
|
||||
|
||||
@@ -87,11 +87,15 @@ export class Route extends AppClass {
|
||||
for ( const group of stack ) {
|
||||
route.prepend(group.prefix)
|
||||
group.getGroupMiddlewareDefinitions()
|
||||
.each(def => route.prependMiddleware(def))
|
||||
.where('stage', '=', 'pre')
|
||||
.each(def => {
|
||||
route.prependMiddleware(def)
|
||||
})
|
||||
}
|
||||
|
||||
for ( const group of this.compiledGroupStack ) {
|
||||
group.getGroupMiddlewareDefinitions()
|
||||
.where('stage', '=', 'post')
|
||||
.each(def => route.appendMiddleware(def))
|
||||
}
|
||||
|
||||
|
||||
169
src/http/servers/static.ts
Normal file
169
src/http/servers/static.ts
Normal file
@@ -0,0 +1,169 @@
|
||||
import {Request} from '../lifecycle/Request'
|
||||
import {ActivatedRoute} from '../routing/ActivatedRoute'
|
||||
import {Config} from '../../service/Config'
|
||||
import {Collection, HTTPStatus, UniversalPath, universalPath} from '../../util'
|
||||
import {Application} from '../../lifecycle/Application'
|
||||
import {HTTPError} from '../HTTPError'
|
||||
import {view, ViewResponseFactory} from '../response/ViewResponseFactory'
|
||||
import {redirect} from '../response/TemporaryRedirectResponseFactory'
|
||||
import {file} from '../response/FileResponseFactory'
|
||||
import {RouteHandler} from '../routing/Route'
|
||||
|
||||
/**
|
||||
* Defines the behavior of the static server.
|
||||
*/
|
||||
export interface StaticServerOptions {
|
||||
/** If true, browsing to a directory route will show the directory listing page. */
|
||||
directoryListing?: boolean
|
||||
|
||||
/** The path to the directory whose files should be served. */
|
||||
basePath?: string | string[] | UniversalPath
|
||||
|
||||
/** If specified, only files with these extensions will be served. */
|
||||
allowedExtensions?: string[]
|
||||
|
||||
/** If specified, files with these extensions will not be served. */
|
||||
excludedExtensions?: string[]
|
||||
}
|
||||
|
||||
/**
|
||||
* HTTPError class thrown by the static server.
|
||||
*/
|
||||
export class StaticServerHTTPError extends HTTPError {
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* Build the response factory that shows the directory listing.
|
||||
* @param dirname
|
||||
* @param dirPath
|
||||
*/
|
||||
async function getDirectoryListingResponse(dirname: string, dirPath: UniversalPath): Promise<ViewResponseFactory> {
|
||||
return view('@extollo:static:dirlist', {
|
||||
dirname,
|
||||
contents: (await (await dirPath.list())
|
||||
.promiseMap(async path => {
|
||||
const isDirectory = await path.isDirectory()
|
||||
return {
|
||||
isDirectory,
|
||||
name: path.toBase,
|
||||
size: isDirectory ? '-' : await path.sizeForHumans(),
|
||||
}
|
||||
}))
|
||||
.sortBy(row => {
|
||||
return `${row.isDirectory ? 0 : 1}${row.name}`
|
||||
})
|
||||
.all(),
|
||||
})
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns true if the given file path has an extension that is allowed by
|
||||
* the static server options.
|
||||
* @param filePath
|
||||
* @param options
|
||||
*/
|
||||
function isValidFileExtension(filePath: UniversalPath, options: StaticServerOptions): boolean {
|
||||
return (
|
||||
(
|
||||
!options.allowedExtensions
|
||||
|| options.allowedExtensions.includes(filePath.ext)
|
||||
)
|
||||
&& (
|
||||
!options.excludedExtensions
|
||||
|| !options.excludedExtensions.includes(filePath.ext)
|
||||
)
|
||||
)
|
||||
}
|
||||
|
||||
/**
|
||||
* Resolve the configured base path into a universal path.
|
||||
* Defaults to `{app path}/resources/static` if none provided.
|
||||
* @param appPath
|
||||
* @param basePath
|
||||
*/
|
||||
function getBasePath(appPath: UniversalPath, basePath?: string | string[] | UniversalPath): UniversalPath {
|
||||
if ( basePath instanceof UniversalPath ) {
|
||||
return basePath
|
||||
}
|
||||
|
||||
if ( !basePath ) {
|
||||
return appPath.concat('resources', 'static')
|
||||
}
|
||||
|
||||
if ( Array.isArray(basePath) ) {
|
||||
return appPath.concat(...basePath)
|
||||
}
|
||||
|
||||
if ( basePath.startsWith('/') ) {
|
||||
return universalPath(basePath)
|
||||
}
|
||||
|
||||
return appPath.concat(basePath)
|
||||
}
|
||||
|
||||
/**
|
||||
* Get a route handler that serves a directory as static files.
|
||||
* @param options
|
||||
*/
|
||||
export function staticServer(options: StaticServerOptions = {}): RouteHandler {
|
||||
return async (request: Request) => {
|
||||
const config = <Config> request.make(Config)
|
||||
const route = <ActivatedRoute> request.make(ActivatedRoute)
|
||||
const app = <Application> request.make(Application)
|
||||
|
||||
const staticConfig = config.get('server.builtIns.static', {})
|
||||
const mergedOptions = {
|
||||
...staticConfig,
|
||||
...options,
|
||||
}
|
||||
|
||||
// Resolve the path to the resource on the filesystem
|
||||
const basePath = getBasePath(app.appPath(), mergedOptions.basePath)
|
||||
const filePath = basePath.concat(...Collection.normalize<string>(route.params[0]))
|
||||
|
||||
// If the resolved path is outside of the base path, fail out
|
||||
if ( !filePath.isChildOf(basePath) && !filePath.is(basePath) ) {
|
||||
throw new StaticServerHTTPError(HTTPStatus.NOT_FOUND, 'File not found', {
|
||||
basePath: basePath.toString(),
|
||||
filePath: filePath.toString(),
|
||||
route: route.path,
|
||||
reason: 'Resolved file is not a child of the base path.',
|
||||
})
|
||||
}
|
||||
|
||||
// If the resolved file is an invalid file extension, fail out
|
||||
if ( !isValidFileExtension(filePath, mergedOptions) ) {
|
||||
throw new StaticServerHTTPError(HTTPStatus.NOT_FOUND, 'File not found', {
|
||||
basePath: basePath.toString(),
|
||||
filePath: filePath.toString(),
|
||||
route: route.path,
|
||||
allowedExtensions: mergedOptions.allowedExtensions,
|
||||
excludedExtensions: mergedOptions.excludedExtensions,
|
||||
reason: 'Resolved file is not an allowed extension type',
|
||||
})
|
||||
}
|
||||
|
||||
// If the resolved file does not exist on the filesystem, fail out
|
||||
if ( !(await filePath.exists()) ) {
|
||||
throw new StaticServerHTTPError(HTTPStatus.NOT_FOUND, `File not found: ${route.path}`, {
|
||||
basePath: basePath.toString(),
|
||||
filePath: filePath.toString(),
|
||||
route: route.path,
|
||||
reason: 'Resolved file does not exist on the filesystem',
|
||||
})
|
||||
}
|
||||
|
||||
// If the resolved path is a directory, send the directory listing response
|
||||
if ( await filePath.isDirectory() ) {
|
||||
if ( !route.path.endsWith('/') ) {
|
||||
return redirect(`${route.path}/`)
|
||||
}
|
||||
|
||||
return getDirectoryListingResponse(route.path, filePath)
|
||||
}
|
||||
|
||||
// Otherwise, just send the file as the response body
|
||||
return file(filePath)
|
||||
}
|
||||
}
|
||||
@@ -1,4 +1,5 @@
|
||||
export * from './util'
|
||||
export * from './lib'
|
||||
export * from './di'
|
||||
|
||||
export * from './event/types'
|
||||
@@ -42,6 +43,7 @@ export * from './http/response/ResponseFactory'
|
||||
export * from './http/response/StringResponseFactory'
|
||||
export * from './http/response/TemporaryRedirectResponseFactory'
|
||||
export * from './http/response/ViewResponseFactory'
|
||||
export * from './http/response/FileResponseFactory'
|
||||
|
||||
export * from './http/routing/ActivatedRoute'
|
||||
export * from './http/routing/Route'
|
||||
@@ -56,6 +58,8 @@ export * from './http/session/MemorySession'
|
||||
|
||||
export * from './http/Controller'
|
||||
|
||||
export * from './http/servers/static'
|
||||
|
||||
export * from './service/Canonical'
|
||||
export * from './service/CanonicalInstantiable'
|
||||
export * from './service/CanonicalRecursive'
|
||||
@@ -70,6 +74,7 @@ export * from './service/Middlewares'
|
||||
|
||||
export * from './support/cache/MemoryCache'
|
||||
export * from './support/cache/CacheFactory'
|
||||
export * from './support/NodeModules'
|
||||
|
||||
export * from './views/ViewEngine'
|
||||
export * from './views/ViewEngineFactory'
|
||||
|
||||
8
src/lib.ts
Normal file
8
src/lib.ts
Normal file
@@ -0,0 +1,8 @@
|
||||
import {UniversalPath} from './util'
|
||||
|
||||
/**
|
||||
* Get the path to the root of the @extollo/lib package.
|
||||
*/
|
||||
export function lib(): UniversalPath {
|
||||
return new UniversalPath(__dirname)
|
||||
}
|
||||
@@ -48,6 +48,12 @@ export function appPath(...parts: PathLike[]): UniversalPath {
|
||||
* The main application container.
|
||||
*/
|
||||
export class Application extends Container {
|
||||
public static readonly NODE_MODULES_INJECTION = 'extollo/npm'
|
||||
|
||||
public static get NODE_MODULES_PROVIDER(): string {
|
||||
return process.env.EXTOLLO_NPM || 'pnpm'
|
||||
}
|
||||
|
||||
public static getContainer(): Container {
|
||||
const existing = <Container | undefined> globalRegistry.getGlobal('extollo/injector')
|
||||
if ( !existing ) {
|
||||
@@ -205,6 +211,7 @@ export class Application extends Container {
|
||||
this.setupLogging()
|
||||
|
||||
this.registerFactory(new CacheFactory()) // FIXME move this somewhere else?
|
||||
this.registerSingleton(Application.NODE_MODULES_INJECTION, Application.NODE_MODULES_PROVIDER)
|
||||
|
||||
this.make<Logging>(Logging).debug(`Application root: ${this.baseDir}`)
|
||||
}
|
||||
|
||||
12
src/resources/views/auth/form.pug
Normal file
12
src/resources/views/auth/form.pug
Normal file
@@ -0,0 +1,12 @@
|
||||
extends ./theme
|
||||
|
||||
block content
|
||||
h3.login-heading.mb-4
|
||||
block heading
|
||||
|
||||
if errors
|
||||
each error in errors
|
||||
p.form-error-message #{error}
|
||||
|
||||
form(method='post' enctype='multipart/form-data')
|
||||
block form
|
||||
22
src/resources/views/auth/login.pug
Normal file
22
src/resources/views/auth/login.pug
Normal file
@@ -0,0 +1,22 @@
|
||||
extends ./form
|
||||
|
||||
block head
|
||||
title Login | #{config('app.name', 'Extollo')}
|
||||
|
||||
block heading
|
||||
| Login to Continue
|
||||
|
||||
block form
|
||||
.form-label-group
|
||||
input#inputUsername.form-control(type='text' name='username' value=(form_data ? form_data.username : '') required placeholder='Username' autofocus)
|
||||
label(for='inputUsername') Username
|
||||
.form-label-group
|
||||
input#inputPassword.form-control(type='password' name='password' required placeholder='Password')
|
||||
label(for='inputPassword') Password
|
||||
button.btn.btn-lg.btn-primary.btn-block.btn-login.text-uppercase.font-weight-bold.mb-2.form-submit-button(type='submit') Login
|
||||
|
||||
.text-center
|
||||
span.small Need an account?
|
||||
a(href='./register') Register here.
|
||||
// .text-center
|
||||
span.small(style="color: #999999;") Provider: #{provider_name}
|
||||
22
src/resources/views/auth/theme.pug
Normal file
22
src/resources/views/auth/theme.pug
Normal file
@@ -0,0 +1,22 @@
|
||||
html
|
||||
head
|
||||
meta(name='viewport' content='width=device-width initial-scale=1')
|
||||
|
||||
block head
|
||||
|
||||
block styles
|
||||
link(rel='stylesheet' href='https://stackpath.bootstrapcdn.com/bootstrap/4.3.1/css/bootstrap.min.css')
|
||||
link(rel='stylesheet' href=vendor('@extollo', 'auth/theme.css'))
|
||||
body
|
||||
.container-fluid
|
||||
.row.no-gutter
|
||||
.d-none.d-md-flex.col-md-6.col-lg-8.bg-image
|
||||
.col-md-6.col-lg-4
|
||||
.login.d-flex.align-items-center.py-5
|
||||
.container
|
||||
.row
|
||||
.col-md-9.col-lg-8.mx-auto
|
||||
block content
|
||||
|
||||
block scripts
|
||||
script(src='https://stackpath.bootstrapcdn.com/bootstrap/4.3.1/css/bootstrap.min.css')
|
||||
45
src/resources/views/static/dirlist.pug
Normal file
45
src/resources/views/static/dirlist.pug
Normal file
@@ -0,0 +1,45 @@
|
||||
doctype html
|
||||
html
|
||||
head
|
||||
title Index of #{dirname}
|
||||
style.
|
||||
body {
|
||||
font-family: Arial, sans-serif;
|
||||
}
|
||||
|
||||
table {
|
||||
border-collapse: collapse;
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
td, th {
|
||||
border: 1px solid #dddddd;
|
||||
text-align: left;
|
||||
padding: 8px;
|
||||
}
|
||||
|
||||
tr:nth-child(even) {
|
||||
background-color: #dddddd;
|
||||
}
|
||||
body
|
||||
h1 Directory Listing
|
||||
h2 #{dirname}
|
||||
table
|
||||
tr
|
||||
th Name
|
||||
th Type
|
||||
th Size
|
||||
tr
|
||||
td 📂
|
||||
a(href='..') ..
|
||||
td Directory
|
||||
td -
|
||||
each entry in contents
|
||||
tr
|
||||
td #{entry.isDirectory ? '📂 ' : ''}
|
||||
a(href='./' + entry.name) #{entry.name}
|
||||
td #{entry.isDirectory ? 'Directory' : 'File'}
|
||||
td #{entry.size}
|
||||
if !config('server.poweredBy.hide', false)
|
||||
hr
|
||||
small retrieved at #{(new Date).toDateString()} #{(new Date).toTimeString()} | powered by <a href="https://extollo.garrettmills.dev/" target="_blank">Extollo</a>
|
||||
@@ -1,10 +1,13 @@
|
||||
import {Singleton, Inject} from '../di'
|
||||
import {UniversalPath, Collection} from '../util'
|
||||
import {UniversalPath, Collection, Pipe, universalPath} from '../util'
|
||||
import {Unit} from '../lifecycle/Unit'
|
||||
import {Logging} from './Logging'
|
||||
import {Route} from '../http/routing/Route'
|
||||
import {HTTPMethod} from '../http/lifecycle/Request'
|
||||
import {ViewEngineFactory} from '../views/ViewEngineFactory'
|
||||
import {ViewEngine} from '../views/ViewEngine'
|
||||
import {lib} from '../lib'
|
||||
import {Config} from './Config'
|
||||
|
||||
/**
|
||||
* Application unit that loads the various route files from `app/http/routes` and pre-compiles the route handlers.
|
||||
@@ -14,10 +17,16 @@ export class Routing extends Unit {
|
||||
@Inject()
|
||||
protected readonly logging!: Logging
|
||||
|
||||
@Inject()
|
||||
protected readonly config!: Config
|
||||
|
||||
protected compiledRoutes: Collection<Route> = new Collection<Route>()
|
||||
|
||||
public async up(): Promise<void> {
|
||||
this.app().registerFactory(new ViewEngineFactory())
|
||||
const engine = <ViewEngine> this.make(ViewEngine)
|
||||
this.logging.verbose('Registering @extollo view engine namespace.')
|
||||
engine.registerNamespace('extollo', lib().concat('resources', 'views'))
|
||||
|
||||
for await ( const entry of this.path.walk() ) {
|
||||
if ( !entry.endsWith('.routes.js') ) {
|
||||
@@ -63,4 +72,55 @@ export class Routing extends Unit {
|
||||
public getCompiled(): Collection<Route> {
|
||||
return this.compiledRoutes
|
||||
}
|
||||
|
||||
/**
|
||||
* Resolve a UniversalPath to a file served as an asset.
|
||||
* @example
|
||||
* ```ts
|
||||
* this.getAssetPath('images', '123.jpg').toRemote // => http://localhost:8000/assets/images/123.jpg
|
||||
* ```
|
||||
* @param parts
|
||||
*/
|
||||
public getAssetPath(...parts: string[]): UniversalPath {
|
||||
return this.getAssetBase().concat(...parts)
|
||||
}
|
||||
|
||||
public getAssetBase(): UniversalPath {
|
||||
return this.getAppUrl().concat(this.config.get('server.builtIns.assets.prefix', '/assets'))
|
||||
}
|
||||
|
||||
public getVendorPath(namespace: string, ...parts: string[]): UniversalPath {
|
||||
return this.getVendorBase().concat(encodeURIComponent(namespace), ...parts)
|
||||
}
|
||||
|
||||
public getVendorBase(): UniversalPath {
|
||||
return this.getAppUrl().concat(this.config.get('server.builtIns.vendor.prefix', '/vendor'))
|
||||
}
|
||||
|
||||
public getAppUrl(): UniversalPath {
|
||||
const rawHost = String(this.config.get('server.url', 'http://localhost')).toLowerCase()
|
||||
const isSSL = rawHost.startsWith('https://')
|
||||
const port = this.config.get('server.port', 8000)
|
||||
|
||||
return Pipe.wrap<string>(rawHost)
|
||||
.unless(
|
||||
host => host.startsWith('http://') || host.startsWith('https'),
|
||||
host => `http://${host}`,
|
||||
)
|
||||
.when(
|
||||
host => {
|
||||
const hasPort = host.split(':').length > 2
|
||||
const defaultRaw = !isSSL && port === 80
|
||||
const defaultSSL = isSSL && port === 443
|
||||
return !hasPort && !defaultRaw && !defaultSSL
|
||||
},
|
||||
host => {
|
||||
const parts = host.split('/')
|
||||
parts[2] += `:${port}`
|
||||
return parts.join('/')
|
||||
},
|
||||
)
|
||||
.tap<UniversalPath>(host => universalPath(host))
|
||||
.get()
|
||||
}
|
||||
}
|
||||
|
||||
112
src/support/NodeModules.ts
Normal file
112
src/support/NodeModules.ts
Normal file
@@ -0,0 +1,112 @@
|
||||
import * as childProcess from 'child_process'
|
||||
import {UniversalPath} from '../util'
|
||||
import {Inject, Injectable, InjectParam} from '../di'
|
||||
import {Application} from '../lifecycle/Application'
|
||||
import {Logging} from '../service/Logging'
|
||||
import {NodeModule, ExtolloAwareNodeModule} from './types'
|
||||
import {EventBus} from '../event/EventBus'
|
||||
import {PackageDiscovered} from './PackageDiscovered'
|
||||
|
||||
/**
|
||||
* A helper class for discovering and interacting with
|
||||
* NPM-style modules.
|
||||
*/
|
||||
@Injectable()
|
||||
export class NodeModules {
|
||||
@Inject()
|
||||
protected readonly logging!: Logging
|
||||
|
||||
@Inject()
|
||||
protected readonly bus!: EventBus
|
||||
|
||||
constructor(
|
||||
@InjectParam(Application.NODE_MODULES_INJECTION)
|
||||
protected readonly manager: string,
|
||||
) { }
|
||||
|
||||
/**
|
||||
* Get the NodeModule entry for the base application.
|
||||
*/
|
||||
async app(): Promise<NodeModule> {
|
||||
return new Promise<NodeModule>((res, rej) => {
|
||||
childProcess.exec(`${this.manager} ls --json`, (error, stdout) => {
|
||||
if ( error ) {
|
||||
return rej(error)
|
||||
}
|
||||
|
||||
res(JSON.parse(stdout)[0])
|
||||
})
|
||||
})
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the path to the node_modules folder for the base application.
|
||||
*/
|
||||
async root(): Promise<UniversalPath> {
|
||||
return new Promise<UniversalPath>((res, rej) => {
|
||||
childProcess.exec(`${this.manager} root`, (error, stdout) => {
|
||||
if ( error ) {
|
||||
return rej(error)
|
||||
}
|
||||
|
||||
res(new UniversalPath(stdout.trim()))
|
||||
})
|
||||
})
|
||||
}
|
||||
|
||||
/**
|
||||
* Iterate over packages, recursively, starting with the base application's
|
||||
* package.json and fire PackageDiscovered events for any that have a valid
|
||||
* Extollo discovery entry.
|
||||
*/
|
||||
async discover(): Promise<void> {
|
||||
const root = await this.root()
|
||||
const module = await this.app()
|
||||
return this.discoverRoot(root, module)
|
||||
}
|
||||
|
||||
/**
|
||||
* Recursively discover child-packages from the node_modules root for the
|
||||
* given module.
|
||||
*
|
||||
* Fires PackageDiscovered events for valid, discovery-enabled packages.
|
||||
*
|
||||
* @param root - the path to node_modules
|
||||
* @param module - the module whose children we are discovering
|
||||
* @protected
|
||||
*/
|
||||
protected async discoverRoot(root: UniversalPath, module: NodeModule): Promise<void> {
|
||||
for ( const key in module.dependencies ) {
|
||||
if ( !Object.prototype.hasOwnProperty.call(module.dependencies, key) ) {
|
||||
continue
|
||||
}
|
||||
|
||||
this.logging.verbose(`Auto-discovery considering package: ${key}`)
|
||||
|
||||
try {
|
||||
const packageJson = root.concat(key, 'package.json')
|
||||
this.logging.verbose(`Auto-discovery package path: ${packageJson}`)
|
||||
if ( await packageJson.exists() ) {
|
||||
const packageJsonString: string = await packageJson.read()
|
||||
const packageJsonData: ExtolloAwareNodeModule = JSON.parse(packageJsonString)
|
||||
if ( !packageJsonData?.extollo?.discover ) {
|
||||
this.logging.debug(`Skipping non-discoverable package: ${key}`)
|
||||
continue
|
||||
}
|
||||
|
||||
this.logging.info(`Auto-discovering package: ${key}`)
|
||||
await this.bus.dispatch(new PackageDiscovered(packageJsonData, packageJson.clone()))
|
||||
|
||||
const packageNodeModules = packageJson.concat('..', 'node_modules')
|
||||
if ( await packageNodeModules.exists() && packageJsonData?.extollo?.recursiveDependencies?.discover ) {
|
||||
this.logging.debug(`Recursing: ${packageNodeModules}`)
|
||||
await this.discoverRoot(packageNodeModules, packageJsonData)
|
||||
}
|
||||
}
|
||||
} catch (e: unknown) {
|
||||
this.logging.error(`Encountered error while discovering package: ${key}`)
|
||||
this.logging.error(e)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
33
src/support/PackageDiscovered.ts
Normal file
33
src/support/PackageDiscovered.ts
Normal file
@@ -0,0 +1,33 @@
|
||||
import {Event} from '../event/Event'
|
||||
import {Awaitable, JSONState, UniversalPath} from '../util'
|
||||
import {ExtolloAwareNodeModule} from './types'
|
||||
|
||||
/**
|
||||
* An event indicating that an NPM package has been discovered
|
||||
* by the framework.
|
||||
*
|
||||
* Application services can listen for this event to register
|
||||
* various discovery logic (e.g. automatically boot units
|
||||
*/
|
||||
export class PackageDiscovered extends Event {
|
||||
constructor(
|
||||
public packageConfig: ExtolloAwareNodeModule,
|
||||
public packageJson: UniversalPath,
|
||||
) {
|
||||
super()
|
||||
}
|
||||
|
||||
dehydrate(): Awaitable<JSONState> {
|
||||
return {
|
||||
packageConfig: this.packageConfig as JSONState,
|
||||
packageJson: this.packageJson.toString(),
|
||||
}
|
||||
}
|
||||
|
||||
rehydrate(state: JSONState): Awaitable<void> {
|
||||
if ( typeof state === 'object' ) {
|
||||
this.packageConfig = (state.packageConfig as ExtolloAwareNodeModule)
|
||||
this.packageJson = new UniversalPath(String(state.packageJson))
|
||||
}
|
||||
}
|
||||
}
|
||||
45
src/support/types.ts
Normal file
45
src/support/types.ts
Normal file
@@ -0,0 +1,45 @@
|
||||
/**
|
||||
* Partial package.json that may contain a partial Extollo discovery config.
|
||||
*/
|
||||
export interface ExtolloPackageDiscoveryConfig {
|
||||
extollo?: {
|
||||
discover?: boolean,
|
||||
units?: {
|
||||
discover?: boolean,
|
||||
paths?: string[],
|
||||
},
|
||||
recursiveDependencies?: {
|
||||
discover?: boolean,
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
/**
|
||||
* Interface that defines a NodeModule dependency.
|
||||
*/
|
||||
export interface NodeDependencySpecEntry {
|
||||
from: string,
|
||||
version: string,
|
||||
resolved?: string,
|
||||
dependencies?: {[key: string]: NodeDependencySpecEntry},
|
||||
devDependencies?: {[key: string]: NodeDependencySpecEntry},
|
||||
unsavedDependencies?: {[key: string]: NodeDependencySpecEntry},
|
||||
optionalDependencies?: {[key: string]: NodeDependencySpecEntry},
|
||||
}
|
||||
|
||||
/**
|
||||
* Defines information and dependencies of an NPM package.
|
||||
*/
|
||||
export interface NodeModule {
|
||||
name?: string,
|
||||
version?: string,
|
||||
dependencies?: {[key: string]: NodeDependencySpecEntry},
|
||||
devDependencies?: {[key: string]: NodeDependencySpecEntry},
|
||||
unsavedDependencies?: {[key: string]: NodeDependencySpecEntry},
|
||||
optionalDependencies?: {[key: string]: NodeDependencySpecEntry},
|
||||
}
|
||||
|
||||
/**
|
||||
* Type alias for a NodeModule that contains an ExtolloPackageDiscoveryConfig.
|
||||
*/
|
||||
export type ExtolloAwareNodeModule = NodeModule & ExtolloPackageDiscoveryConfig
|
||||
@@ -50,6 +50,20 @@ class Collection<T> {
|
||||
return new Collection(items)
|
||||
}
|
||||
|
||||
/**
|
||||
* Create a new collection from an item or array of items.
|
||||
* Filters out undefined items.
|
||||
* @param itemOrItems
|
||||
*/
|
||||
public static normalize<T2>(itemOrItems: (CollectionItem<T2> | undefined)[] | CollectionItem<T2> | undefined): Collection<T2> {
|
||||
if ( !Array.isArray(itemOrItems) ) {
|
||||
itemOrItems = [itemOrItems]
|
||||
}
|
||||
|
||||
const items = itemOrItems.filter(x => typeof x !== 'undefined') as CollectionItem<T2>[]
|
||||
return new Collection<T2>(items)
|
||||
}
|
||||
|
||||
/**
|
||||
* Create a collection of "undefined" elements of a given size.
|
||||
* @param size
|
||||
|
||||
@@ -8,6 +8,11 @@ export type PipeOperator<T, T2> = (subject: T) => T2
|
||||
*/
|
||||
export type ReflexivePipeOperator<T> = (subject: T) => T
|
||||
|
||||
/**
|
||||
* A condition or condition-resolving function for pipe methods.
|
||||
*/
|
||||
export type PipeCondition<T> = boolean | ((subject: T) => boolean)
|
||||
|
||||
/**
|
||||
* A class for writing chained/conditional operations in a data-flow manner.
|
||||
*
|
||||
@@ -79,8 +84,8 @@ export class Pipe<T> {
|
||||
* @param check
|
||||
* @param op
|
||||
*/
|
||||
when(check: boolean, op: ReflexivePipeOperator<T>): Pipe<T> {
|
||||
if ( check ) {
|
||||
when(check: PipeCondition<T>, op: ReflexivePipeOperator<T>): Pipe<T> {
|
||||
if ( (typeof check === 'function' && check(this.subject)) || check ) {
|
||||
return Pipe.wrap(op(this.subject))
|
||||
}
|
||||
|
||||
@@ -94,8 +99,12 @@ export class Pipe<T> {
|
||||
* @param check
|
||||
* @param op
|
||||
*/
|
||||
unless(check: boolean, op: ReflexivePipeOperator<T>): Pipe<T> {
|
||||
return this.when(!check, op)
|
||||
unless(check: PipeCondition<T>, op: ReflexivePipeOperator<T>): Pipe<T> {
|
||||
if ( (typeof check === 'function' && check(this.subject)) || check ) {
|
||||
return this
|
||||
}
|
||||
|
||||
return Pipe.wrap(op(this.subject))
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -103,7 +112,7 @@ export class Pipe<T> {
|
||||
* @param check
|
||||
* @param op
|
||||
*/
|
||||
whenNot(check: boolean, op: ReflexivePipeOperator<T>): Pipe<T> {
|
||||
whenNot(check: PipeCondition<T>, op: ReflexivePipeOperator<T>): Pipe<T> {
|
||||
return this.unless(check, op)
|
||||
}
|
||||
|
||||
|
||||
@@ -2,6 +2,7 @@
|
||||
* Type representing a JSON serializable object.
|
||||
*/
|
||||
import {ErrorWithContext} from '../error/ErrorWithContext'
|
||||
import {Awaitable} from './types'
|
||||
|
||||
export type JSONState = { [key: string]: string | boolean | number | undefined | JSONState | Array<string | boolean | number | undefined | JSONState> }
|
||||
|
||||
@@ -30,14 +31,14 @@ export function isJSONState(what: unknown): what is JSONState {
|
||||
export interface Rehydratable {
|
||||
/**
|
||||
* Dehydrate this class' state and get it.
|
||||
* @return Promise<JSONState>
|
||||
* @return JSONState|Promise<JSONState>
|
||||
*/
|
||||
dehydrate(): Promise<JSONState>
|
||||
dehydrate(): Awaitable<JSONState>
|
||||
|
||||
/**
|
||||
* Rehydrate a state into this class.
|
||||
* @param {JSONState} state
|
||||
* @return void|Promise<void>
|
||||
*/
|
||||
rehydrate(state: JSONState): void | Promise<void>
|
||||
rehydrate(state: JSONState): Awaitable<void>
|
||||
}
|
||||
|
||||
@@ -1,9 +1,10 @@
|
||||
import * as nodePath from 'path'
|
||||
import * as fs from 'fs'
|
||||
import * as mkdirp from 'mkdirp'
|
||||
import { Filesystem } from './path/Filesystem'
|
||||
import ReadableStream = NodeJS.ReadableStream;
|
||||
import WritableStream = NodeJS.WritableStream;
|
||||
import * as mime from 'mime-types'
|
||||
import {FileNotFoundError, Filesystem} from './path/Filesystem'
|
||||
import {Collection} from '../collection/Collection'
|
||||
import {Readable, Writable} from 'stream'
|
||||
|
||||
/**
|
||||
* An item that could represent a path.
|
||||
@@ -22,6 +23,36 @@ export function universalPath(...parts: PathLike[]): UniversalPath {
|
||||
return main.concat(...concats)
|
||||
}
|
||||
|
||||
/**
|
||||
* Format bytes as human-readable text.
|
||||
*
|
||||
* @param bytes Number of bytes.
|
||||
* @param si True to use metric (SI) units, aka powers of 1000. False to use
|
||||
* binary (IEC), aka powers of 1024.
|
||||
* @param dp Number of decimal places to display.
|
||||
* @see https://stackoverflow.com/a/14919494/4971138
|
||||
*/
|
||||
export function bytesToHumanFileSize(bytes: number, si = false, dp = 1): string {
|
||||
const thresh = si ? 1000 : 1024
|
||||
|
||||
if (Math.abs(bytes) < thresh) {
|
||||
return bytes + ' B'
|
||||
}
|
||||
|
||||
const units = si
|
||||
? ['kB', 'MB', 'GB', 'TB', 'PB', 'EB', 'ZB', 'YB']
|
||||
: ['KiB', 'MiB', 'GiB', 'TiB', 'PiB', 'EiB', 'ZiB', 'YiB']
|
||||
let u = -1
|
||||
const r = 10 ** dp
|
||||
|
||||
do {
|
||||
bytes /= thresh
|
||||
++u
|
||||
} while (Math.round(Math.abs(bytes) * r) / r >= thresh && u < units.length - 1)
|
||||
|
||||
return bytes.toFixed(dp) + ' ' + units[u]
|
||||
}
|
||||
|
||||
/**
|
||||
* Walk recursively over entries in a directory.
|
||||
*
|
||||
@@ -155,6 +186,13 @@ export class UniversalPath {
|
||||
return `${this.prefix}${this.resourceLocalPath}`
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the basename of the path.
|
||||
*/
|
||||
get toBase(): string {
|
||||
return nodePath.basename(this.resourceLocalPath)
|
||||
}
|
||||
|
||||
/**
|
||||
* Append and resolve the given paths to this resource and return a new UniversalPath.
|
||||
*
|
||||
@@ -224,6 +262,44 @@ export class UniversalPath {
|
||||
return walk(this.resourceLocalPath)
|
||||
}
|
||||
|
||||
/**
|
||||
* Resolves true if this resource is a directory.
|
||||
*/
|
||||
async isDirectory(): Promise<boolean> {
|
||||
if ( this.filesystem ) {
|
||||
const stat = await this.filesystem.stat({
|
||||
storePath: this.resourceLocalPath,
|
||||
})
|
||||
|
||||
return stat.isDirectory
|
||||
}
|
||||
|
||||
try {
|
||||
return (await fs.promises.stat(this.resourceLocalPath)).isDirectory()
|
||||
} catch (e) {
|
||||
return false
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Resolves true if this resource is a regular file.
|
||||
*/
|
||||
async isFile(): Promise<boolean> {
|
||||
if ( this.filesystem ) {
|
||||
const stat = await this.filesystem.stat({
|
||||
storePath: this.resourceLocalPath,
|
||||
})
|
||||
|
||||
return stat.isFile
|
||||
}
|
||||
|
||||
try {
|
||||
return (await fs.promises.stat(this.resourceLocalPath)).isFile()
|
||||
} catch (e) {
|
||||
return false
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns true if the given resource exists at the path.
|
||||
*/
|
||||
@@ -244,6 +320,20 @@ export class UniversalPath {
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* List any immediate children of this resource.
|
||||
*/
|
||||
async list(): Promise<Collection<UniversalPath>> {
|
||||
if ( this.filesystem ) {
|
||||
const files = await this.filesystem.list(this.resourceLocalPath)
|
||||
return files.map(x => this.concat(x))
|
||||
}
|
||||
|
||||
const paths = await fs.promises.readdir(this.resourceLocalPath)
|
||||
return Collection.collect<string>(paths)
|
||||
.map(x => this.concat(x))
|
||||
}
|
||||
|
||||
/**
|
||||
* Recursively create this path as a directory. Equivalent to `mkdir -p` on Linux.
|
||||
*/
|
||||
@@ -290,7 +380,7 @@ export class UniversalPath {
|
||||
/**
|
||||
* Get a writable stream to this file's contents.
|
||||
*/
|
||||
async writeStream(): Promise<WritableStream> {
|
||||
async writeStream(): Promise<Writable> {
|
||||
if ( this.filesystem ) {
|
||||
return this.filesystem.putStoreFileAsStream({
|
||||
storePath: this.resourceLocalPath,
|
||||
@@ -304,7 +394,7 @@ export class UniversalPath {
|
||||
* Read the data from this resource's file as a string.
|
||||
*/
|
||||
async read(): Promise<string> {
|
||||
let stream: ReadableStream
|
||||
let stream: Readable
|
||||
if ( this.filesystem ) {
|
||||
stream = await this.filesystem.getStoreFileAsStream({
|
||||
storePath: this.resourceLocalPath,
|
||||
@@ -321,10 +411,37 @@ export class UniversalPath {
|
||||
})
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the size of this resource in bytes.
|
||||
*/
|
||||
async sizeInBytes(): Promise<number> {
|
||||
if ( this.filesystem ) {
|
||||
const stat = await this.filesystem.stat({
|
||||
storePath: this.resourceLocalPath,
|
||||
})
|
||||
|
||||
if ( stat.exists ) {
|
||||
return stat.sizeInBytes
|
||||
}
|
||||
|
||||
throw new FileNotFoundError(this.toString())
|
||||
}
|
||||
|
||||
const stat = await fs.promises.stat(this.resourceLocalPath)
|
||||
return stat.size
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the size of this resource, formatted in a human-readable string.
|
||||
*/
|
||||
async sizeForHumans(): Promise<string> {
|
||||
return bytesToHumanFileSize(await this.sizeInBytes())
|
||||
}
|
||||
|
||||
/**
|
||||
* Get a readable stream of this file's contents.
|
||||
*/
|
||||
async readStream(): Promise<ReadableStream> {
|
||||
async readStream(): Promise<Readable> {
|
||||
if ( this.filesystem ) {
|
||||
return this.filesystem.getStoreFileAsStream({
|
||||
storePath: this.resourceLocalPath,
|
||||
@@ -334,17 +451,70 @@ export class UniversalPath {
|
||||
}
|
||||
}
|
||||
|
||||
/* get mime_type() {
|
||||
return Mime.lookup(this.ext)
|
||||
/**
|
||||
* Returns true if this path exists in the subtree of the given path.
|
||||
* @param otherPath
|
||||
*/
|
||||
isChildOf(otherPath: UniversalPath): boolean {
|
||||
if ( (this.filesystem || otherPath.filesystem) && otherPath.filesystem !== this.filesystem ) {
|
||||
return false
|
||||
}
|
||||
|
||||
get content_type() {
|
||||
return Mime.contentType(this.ext)
|
||||
if ( this.prefix !== otherPath.prefix ) {
|
||||
return false
|
||||
}
|
||||
|
||||
get charset() {
|
||||
if ( this.mime_type ) {
|
||||
return Mime.charset(this.mime_type)
|
||||
const relative = nodePath.relative(otherPath.toLocal, this.toLocal)
|
||||
return Boolean(relative && !relative.startsWith('..') && !nodePath.isAbsolute(relative))
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns true if the given path exists in the subtree of this path.
|
||||
* @param otherPath
|
||||
*/
|
||||
isParentOf(otherPath: UniversalPath): boolean {
|
||||
return otherPath.isChildOf(this)
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns true if the given path refers to the same resource as this path.
|
||||
* @param otherPath
|
||||
*/
|
||||
is(otherPath: UniversalPath): boolean {
|
||||
if ( (this.filesystem || otherPath.filesystem) && otherPath.filesystem !== this.filesystem ) {
|
||||
return false
|
||||
}
|
||||
|
||||
if ( this.prefix !== otherPath.prefix ) {
|
||||
return false
|
||||
}
|
||||
|
||||
const relative = nodePath.relative(otherPath.toLocal, this.toLocal)
|
||||
return relative === ''
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the mime-type of this resource.
|
||||
*/
|
||||
get mimeType(): string | false {
|
||||
return mime.lookup(this.resourceLocalPath)
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the content-type header of this resource.
|
||||
*/
|
||||
get contentType(): string | false {
|
||||
return mime.contentType(this.resourceLocalPath)
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the charset of this resource.
|
||||
*/
|
||||
get charset(): string | false {
|
||||
if ( this.mimeType ) {
|
||||
return mime.charset(this.mimeType)
|
||||
}
|
||||
|
||||
return false
|
||||
}
|
||||
}*/
|
||||
}
|
||||
|
||||
@@ -2,9 +2,10 @@ import {UniversalPath} from '../path'
|
||||
import * as path from 'path'
|
||||
import * as os from 'os'
|
||||
import {uuid4} from '../data'
|
||||
import ReadableStream = NodeJS.ReadableStream;
|
||||
import WritableStream = NodeJS.WritableStream;
|
||||
import {ErrorWithContext} from '../../error/ErrorWithContext'
|
||||
import {Readable, Writable} from 'stream'
|
||||
import {Awaitable} from '../types'
|
||||
import {Collection} from '../../collection/Collection'
|
||||
|
||||
/**
|
||||
* Error thrown when an operation is attempted on a non-existent file.
|
||||
@@ -65,6 +66,16 @@ export interface Stat {
|
||||
*/
|
||||
tags: string[],
|
||||
|
||||
/**
|
||||
* True if the resource exists as a directory.
|
||||
*/
|
||||
isDirectory: boolean,
|
||||
|
||||
/**
|
||||
* True if the resource exists as a regular file.
|
||||
*/
|
||||
isFile: boolean,
|
||||
|
||||
accessed?: Date,
|
||||
modified?: Date,
|
||||
created?: Date,
|
||||
@@ -77,12 +88,12 @@ export abstract class Filesystem {
|
||||
/**
|
||||
* Called when the Filesystem driver is initialized. Do any standup here.
|
||||
*/
|
||||
public open(): void | Promise<void> {} // eslint-disable-line @typescript-eslint/no-empty-function
|
||||
public open(): Awaitable<void> {} // eslint-disable-line @typescript-eslint/no-empty-function
|
||||
|
||||
/**
|
||||
* Called when the Filesystem driver is destroyed. Do any cleanup here.
|
||||
*/
|
||||
public close(): void | Promise<void> {} // eslint-disable-line @typescript-eslint/no-empty-function
|
||||
public close(): Awaitable<void> {} // eslint-disable-line @typescript-eslint/no-empty-function
|
||||
|
||||
/**
|
||||
* Get the URI prefix for this filesystem.
|
||||
@@ -114,62 +125,67 @@ export abstract class Filesystem {
|
||||
*
|
||||
* @param args
|
||||
*/
|
||||
public abstract putLocalFile(args: {localPath: string, storePath: string, mimeType?: string, tags?: string[], tag?: string}): void | Promise<void>
|
||||
public abstract putLocalFile(args: {localPath: string, storePath: string, mimeType?: string, tags?: string[], tag?: string}): Awaitable<void>
|
||||
|
||||
/**
|
||||
* Download a file in the remote filesystem to the local filesystem and return it as a UniversalPath.
|
||||
* @param args
|
||||
*/
|
||||
public abstract getStoreFileAsTemp(args: {storePath: string}): UniversalPath | Promise<UniversalPath>
|
||||
public abstract getStoreFileAsTemp(args: {storePath: string}): Awaitable<UniversalPath>
|
||||
|
||||
/**
|
||||
* Open a readable stream for a file in the remote filesystem.
|
||||
* @param args
|
||||
*/
|
||||
public abstract getStoreFileAsStream(args: {storePath: string}): ReadableStream | Promise<ReadableStream>
|
||||
public abstract getStoreFileAsStream(args: {storePath: string}): Awaitable<Readable>
|
||||
|
||||
/**
|
||||
* Open a writable stream for a file in the remote filesystem.
|
||||
* @param args
|
||||
*/
|
||||
public abstract putStoreFileAsStream(args: {storePath: string}): WritableStream | Promise<WritableStream>
|
||||
public abstract putStoreFileAsStream(args: {storePath: string}): Awaitable<Writable>
|
||||
|
||||
/**
|
||||
* Fetch some information about a file that may or may not be in the remote filesystem without fetching the entire file.
|
||||
* @param args
|
||||
*/
|
||||
public abstract stat(args: {storePath: string}): Stat | Promise<Stat>
|
||||
public abstract stat(args: {storePath: string}): Awaitable<Stat>
|
||||
|
||||
/**
|
||||
* If the file does not exist in the remote filesystem, create it. If it does exist, update the modify timestamps.
|
||||
* @param args
|
||||
*/
|
||||
public abstract touch(args: {storePath: string}): void | Promise<void>
|
||||
public abstract touch(args: {storePath: string}): Awaitable<void>
|
||||
|
||||
/**
|
||||
* Remove the given resource(s) from the remote filesystem.
|
||||
* @param args
|
||||
*/
|
||||
public abstract remove(args: {storePath: string, recursive?: boolean }): void | Promise<void>
|
||||
public abstract remove(args: {storePath: string, recursive?: boolean }): Awaitable<void>
|
||||
|
||||
/**
|
||||
* Create the given path on the store as a directory, recursively.
|
||||
* @param args
|
||||
*/
|
||||
public abstract mkdir(args: {storePath: string}): void | Promise<void>
|
||||
public abstract mkdir(args: {storePath: string}): Awaitable<void>
|
||||
|
||||
/**
|
||||
* Get the metadata object for the given file, if it exists.
|
||||
* @param storePath
|
||||
*/
|
||||
public abstract getMetadata(storePath: string): FileMetadata | Promise<FileMetadata>
|
||||
public abstract getMetadata(storePath: string): Awaitable<FileMetadata>
|
||||
|
||||
/**
|
||||
* Set the metadata object for the given file, if the file exists.
|
||||
* @param storePath
|
||||
* @param meta
|
||||
*/
|
||||
public abstract setMetadata(storePath: string, meta: FileMetadata): void | Promise<void>
|
||||
public abstract setMetadata(storePath: string, meta: FileMetadata): Awaitable<void>
|
||||
|
||||
/**
|
||||
* List direct children of this resource.
|
||||
*/
|
||||
public abstract list(storePath: string): Awaitable<Collection<string>>
|
||||
|
||||
/**
|
||||
* Normalize the input tags into a single array of strings. This is useful for implementing the fluent
|
||||
|
||||
@@ -4,6 +4,7 @@ import * as path from 'path'
|
||||
import {UniversalPath} from '../path'
|
||||
import * as rimraf from 'rimraf'
|
||||
import * as mkdirp from 'mkdirp'
|
||||
import { Collection } from '../../collection/Collection'
|
||||
|
||||
export interface LocalFilesystemConfig {
|
||||
baseDir: string
|
||||
@@ -87,6 +88,8 @@ export class LocalFilesystem extends Filesystem {
|
||||
accessed: stat.atime,
|
||||
modified: stat.mtime,
|
||||
created: stat.ctime,
|
||||
isDirectory: stat.isDirectory(),
|
||||
isFile: stat.isFile(),
|
||||
}
|
||||
} catch (e) {
|
||||
if ( e?.code === 'ENOENT' ) {
|
||||
@@ -95,6 +98,8 @@ export class LocalFilesystem extends Filesystem {
|
||||
exists: false,
|
||||
sizeInBytes: 0,
|
||||
tags: [],
|
||||
isFile: false,
|
||||
isDirectory: false,
|
||||
}
|
||||
}
|
||||
|
||||
@@ -166,4 +171,13 @@ export class LocalFilesystem extends Filesystem {
|
||||
protected metadataPath(storePath: string): string {
|
||||
return path.resolve(this.baseConfig.baseDir, 'meta', storePath + '.json')
|
||||
}
|
||||
|
||||
/**
|
||||
* List all immediate children of the given path.
|
||||
* @param storePath
|
||||
*/
|
||||
public async list(storePath: string): Promise<Collection<string>> {
|
||||
const paths = await fs.promises.readdir(this.storePath(storePath))
|
||||
return Collection.collect<string>(paths)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -2,7 +2,8 @@ import {FileMetadata, Filesystem, Stat} from './Filesystem'
|
||||
import * as ssh2 from 'ssh2'
|
||||
import * as path from 'path'
|
||||
import * as fs from 'fs'
|
||||
import ReadableStream = NodeJS.ReadableStream
|
||||
import {Readable, Writable} from 'stream'
|
||||
import {Collection} from '../../collection/Collection'
|
||||
import {UniversalPath} from '../path'
|
||||
|
||||
/**
|
||||
@@ -37,7 +38,7 @@ export class SSHFilesystem extends Filesystem {
|
||||
})
|
||||
}
|
||||
|
||||
async getStoreFileAsStream(args: { storePath: string }): Promise<ReadableStream> {
|
||||
async getStoreFileAsStream(args: { storePath: string }): Promise<Readable> {
|
||||
const sftp = await this.getSFTP()
|
||||
return sftp.createReadStream(this.storePath(args.storePath))
|
||||
}
|
||||
@@ -62,7 +63,7 @@ export class SSHFilesystem extends Filesystem {
|
||||
})
|
||||
}
|
||||
|
||||
async putStoreFileAsStream(args: { storePath: string }): Promise<NodeJS.WritableStream> {
|
||||
async putStoreFileAsStream(args: { storePath: string }): Promise<Writable> {
|
||||
const sftp = await this.getSFTP()
|
||||
return sftp.createWriteStream(this.storePath(args.storePath))
|
||||
}
|
||||
@@ -126,6 +127,8 @@ export class SSHFilesystem extends Filesystem {
|
||||
accessed: stat.atime,
|
||||
modified: stat.mtime,
|
||||
created: stat.ctime,
|
||||
isFile: stat.isFile(),
|
||||
isDirectory: stat.isDirectory(),
|
||||
}
|
||||
} catch (e) {
|
||||
return {
|
||||
@@ -133,6 +136,8 @@ export class SSHFilesystem extends Filesystem {
|
||||
exists: false,
|
||||
sizeInBytes: 0,
|
||||
tags: [],
|
||||
isFile: false,
|
||||
isDirectory: false,
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -243,7 +248,7 @@ export class SSHFilesystem extends Filesystem {
|
||||
* @protected
|
||||
*/
|
||||
protected storePath(storePath: string): string {
|
||||
return path.resolve(this.baseConfig.baseDir, 'data', storePath)
|
||||
return path.join(this.baseConfig.baseDir, 'data', storePath)
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -252,7 +257,7 @@ export class SSHFilesystem extends Filesystem {
|
||||
* @protected
|
||||
*/
|
||||
protected metadataPath(storePath: string): string {
|
||||
return path.resolve(this.baseConfig.baseDir, 'meta', storePath + '.json')
|
||||
return path.join(this.baseConfig.baseDir, 'meta', storePath + '.json')
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -268,4 +273,18 @@ export class SSHFilesystem extends Filesystem {
|
||||
stream.on('end', () => resolve(Buffer.concat(chunks).toString('utf8')))
|
||||
})
|
||||
}
|
||||
|
||||
async list(storePath: string): Promise<Collection<string>> {
|
||||
const sftp = await this.getSFTP()
|
||||
|
||||
return new Promise<Collection<string>>((res, rej) => {
|
||||
sftp.readdir(this.storePath(storePath), (error, files) => {
|
||||
if ( error ) {
|
||||
rej(error)
|
||||
} else {
|
||||
res(Collection.collect(files).map(x => x.filename))
|
||||
}
|
||||
})
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
@@ -17,29 +17,36 @@ export class PugViewEngine extends ViewEngine {
|
||||
public renderByName(templateName: string, locals: { [p: string]: any }): string | Promise<string> {
|
||||
let compiled = this.compileCache[templateName]
|
||||
if ( compiled ) {
|
||||
return compiled(locals)
|
||||
return compiled({
|
||||
...this.getGlobals(),
|
||||
...locals,
|
||||
})
|
||||
}
|
||||
|
||||
if ( !templateName.endsWith('.pug') ) {
|
||||
templateName += '.pug'
|
||||
}
|
||||
const filePath = this.path.concat(...templateName.split(':'))
|
||||
compiled = pug.compileFile(filePath.toLocal, this.getOptions())
|
||||
const filePath = this.resolveName(templateName)
|
||||
compiled = pug.compileFile(filePath.toLocal, this.getOptions(templateName))
|
||||
|
||||
this.compileCache[templateName] = compiled
|
||||
return compiled(locals)
|
||||
return compiled({
|
||||
...this.getGlobals(),
|
||||
...locals,
|
||||
})
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the object of options passed to Pug's compile methods.
|
||||
* @protected
|
||||
*/
|
||||
protected getOptions(): pug.Options {
|
||||
protected getOptions(templateName?: string): pug.Options {
|
||||
return {
|
||||
basedir: this.path.toLocal,
|
||||
basedir: templateName ? this.resolveBasePath(templateName).toLocal : this.path.toLocal,
|
||||
debug: this.debug,
|
||||
compileDebug: this.debug,
|
||||
globals: [],
|
||||
}
|
||||
}
|
||||
|
||||
getFileExtension(): string {
|
||||
return '.pug'
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,7 +1,8 @@
|
||||
import {AppClass} from '../lifecycle/AppClass'
|
||||
import {Config} from '../service/Config'
|
||||
import {Container} from '../di'
|
||||
import {UniversalPath} from '../util'
|
||||
import {ErrorWithContext, UniversalPath} from '../util'
|
||||
import {Routing} from '../service/Routing'
|
||||
|
||||
/**
|
||||
* Abstract base class for rendering views via different view engines.
|
||||
@@ -9,11 +10,16 @@ import {UniversalPath} from '../util'
|
||||
export abstract class ViewEngine extends AppClass {
|
||||
protected readonly config: Config
|
||||
|
||||
protected readonly routing: Routing
|
||||
|
||||
protected readonly debug: boolean
|
||||
|
||||
protected readonly namespaces: {[key: string]: UniversalPath} = {}
|
||||
|
||||
constructor() {
|
||||
super()
|
||||
this.config = Container.getContainer().make(Config)
|
||||
this.routing = Container.getContainer().make(Routing)
|
||||
this.debug = (this.config.get('server.mode', 'production') === 'development'
|
||||
|| this.config.get('server.debug', false))
|
||||
}
|
||||
@@ -38,4 +44,86 @@ export abstract class ViewEngine extends AppClass {
|
||||
* @param locals
|
||||
*/
|
||||
public abstract renderByName(templateName: string, locals: {[key: string]: any}): string | Promise<string>
|
||||
|
||||
/**
|
||||
* Get the file extension of template files of this engine.
|
||||
* @example `.pug`
|
||||
*/
|
||||
public abstract getFileExtension(): string
|
||||
|
||||
/**
|
||||
* Get the global variables that should be passed to every view rendered.
|
||||
* @protected
|
||||
*/
|
||||
protected getGlobals(): {[key: string]: any} {
|
||||
return {
|
||||
app: this.app(),
|
||||
config: (key: string, fallback?: any) => this.config.get(key, fallback),
|
||||
asset: (...parts: string[]) => this.routing.getAssetPath(...parts).toRemote,
|
||||
vendor: (namespace: string, ...parts: string[]) => this.routing.getVendorPath(namespace, ...parts).toRemote,
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Register a path as a root for rendering views prefixed with the given namespace.
|
||||
* @param namespace
|
||||
* @param basePath
|
||||
*/
|
||||
public registerNamespace(namespace: string, basePath: UniversalPath): this {
|
||||
if ( namespace.startsWith('@') ) {
|
||||
namespace = namespace.substr(1)
|
||||
}
|
||||
|
||||
this.namespaces[namespace] = basePath
|
||||
return this
|
||||
}
|
||||
|
||||
/**
|
||||
* Given the name of a template, get a UniversalPath pointing to its file.
|
||||
* @param templateName
|
||||
*/
|
||||
public resolveName(templateName: string): UniversalPath {
|
||||
let path = this.path
|
||||
if ( templateName.startsWith('@') ) {
|
||||
const [namespace, ...parts] = templateName.split(':')
|
||||
path = this.namespaces[namespace.substr(1)]
|
||||
|
||||
if ( !path ) {
|
||||
throw new ErrorWithContext('Invalid template namespace: ' + namespace, {
|
||||
namespace,
|
||||
templateName,
|
||||
})
|
||||
}
|
||||
|
||||
templateName = parts.join(':')
|
||||
}
|
||||
|
||||
if ( !templateName.endsWith(this.getFileExtension()) ) {
|
||||
templateName += this.getFileExtension()
|
||||
}
|
||||
|
||||
return path.concat(...templateName.split(':'))
|
||||
}
|
||||
|
||||
/**
|
||||
* Given the name of a template, get a UniversalPath to the root of the tree where
|
||||
* that template resides.
|
||||
* @param templateName
|
||||
*/
|
||||
public resolveBasePath(templateName: string): UniversalPath {
|
||||
let path = this.path
|
||||
if ( templateName.startsWith('@') ) {
|
||||
const [namespace] = templateName.split(':')
|
||||
path = this.namespaces[namespace.substr(1)]
|
||||
|
||||
if ( !path ) {
|
||||
throw new ErrorWithContext('Invalid template namespace: ' + namespace, {
|
||||
namespace,
|
||||
templateName,
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
return path
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user