1
0
mirror of https://github.com/sasjs/server.git synced 2025-12-10 11:24:35 +00:00

Compare commits

...

22 Commits

Author SHA1 Message Date
semantic-release-bot
476f834a80 chore(release): 0.17.0 [skip ci]
# [0.17.0](https://github.com/sasjs/server/compare/v0.16.1...v0.17.0) (2022-08-25)

### Bug Fixes

* allow underscores in file name ([bce83cb](bce83cb6fb))

### Features

* add the functionality of saving file by ctrl + s in editor ([3a3c90d](3a3c90d9e6))
2022-08-25 09:47:48 +00:00
Sabir Hassan
8b8739a873 Merge pull request #263 from sasjs/ctrl-save
feat: add the functionality of saving file by ctrl + s in editor
2022-08-25 14:43:50 +05:00
bce83cb6fb fix: allow underscores in file name 2022-08-25 14:27:42 +05:00
3a3c90d9e6 feat: add the functionality of saving file by ctrl + s in editor 2022-08-25 14:12:51 +05:00
semantic-release-bot
e63eaa5302 chore(release): 0.16.1 [skip ci]
## [0.16.1](https://github.com/sasjs/server/compare/v0.16.0...v0.16.1) (2022-08-24)

### Bug Fixes

* update response of /SASjsApi/stp/execute and /SASjsApi/code/execute ([98ea2ac](98ea2ac9b9))
2022-08-24 16:07:11 +00:00
Sabir Hassan
65de1bb175 Merge pull request #261 from sasjs/issue-258
fix: update response of /SASjsApi/stp/execute and /SASjsApi/code/execute
2022-08-24 21:02:57 +05:00
98ea2ac9b9 fix: update response of /SASjsApi/stp/execute and /SASjsApi/code/execute 2022-08-19 15:06:39 +05:00
semantic-release-bot
e94c56b23f chore(release): 0.16.0 [skip ci]
# [0.16.0](https://github.com/sasjs/server/compare/v0.15.3...v0.16.0) (2022-08-17)

### Bug Fixes

* add a new variable _SASJS_WEBOUT_HEADERS to code.js and code.py ([882bedd](882bedd5d5))
* update content for code.sas file ([02e88ae](02e88ae728))
* update default content type for python and js runtimes ([8780b80](8780b800a3))

### Features

* implement the logic for running python stored programs ([b06993a](b06993ab9e))
2022-08-17 20:49:53 +00:00
Allan Bowe
64f80e958d Merge pull request #259 from sasjs/python-runtime
feat: implement the logic for running python stored programs
2022-08-17 21:45:55 +01:00
bd97363c13 chore: quick fixes 2022-08-18 01:39:03 +05:00
02e88ae728 fix: update content for code.sas file 2022-08-18 01:20:33 +05:00
882bedd5d5 fix: add a new variable _SASJS_WEBOUT_HEADERS to code.js and code.py 2022-08-18 01:19:47 +05:00
8780b800a3 fix: update default content type for python and js runtimes 2022-08-18 01:17:17 +05:00
4c11082796 chore: addressed comments 2022-08-17 21:24:30 +05:00
a9b25b8880 chore: added specs for stp 2022-08-16 22:39:15 +05:00
b06993ab9e feat: implement the logic for running python stored programs 2022-08-16 15:51:37 +05:00
semantic-release-bot
f736e67517 chore(release): 0.15.3 [skip ci]
## [0.15.3](https://github.com/sasjs/server/compare/v0.15.2...v0.15.3) (2022-08-11)

### Bug Fixes

* adding proc printto in precode to enable print output in log.  Closes [#253](https://github.com/sasjs/server/issues/253) ([f8bb732](f8bb7327a8))
2022-08-11 15:07:31 +00:00
Allan Bowe
0f4a60c0c7 Merge pull request #254 from sasjs/allanbowe/sasjs-server-does-not-253
fix: adding proc printto in precode to enable print output in log.  Closes #253
2022-08-11 16:03:13 +01:00
Allan Bowe
f8bb7327a8 fix: adding proc printto in precode to enable print output in log. Closes #253 2022-08-11 15:01:46 +00:00
semantic-release-bot
abce135da2 chore(release): 0.15.2 [skip ci]
## [0.15.2](https://github.com/sasjs/server/compare/v0.15.1...v0.15.2) (2022-08-10)

### Bug Fixes

* remove vulnerabitities ([f27ac51](f27ac51fc4))
2022-08-10 11:28:07 +00:00
Allan Bowe
a6c014946a Merge pull request #252 from sasjs/fix-vulnerabilities
fix: remove vulnerabitities
2022-08-10 12:23:23 +01:00
f27ac51fc4 fix: remove vulnerabitities 2022-08-10 16:10:37 +05:00
26 changed files with 1089 additions and 902 deletions

View File

@@ -1,3 +1,50 @@
# [0.17.0](https://github.com/sasjs/server/compare/v0.16.1...v0.17.0) (2022-08-25)
### Bug Fixes
* allow underscores in file name ([bce83cb](https://github.com/sasjs/server/commit/bce83cb6fbc98f8198564c9399821f5829acc767))
### Features
* add the functionality of saving file by ctrl + s in editor ([3a3c90d](https://github.com/sasjs/server/commit/3a3c90d9e690ac5267bf1acc834b5b5c5b4dadb6))
## [0.16.1](https://github.com/sasjs/server/compare/v0.16.0...v0.16.1) (2022-08-24)
### Bug Fixes
* update response of /SASjsApi/stp/execute and /SASjsApi/code/execute ([98ea2ac](https://github.com/sasjs/server/commit/98ea2ac9b98631605e39e5900e533727ea0e3d85))
# [0.16.0](https://github.com/sasjs/server/compare/v0.15.3...v0.16.0) (2022-08-17)
### Bug Fixes
* add a new variable _SASJS_WEBOUT_HEADERS to code.js and code.py ([882bedd](https://github.com/sasjs/server/commit/882bedd5d5da22de6ed45c03d0a261aadfb3a33c))
* update content for code.sas file ([02e88ae](https://github.com/sasjs/server/commit/02e88ae7280d020a753bc2c095a931c79ac392d1))
* update default content type for python and js runtimes ([8780b80](https://github.com/sasjs/server/commit/8780b800a34aa618631821e5d97e26e8b0f15806))
### Features
* implement the logic for running python stored programs ([b06993a](https://github.com/sasjs/server/commit/b06993ab9ea24b28d9e553763187387685aaa666))
## [0.15.3](https://github.com/sasjs/server/compare/v0.15.2...v0.15.3) (2022-08-11)
### Bug Fixes
* adding proc printto in precode to enable print output in log. Closes [#253](https://github.com/sasjs/server/issues/253) ([f8bb732](https://github.com/sasjs/server/commit/f8bb7327a8a4649ac77bb6237e31cea075d46bb9))
## [0.15.2](https://github.com/sasjs/server/compare/v0.15.1...v0.15.2) (2022-08-10)
### Bug Fixes
* remove vulnerabitities ([f27ac51](https://github.com/sasjs/server/commit/f27ac51fc4beb21070d0ab551cfdaec1f6ba39e0))
## [0.15.1](https://github.com/sasjs/server/compare/v0.15.0...v0.15.1) (2022-08-10)

View File

@@ -64,12 +64,27 @@ Example contents of a `.env` file:
# Server mode is multi-user and suitable for intranet / internet use
MODE=
# A comma separated string that defines the available runTimes.
# Priority is given to the runtime that comes first in the string.
# Possible options at the moment are sas and js
# This string sets the priority of the available analytic runtimes
# Valid runtimes are SAS (sas), JavaScript (js) and Python (py)
# For each option provided, there should be a corresponding path,
# eg SAS_PATH, NODE_PATH or PYTHON_PATH
# Priority is given to runtimes earlier in the string
# Example options: [sas,js,py | js,py | sas | sas,js]
RUN_TIMES=
# Path to SAS executable (sas.exe / sas.sh)
SAS_PATH=/path/to/sas/executable.exe
# Path to Node.js executable
NODE_PATH=~/.nvm/versions/node/v16.14.0/bin/node
# Path to Python executable
PYTHON_PATH=/usr/bin/python
# Path to working directory
# This location is for SAS WORK, staged files, DRIVE, configuration etc
SASJS_ROOT=./sasjs_root
@@ -139,13 +154,6 @@ LOG_FORMAT_MORGAN=
# This location is for server logs with classical UNIX logrotate behavior
LOG_LOCATION=./sasjs_root/logs
# A comma separated string that defines the available runTimes.
# Priority is given to the runtime that comes first in the string.
# Possible options at the moment are sas and js
# options: [sas,js|js,sas|sas|js] default:sas
RUN_TIMES=
```
## Persisting the Session

View File

@@ -14,9 +14,10 @@ HELMET_COEP=[true|false] if omitted HELMET default will be used
DB_CONNECT=mongodb+srv://<DB_USERNAME>:<DB_PASSWORD>@<CLUSTER>/<DB_NAME>?retryWrites=true&w=majority
RUN_TIMES=[sas|js|sas,js|js,sas] default considered as sas
RUN_TIMES=[sas,js,py | js,py | sas | sas,js] default considered as sas
SAS_PATH=/opt/sas/sas9/SASHome/SASFoundation/9.4/sas
NODE_PATH=~/.nvm/versions/node/v16.14.0/bin/node
PYTHON_PATH=/usr/bin/python
SASJS_ROOT=./sasjs_root

503
api/package-lock.json generated
View File

@@ -23,7 +23,7 @@
"mongoose": "^6.0.12",
"mongoose-sequence": "^5.3.1",
"morgan": "^1.10.0",
"multer": "^1.4.3",
"multer": "^1.4.5-lts.1",
"rotating-file-stream": "^3.0.4",
"swagger-ui-express": "4.3.0",
"unzipper": "^0.10.11",
@@ -2184,9 +2184,9 @@
"integrity": "sha512-XAahCdThVuCFDQLT7R7Pk/vqeObFNL3YqRyFZg+AqAP/W1/w3xHaIxuW7WszQqTbIBOPRcItYJIou3i/mppu3Q=="
},
"node_modules/@types/whatwg-url": {
"version": "8.2.1",
"resolved": "https://registry.npmjs.org/@types/whatwg-url/-/whatwg-url-8.2.1.tgz",
"integrity": "sha512-2YubE1sjj5ifxievI5Ge1sckb9k/Er66HyR2c+3+I6VDUUg1TLPdYYTEbQ+DjRkS4nTxMJhgWfSfMRD2sl2EYQ==",
"version": "8.2.2",
"resolved": "https://registry.npmjs.org/@types/whatwg-url/-/whatwg-url-8.2.2.tgz",
"integrity": "sha512-FtQu10RWgn3D9U4aazdwIE2yzphmTJREDqNdODHrbrZmmMqI0vMheC/6NE/J1Yveaj8H+ela+YwWTjq5PGmuhA==",
"dependencies": {
"@types/node": "*",
"@types/webidl-conversions": "*"
@@ -2834,9 +2834,9 @@
}
},
"node_modules/bson": {
"version": "4.5.4",
"resolved": "https://registry.npmjs.org/bson/-/bson-4.5.4.tgz",
"integrity": "sha512-wIt0bPACnx8Ju9r6IsS2wVtGDHBr9Dxb+U29A1YED2pu8XOhS8aKjOnLZ8sxyXkPwanoK7iWWVhS1+coxde6xA==",
"version": "4.6.5",
"resolved": "https://registry.npmjs.org/bson/-/bson-4.6.5.tgz",
"integrity": "sha512-uqrgcjyOaZsHfz7ea8zLRCLe1u+QGUSzMZmvXqO24CDW7DWoW1qiN9folSwa7hSneTSgM2ykDIzF5kcQQ8cwNw==",
"dependencies": {
"buffer": "^5.6.0"
},
@@ -2903,38 +2903,16 @@
}
},
"node_modules/busboy": {
"version": "0.2.14",
"resolved": "https://registry.npmjs.org/busboy/-/busboy-0.2.14.tgz",
"integrity": "sha512-InWFDomvlkEj+xWLBfU3AvnbVYqeTWmQopiW0tWWEy5yehYm2YkGEc59sUmw/4ty5Zj/b0WHGs1LgecuBSBGrg==",
"version": "1.6.0",
"resolved": "https://registry.npmjs.org/busboy/-/busboy-1.6.0.tgz",
"integrity": "sha512-8SFQbg/0hQ9xy3UNTB0YEnsNBbWfhf7RtnzpL7TkBiTBRfrQ9Fxcnz7VJsleJpyp6rVLvXiuORqjlHi5q+PYuA==",
"dependencies": {
"dicer": "0.2.5",
"readable-stream": "1.1.x"
"streamsearch": "^1.1.0"
},
"engines": {
"node": ">=0.8.0"
"node": ">=10.16.0"
}
},
"node_modules/busboy/node_modules/isarray": {
"version": "0.0.1",
"resolved": "https://registry.npmjs.org/isarray/-/isarray-0.0.1.tgz",
"integrity": "sha1-ihis/Kmo9Bd+Cav8YDiTmwXR7t8="
},
"node_modules/busboy/node_modules/readable-stream": {
"version": "1.1.14",
"resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-1.1.14.tgz",
"integrity": "sha1-fPTFTvZI44EwhMY23SB54WbAgdk=",
"dependencies": {
"core-util-is": "~1.0.0",
"inherits": "~2.0.1",
"isarray": "0.0.1",
"string_decoder": "~0.10.x"
}
},
"node_modules/busboy/node_modules/string_decoder": {
"version": "0.10.31",
"resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-0.10.31.tgz",
"integrity": "sha1-YuIDvEF2bGwoyfyEMB2rHFMQ+pQ="
},
"node_modules/bytes": {
"version": "3.1.0",
"resolved": "https://registry.npmjs.org/bytes/-/bytes-3.1.0.tgz",
@@ -3566,39 +3544,6 @@
"node": ">=8"
}
},
"node_modules/dicer": {
"version": "0.2.5",
"resolved": "https://registry.npmjs.org/dicer/-/dicer-0.2.5.tgz",
"integrity": "sha512-FDvbtnq7dzlPz0wyYlOExifDEZcu8h+rErEXgfxqmLfRfC/kJidEFh4+effJRO3P0xmfqyPbSMG0LveNRfTKVg==",
"dependencies": {
"readable-stream": "1.1.x",
"streamsearch": "0.1.2"
},
"engines": {
"node": ">=0.8.0"
}
},
"node_modules/dicer/node_modules/isarray": {
"version": "0.0.1",
"resolved": "https://registry.npmjs.org/isarray/-/isarray-0.0.1.tgz",
"integrity": "sha1-ihis/Kmo9Bd+Cav8YDiTmwXR7t8="
},
"node_modules/dicer/node_modules/readable-stream": {
"version": "1.1.14",
"resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-1.1.14.tgz",
"integrity": "sha1-fPTFTvZI44EwhMY23SB54WbAgdk=",
"dependencies": {
"core-util-is": "~1.0.0",
"inherits": "~2.0.1",
"isarray": "0.0.1",
"string_decoder": "~0.10.x"
}
},
"node_modules/dicer/node_modules/string_decoder": {
"version": "0.10.31",
"resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-0.10.31.tgz",
"integrity": "sha1-YuIDvEF2bGwoyfyEMB2rHFMQ+pQ="
},
"node_modules/diff": {
"version": "4.0.2",
"resolved": "https://registry.npmjs.org/diff/-/diff-4.0.2.tgz",
@@ -4946,6 +4891,11 @@
"url": "https://github.com/sponsors/sindresorhus"
}
},
"node_modules/ip": {
"version": "2.0.0",
"resolved": "https://registry.npmjs.org/ip/-/ip-2.0.0.tgz",
"integrity": "sha512-WKa+XuLG1A1R0UWhl2+1XQSi+fZWMsYKffMZTTYsiZaUD8k2yDAj5atimTUD2TZkyCkNEeYE5NhFZmupOGtjYQ=="
},
"node_modules/ipaddr.js": {
"version": "1.9.1",
"resolved": "https://registry.npmjs.org/ipaddr.js/-/ipaddr.js-1.9.1.tgz",
@@ -6788,9 +6738,9 @@
"integrity": "sha512-UfpWE/VZn0iP50d8cz9NrZLM9lSWhcJ+0Gt/nm4by88UL+J1SiKN8/5dkjMmbEzwL2CAe+67GsegCbIKtbp75A=="
},
"node_modules/kareem": {
"version": "2.3.2",
"resolved": "https://registry.npmjs.org/kareem/-/kareem-2.3.2.tgz",
"integrity": "sha512-STHz9P7X2L4Kwn72fA4rGyqyXdmrMSdxqHx9IXon/FXluXieaFA6KJ2upcHAHxQPQ0LeM/OjLrhFxifHewOALQ=="
"version": "2.4.1",
"resolved": "https://registry.npmjs.org/kareem/-/kareem-2.4.1.tgz",
"integrity": "sha512-aJ9opVoXroQUPfovYP5kaj2lM7Jn02Gw13bL0lg9v0V7SaUc0qavPs0Eue7d2DcC3NjqI6QAUElXNsuZSeM+EA=="
},
"node_modules/kleur": {
"version": "3.0.3",
@@ -7093,13 +7043,14 @@
"dev": true
},
"node_modules/mongodb": {
"version": "4.1.4",
"resolved": "https://registry.npmjs.org/mongodb/-/mongodb-4.1.4.tgz",
"integrity": "sha512-Cv/sk8on/tpvvqbEvR1h03mdyNdyvvO+WhtFlL4jrZ+DSsN/oSQHVqmJQI/sBCqqbOArFcYCAYDfyzqFwV4GSQ==",
"version": "4.8.1",
"resolved": "https://registry.npmjs.org/mongodb/-/mongodb-4.8.1.tgz",
"integrity": "sha512-/NyiM3Ox9AwP5zrfT9TXjRKDJbXlLaUDQ9Rg//2lbg8D2A8GXV0VidYYnA/gfdK6uwbnL4FnAflH7FbGw3TS7w==",
"dependencies": {
"bson": "^4.5.4",
"bson": "^4.6.5",
"denque": "^2.0.1",
"mongodb-connection-string-url": "^2.1.0"
"mongodb-connection-string-url": "^2.5.2",
"socks": "^2.6.2"
},
"engines": {
"node": ">=12.9.0"
@@ -7109,21 +7060,40 @@
}
},
"node_modules/mongodb-connection-string-url": {
"version": "2.1.0",
"resolved": "https://registry.npmjs.org/mongodb-connection-string-url/-/mongodb-connection-string-url-2.1.0.tgz",
"integrity": "sha512-Qf9Zw7KGiRljWvMrrUFDdVqo46KIEiDuCzvEN97rh/PcKzk2bd6n9KuzEwBwW9xo5glwx69y1mI6s+jFUD/aIQ==",
"version": "2.5.3",
"resolved": "https://registry.npmjs.org/mongodb-connection-string-url/-/mongodb-connection-string-url-2.5.3.tgz",
"integrity": "sha512-f+/WsED+xF4B74l3k9V/XkTVj5/fxFH2o5ToKXd8Iyi5UhM+sO9u0Ape17Mvl/GkZaFtM0HQnzAG5OTmhKw+tQ==",
"dependencies": {
"@types/whatwg-url": "^8.2.1",
"whatwg-url": "^9.1.0"
"whatwg-url": "^11.0.0"
}
},
"node_modules/mongodb-connection-string-url/node_modules/tr46": {
"version": "3.0.0",
"resolved": "https://registry.npmjs.org/tr46/-/tr46-3.0.0.tgz",
"integrity": "sha512-l7FvfAHlcmulp8kr+flpQZmVwtu7nfRV7NZujtN0OqES8EL4O4e0qqzL0DC5gAvx/ZC/9lk6rhcUwYvkBnBnYA==",
"dependencies": {
"punycode": "^2.1.1"
},
"engines": {
"node": ">=12"
}
},
"node_modules/mongodb-connection-string-url/node_modules/webidl-conversions": {
"version": "7.0.0",
"resolved": "https://registry.npmjs.org/webidl-conversions/-/webidl-conversions-7.0.0.tgz",
"integrity": "sha512-VwddBukDzu71offAQR975unBIGqfKZpM+8ZX6ySk8nYhVoo5CYaZyzt3YBvYtRtO+aoGlqxPg/B87NGVZ/fu6g==",
"engines": {
"node": ">=12"
}
},
"node_modules/mongodb-connection-string-url/node_modules/whatwg-url": {
"version": "9.1.0",
"resolved": "https://registry.npmjs.org/whatwg-url/-/whatwg-url-9.1.0.tgz",
"integrity": "sha512-CQ0UcrPHyomtlOCot1TL77WyMIm/bCwrJ2D6AOKGwEczU9EpyoqAokfqrf/MioU9kHcMsmJZcg1egXix2KYEsA==",
"version": "11.0.0",
"resolved": "https://registry.npmjs.org/whatwg-url/-/whatwg-url-11.0.0.tgz",
"integrity": "sha512-RKT8HExMpoYx4igMiVMY83lN6UeITKJlBQ+vR/8ZJ8OCdSiN3RwCq+9gH0+Xzj0+5IrM6i4j/6LuvzbZIQgEcQ==",
"dependencies": {
"tr46": "^2.1.0",
"webidl-conversions": "^6.1.0"
"tr46": "^3.0.0",
"webidl-conversions": "^7.0.0"
},
"engines": {
"node": ">=12"
@@ -7221,19 +7191,17 @@
}
},
"node_modules/mongoose": {
"version": "6.0.12",
"resolved": "https://registry.npmjs.org/mongoose/-/mongoose-6.0.12.tgz",
"integrity": "sha512-BvsZk7zEEhb1AgQFLtxN9C+7qgy5edRuA3ZDDwHU+kHG/HM44vI6FdKV5m6HVdAUeCHHQTiVv+YQh8BRsToSHw==",
"version": "6.5.2",
"resolved": "https://registry.npmjs.org/mongoose/-/mongoose-6.5.2.tgz",
"integrity": "sha512-3CFDrSLtK2qjM1pZeZpLTUyqPRkc11Iuh74ZrwS4IwEJ3K2PqGnmyPLw7ex4Kzu37ujIMp3MAuiBlUjfrcb6hw==",
"dependencies": {
"bson": "^4.2.2",
"kareem": "2.3.2",
"mongodb": "4.1.3",
"mpath": "0.8.4",
"mquery": "4.0.0",
"ms": "2.1.2",
"regexp-clone": "1.0.0",
"sift": "13.5.2",
"sliced": "1.0.1"
"bson": "^4.6.5",
"kareem": "2.4.1",
"mongodb": "4.8.1",
"mpath": "0.9.0",
"mquery": "4.0.3",
"ms": "2.1.3",
"sift": "16.0.0"
},
"engines": {
"node": ">=12.0.0"
@@ -7255,26 +7223,10 @@
"mongoose": ">=4"
}
},
"node_modules/mongoose/node_modules/mongodb": {
"version": "4.1.3",
"resolved": "https://registry.npmjs.org/mongodb/-/mongodb-4.1.3.tgz",
"integrity": "sha512-lHvTqODBiSpuqjpCj48DOyYWS6Iq6ElJNUiH9HWdQtONyOfjgsKzJULipWduMGsSzaNO4nFi/kmlMFCLvjox/Q==",
"dependencies": {
"bson": "^4.5.2",
"denque": "^2.0.1",
"mongodb-connection-string-url": "^2.0.0"
},
"engines": {
"node": ">=12.9.0"
},
"optionalDependencies": {
"saslprep": "^1.0.3"
}
},
"node_modules/mongoose/node_modules/ms": {
"version": "2.1.2",
"resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz",
"integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w=="
"version": "2.1.3",
"resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz",
"integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA=="
},
"node_modules/morgan": {
"version": "1.10.0",
@@ -7300,30 +7252,28 @@
}
},
"node_modules/mpath": {
"version": "0.8.4",
"resolved": "https://registry.npmjs.org/mpath/-/mpath-0.8.4.tgz",
"integrity": "sha512-DTxNZomBcTWlrMW76jy1wvV37X/cNNxPW1y2Jzd4DZkAaC5ZGsm8bfGfNOthcDuRJujXLqiuS6o3Tpy0JEoh7g==",
"version": "0.9.0",
"resolved": "https://registry.npmjs.org/mpath/-/mpath-0.9.0.tgz",
"integrity": "sha512-ikJRQTk8hw5DEoFVxHG1Gn9T/xcjtdnOKIU1JTmGjZZlg9LST2mBLmcX3/ICIbgJydT2GOc15RnNy5mHmzfSew==",
"engines": {
"node": ">=4.0.0"
}
},
"node_modules/mquery": {
"version": "4.0.0",
"resolved": "https://registry.npmjs.org/mquery/-/mquery-4.0.0.tgz",
"integrity": "sha512-nGjm89lHja+T/b8cybAby6H0YgA4qYC/lx6UlwvHGqvTq8bDaNeCwl1sY8uRELrNbVWJzIihxVd+vphGGn1vBw==",
"version": "4.0.3",
"resolved": "https://registry.npmjs.org/mquery/-/mquery-4.0.3.tgz",
"integrity": "sha512-J5heI+P08I6VJ2Ky3+33IpCdAvlYGTSUjwTPxkAr8i8EoduPMBX2OY/wa3IKZIQl7MU4SbFk8ndgSKyB/cl1zA==",
"dependencies": {
"debug": "4.x",
"regexp-clone": "^1.0.0",
"sliced": "1.0.1"
"debug": "4.x"
},
"engines": {
"node": ">=12.0.0"
}
},
"node_modules/mquery/node_modules/debug": {
"version": "4.3.2",
"resolved": "https://registry.npmjs.org/debug/-/debug-4.3.2.tgz",
"integrity": "sha512-mOp8wKcvj7XxC78zLgw/ZA+6TSgkoE2C/ienthhRD298T7UNwAg9diBpLRxC0mOezLl4B0xV7M0cCO6P/O0Xhw==",
"version": "4.3.4",
"resolved": "https://registry.npmjs.org/debug/-/debug-4.3.4.tgz",
"integrity": "sha512-PRWFHuSU3eDtQJPvnNY7Jcket1j0t5OuOsFzPPzsekD52Zl8qUfFIPEiswXqIvHWGVHOgX+7G/vCNNhehwxfkQ==",
"dependencies": {
"ms": "2.1.2"
},
@@ -7347,22 +7297,20 @@
"integrity": "sha1-VgiurfwAvmwpAd9fmGF4jeDVl8g="
},
"node_modules/multer": {
"version": "1.4.4",
"resolved": "https://registry.npmjs.org/multer/-/multer-1.4.4.tgz",
"integrity": "sha512-2wY2+xD4udX612aMqMcB8Ws2Voq6NIUPEtD1be6m411T4uDH/VtL9i//xvcyFlTVfRdaBsk7hV5tgrGQqhuBiw==",
"deprecated": "Multer 1.x is affected by CVE-2022-24434. This is fixed in v1.4.4-lts.1 which drops support for versions of Node.js before 6. Please upgrade to at least Node.js 6 and version 1.4.4-lts.1 of Multer. If you need support for older versions of Node.js, we are open to accepting patches that would fix the CVE on the main 1.x release line, whilst maintaining compatibility with Node.js 0.10.",
"version": "1.4.5-lts.1",
"resolved": "https://registry.npmjs.org/multer/-/multer-1.4.5-lts.1.tgz",
"integrity": "sha512-ywPWvcDMeH+z9gQq5qYHCCy+ethsk4goepZ45GLD63fOu0YcNecQxi64nDs3qluZB+murG3/D4dJ7+dGctcCQQ==",
"dependencies": {
"append-field": "^1.0.0",
"busboy": "^0.2.11",
"busboy": "^1.0.0",
"concat-stream": "^1.5.2",
"mkdirp": "^0.5.4",
"object-assign": "^4.1.1",
"on-finished": "^2.3.0",
"type-is": "^1.6.4",
"xtend": "^4.0.0"
},
"engines": {
"node": ">= 0.10.0"
"node": ">= 6.0.0"
}
},
"node_modules/multer/node_modules/mkdirp": {
@@ -8353,11 +8301,6 @@
"node": ">=8.10.0"
}
},
"node_modules/regexp-clone": {
"version": "1.0.0",
"resolved": "https://registry.npmjs.org/regexp-clone/-/regexp-clone-1.0.0.tgz",
"integrity": "sha512-TuAasHQNamyyJ2hb97IuBEif4qBHGjPHBS64sZwytpLEqtBQ1gPJTnOaQ6qmpET16cK14kkjbazl6+p0RRv0yw=="
},
"node_modules/require-directory": {
"version": "2.1.1",
"resolved": "https://registry.npmjs.org/require-directory/-/require-directory-2.1.1.tgz",
@@ -8606,9 +8549,9 @@
}
},
"node_modules/sift": {
"version": "13.5.2",
"resolved": "https://registry.npmjs.org/sift/-/sift-13.5.2.tgz",
"integrity": "sha512-+gxdEOMA2J+AI+fVsCqeNn7Tgx3M9ZN9jdi95939l1IJ8cZsqS8sqpJyOkic2SJk+1+98Uwryt/gL6XDaV+UZA=="
"version": "16.0.0",
"resolved": "https://registry.npmjs.org/sift/-/sift-16.0.0.tgz",
"integrity": "sha512-ILTjdP2Mv9V1kIxWMXeMTIRbOBrqKc4JAXmFMnFq3fKeyQ2Qwa3Dw1ubcye3vR+Y6ofA0b9gNDr/y2t6eUeIzQ=="
},
"node_modules/signal-exit": {
"version": "3.0.3",
@@ -8706,10 +8649,27 @@
"node": ">=8"
}
},
"node_modules/sliced": {
"version": "1.0.1",
"resolved": "https://registry.npmjs.org/sliced/-/sliced-1.0.1.tgz",
"integrity": "sha1-CzpmK10Ewxd7GSa+qCsD+Dei70E="
"node_modules/smart-buffer": {
"version": "4.2.0",
"resolved": "https://registry.npmjs.org/smart-buffer/-/smart-buffer-4.2.0.tgz",
"integrity": "sha512-94hK0Hh8rPqQl2xXc3HsaBoOXKV20MToPkcXvwbISWLEs+64sBq5kFgn2kJDHb1Pry9yrP0dxrCI9RRci7RXKg==",
"engines": {
"node": ">= 6.0.0",
"npm": ">= 3.0.0"
}
},
"node_modules/socks": {
"version": "2.7.0",
"resolved": "https://registry.npmjs.org/socks/-/socks-2.7.0.tgz",
"integrity": "sha512-scnOe9y4VuiNUULJN72GrM26BNOjVsfPXI+j+98PkyEfsIXroa5ofyjT+FzGvn/xHs73U2JtoBYAVx9Hl4quSA==",
"dependencies": {
"ip": "^2.0.0",
"smart-buffer": "^4.2.0"
},
"engines": {
"node": ">= 10.13.0",
"npm": ">= 3.0.0"
}
},
"node_modules/source-map": {
"version": "0.6.1",
@@ -8808,11 +8768,11 @@
}
},
"node_modules/streamsearch": {
"version": "0.1.2",
"resolved": "https://registry.npmjs.org/streamsearch/-/streamsearch-0.1.2.tgz",
"integrity": "sha1-gIudDlb8Jz2Am6VzOOkpkZoanxo=",
"version": "1.1.0",
"resolved": "https://registry.npmjs.org/streamsearch/-/streamsearch-1.1.0.tgz",
"integrity": "sha512-Mcc5wHehp9aXz1ax6bZUyY5afg9u2rv5cqQI3mRrYkGC8rW2hM02jWuwjtL++LS5qinSyhj2QfLyNsuc+VsExg==",
"engines": {
"node": ">=0.8.0"
"node": ">=10.0.0"
}
},
"node_modules/string_decoder": {
@@ -9296,6 +9256,7 @@
"version": "2.1.0",
"resolved": "https://registry.npmjs.org/tr46/-/tr46-2.1.0.tgz",
"integrity": "sha512-15Ih7phfcdP5YxqiB+iDtLoaTz4Nd35+IiAv0kQ5FNKHzXgdWqPoTIqEDDJmXceQt4JZk6lVPT8lnDlPpGDppw==",
"dev": true,
"dependencies": {
"punycode": "^2.1.1"
},
@@ -9719,6 +9680,7 @@
"version": "6.1.0",
"resolved": "https://registry.npmjs.org/webidl-conversions/-/webidl-conversions-6.1.0.tgz",
"integrity": "sha512-qBIvFLGiBpLjfwmYAaHPXsn+ho5xZnGvyGvsarywGNc8VyQJUMHJ8OBKGGrPER0okBeMDaan4mNBlgBROxuI8w==",
"dev": true,
"engines": {
"node": ">=10.4"
}
@@ -11724,9 +11686,9 @@
"integrity": "sha512-XAahCdThVuCFDQLT7R7Pk/vqeObFNL3YqRyFZg+AqAP/W1/w3xHaIxuW7WszQqTbIBOPRcItYJIou3i/mppu3Q=="
},
"@types/whatwg-url": {
"version": "8.2.1",
"resolved": "https://registry.npmjs.org/@types/whatwg-url/-/whatwg-url-8.2.1.tgz",
"integrity": "sha512-2YubE1sjj5ifxievI5Ge1sckb9k/Er66HyR2c+3+I6VDUUg1TLPdYYTEbQ+DjRkS4nTxMJhgWfSfMRD2sl2EYQ==",
"version": "8.2.2",
"resolved": "https://registry.npmjs.org/@types/whatwg-url/-/whatwg-url-8.2.2.tgz",
"integrity": "sha512-FtQu10RWgn3D9U4aazdwIE2yzphmTJREDqNdODHrbrZmmMqI0vMheC/6NE/J1Yveaj8H+ela+YwWTjq5PGmuhA==",
"requires": {
"@types/node": "*",
"@types/webidl-conversions": "*"
@@ -12240,9 +12202,9 @@
}
},
"bson": {
"version": "4.5.4",
"resolved": "https://registry.npmjs.org/bson/-/bson-4.5.4.tgz",
"integrity": "sha512-wIt0bPACnx8Ju9r6IsS2wVtGDHBr9Dxb+U29A1YED2pu8XOhS8aKjOnLZ8sxyXkPwanoK7iWWVhS1+coxde6xA==",
"version": "4.6.5",
"resolved": "https://registry.npmjs.org/bson/-/bson-4.6.5.tgz",
"integrity": "sha512-uqrgcjyOaZsHfz7ea8zLRCLe1u+QGUSzMZmvXqO24CDW7DWoW1qiN9folSwa7hSneTSgM2ykDIzF5kcQQ8cwNw==",
"requires": {
"buffer": "^5.6.0"
}
@@ -12283,35 +12245,11 @@
"integrity": "sha512-9q/rDEGSb/Qsvv2qvzIzdluL5k7AaJOTrw23z9reQthrbF7is4CtlT0DXyO1oei2DCp4uojjzQ7igaSHp1kAEQ=="
},
"busboy": {
"version": "0.2.14",
"resolved": "https://registry.npmjs.org/busboy/-/busboy-0.2.14.tgz",
"integrity": "sha512-InWFDomvlkEj+xWLBfU3AvnbVYqeTWmQopiW0tWWEy5yehYm2YkGEc59sUmw/4ty5Zj/b0WHGs1LgecuBSBGrg==",
"version": "1.6.0",
"resolved": "https://registry.npmjs.org/busboy/-/busboy-1.6.0.tgz",
"integrity": "sha512-8SFQbg/0hQ9xy3UNTB0YEnsNBbWfhf7RtnzpL7TkBiTBRfrQ9Fxcnz7VJsleJpyp6rVLvXiuORqjlHi5q+PYuA==",
"requires": {
"dicer": "0.2.5",
"readable-stream": "1.1.x"
},
"dependencies": {
"isarray": {
"version": "0.0.1",
"resolved": "https://registry.npmjs.org/isarray/-/isarray-0.0.1.tgz",
"integrity": "sha1-ihis/Kmo9Bd+Cav8YDiTmwXR7t8="
},
"readable-stream": {
"version": "1.1.14",
"resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-1.1.14.tgz",
"integrity": "sha1-fPTFTvZI44EwhMY23SB54WbAgdk=",
"requires": {
"core-util-is": "~1.0.0",
"inherits": "~2.0.1",
"isarray": "0.0.1",
"string_decoder": "~0.10.x"
}
},
"string_decoder": {
"version": "0.10.31",
"resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-0.10.31.tgz",
"integrity": "sha1-YuIDvEF2bGwoyfyEMB2rHFMQ+pQ="
}
"streamsearch": "^1.1.0"
}
},
"bytes": {
@@ -12813,38 +12751,6 @@
"integrity": "sha512-TLz+x/vEXm/Y7P7wn1EJFNLxYpUD4TgMosxY6fAVJUnJMbupHBOncxyWUG9OpTaH9EBD7uFI5LfEgmMOc54DsA==",
"dev": true
},
"dicer": {
"version": "0.2.5",
"resolved": "https://registry.npmjs.org/dicer/-/dicer-0.2.5.tgz",
"integrity": "sha512-FDvbtnq7dzlPz0wyYlOExifDEZcu8h+rErEXgfxqmLfRfC/kJidEFh4+effJRO3P0xmfqyPbSMG0LveNRfTKVg==",
"requires": {
"readable-stream": "1.1.x",
"streamsearch": "0.1.2"
},
"dependencies": {
"isarray": {
"version": "0.0.1",
"resolved": "https://registry.npmjs.org/isarray/-/isarray-0.0.1.tgz",
"integrity": "sha1-ihis/Kmo9Bd+Cav8YDiTmwXR7t8="
},
"readable-stream": {
"version": "1.1.14",
"resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-1.1.14.tgz",
"integrity": "sha1-fPTFTvZI44EwhMY23SB54WbAgdk=",
"requires": {
"core-util-is": "~1.0.0",
"inherits": "~2.0.1",
"isarray": "0.0.1",
"string_decoder": "~0.10.x"
}
},
"string_decoder": {
"version": "0.10.31",
"resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-0.10.31.tgz",
"integrity": "sha1-YuIDvEF2bGwoyfyEMB2rHFMQ+pQ="
}
}
},
"diff": {
"version": "4.0.2",
"resolved": "https://registry.npmjs.org/diff/-/diff-4.0.2.tgz",
@@ -13868,6 +13774,11 @@
"p-is-promise": "^3.0.0"
}
},
"ip": {
"version": "2.0.0",
"resolved": "https://registry.npmjs.org/ip/-/ip-2.0.0.tgz",
"integrity": "sha512-WKa+XuLG1A1R0UWhl2+1XQSi+fZWMsYKffMZTTYsiZaUD8k2yDAj5atimTUD2TZkyCkNEeYE5NhFZmupOGtjYQ=="
},
"ipaddr.js": {
"version": "1.9.1",
"resolved": "https://registry.npmjs.org/ipaddr.js/-/ipaddr.js-1.9.1.tgz",
@@ -15248,9 +15159,9 @@
"integrity": "sha512-UfpWE/VZn0iP50d8cz9NrZLM9lSWhcJ+0Gt/nm4by88UL+J1SiKN8/5dkjMmbEzwL2CAe+67GsegCbIKtbp75A=="
},
"kareem": {
"version": "2.3.2",
"resolved": "https://registry.npmjs.org/kareem/-/kareem-2.3.2.tgz",
"integrity": "sha512-STHz9P7X2L4Kwn72fA4rGyqyXdmrMSdxqHx9IXon/FXluXieaFA6KJ2upcHAHxQPQ0LeM/OjLrhFxifHewOALQ=="
"version": "2.4.1",
"resolved": "https://registry.npmjs.org/kareem/-/kareem-2.4.1.tgz",
"integrity": "sha512-aJ9opVoXroQUPfovYP5kaj2lM7Jn02Gw13bL0lg9v0V7SaUc0qavPs0Eue7d2DcC3NjqI6QAUElXNsuZSeM+EA=="
},
"kleur": {
"version": "3.0.3",
@@ -15486,32 +15397,46 @@
"dev": true
},
"mongodb": {
"version": "4.1.4",
"resolved": "https://registry.npmjs.org/mongodb/-/mongodb-4.1.4.tgz",
"integrity": "sha512-Cv/sk8on/tpvvqbEvR1h03mdyNdyvvO+WhtFlL4jrZ+DSsN/oSQHVqmJQI/sBCqqbOArFcYCAYDfyzqFwV4GSQ==",
"version": "4.8.1",
"resolved": "https://registry.npmjs.org/mongodb/-/mongodb-4.8.1.tgz",
"integrity": "sha512-/NyiM3Ox9AwP5zrfT9TXjRKDJbXlLaUDQ9Rg//2lbg8D2A8GXV0VidYYnA/gfdK6uwbnL4FnAflH7FbGw3TS7w==",
"requires": {
"bson": "^4.5.4",
"bson": "^4.6.5",
"denque": "^2.0.1",
"mongodb-connection-string-url": "^2.1.0",
"saslprep": "^1.0.3"
"mongodb-connection-string-url": "^2.5.2",
"saslprep": "^1.0.3",
"socks": "^2.6.2"
}
},
"mongodb-connection-string-url": {
"version": "2.1.0",
"resolved": "https://registry.npmjs.org/mongodb-connection-string-url/-/mongodb-connection-string-url-2.1.0.tgz",
"integrity": "sha512-Qf9Zw7KGiRljWvMrrUFDdVqo46KIEiDuCzvEN97rh/PcKzk2bd6n9KuzEwBwW9xo5glwx69y1mI6s+jFUD/aIQ==",
"version": "2.5.3",
"resolved": "https://registry.npmjs.org/mongodb-connection-string-url/-/mongodb-connection-string-url-2.5.3.tgz",
"integrity": "sha512-f+/WsED+xF4B74l3k9V/XkTVj5/fxFH2o5ToKXd8Iyi5UhM+sO9u0Ape17Mvl/GkZaFtM0HQnzAG5OTmhKw+tQ==",
"requires": {
"@types/whatwg-url": "^8.2.1",
"whatwg-url": "^9.1.0"
"whatwg-url": "^11.0.0"
},
"dependencies": {
"whatwg-url": {
"version": "9.1.0",
"resolved": "https://registry.npmjs.org/whatwg-url/-/whatwg-url-9.1.0.tgz",
"integrity": "sha512-CQ0UcrPHyomtlOCot1TL77WyMIm/bCwrJ2D6AOKGwEczU9EpyoqAokfqrf/MioU9kHcMsmJZcg1egXix2KYEsA==",
"tr46": {
"version": "3.0.0",
"resolved": "https://registry.npmjs.org/tr46/-/tr46-3.0.0.tgz",
"integrity": "sha512-l7FvfAHlcmulp8kr+flpQZmVwtu7nfRV7NZujtN0OqES8EL4O4e0qqzL0DC5gAvx/ZC/9lk6rhcUwYvkBnBnYA==",
"requires": {
"tr46": "^2.1.0",
"webidl-conversions": "^6.1.0"
"punycode": "^2.1.1"
}
},
"webidl-conversions": {
"version": "7.0.0",
"resolved": "https://registry.npmjs.org/webidl-conversions/-/webidl-conversions-7.0.0.tgz",
"integrity": "sha512-VwddBukDzu71offAQR975unBIGqfKZpM+8ZX6ySk8nYhVoo5CYaZyzt3YBvYtRtO+aoGlqxPg/B87NGVZ/fu6g=="
},
"whatwg-url": {
"version": "11.0.0",
"resolved": "https://registry.npmjs.org/whatwg-url/-/whatwg-url-11.0.0.tgz",
"integrity": "sha512-RKT8HExMpoYx4igMiVMY83lN6UeITKJlBQ+vR/8ZJ8OCdSiN3RwCq+9gH0+Xzj0+5IrM6i4j/6LuvzbZIQgEcQ==",
"requires": {
"tr46": "^3.0.0",
"webidl-conversions": "^7.0.0"
}
}
}
@@ -15583,36 +15508,23 @@
}
},
"mongoose": {
"version": "6.0.12",
"resolved": "https://registry.npmjs.org/mongoose/-/mongoose-6.0.12.tgz",
"integrity": "sha512-BvsZk7zEEhb1AgQFLtxN9C+7qgy5edRuA3ZDDwHU+kHG/HM44vI6FdKV5m6HVdAUeCHHQTiVv+YQh8BRsToSHw==",
"version": "6.5.2",
"resolved": "https://registry.npmjs.org/mongoose/-/mongoose-6.5.2.tgz",
"integrity": "sha512-3CFDrSLtK2qjM1pZeZpLTUyqPRkc11Iuh74ZrwS4IwEJ3K2PqGnmyPLw7ex4Kzu37ujIMp3MAuiBlUjfrcb6hw==",
"requires": {
"bson": "^4.2.2",
"kareem": "2.3.2",
"mongodb": "4.1.3",
"mpath": "0.8.4",
"mquery": "4.0.0",
"ms": "2.1.2",
"regexp-clone": "1.0.0",
"sift": "13.5.2",
"sliced": "1.0.1"
"bson": "^4.6.5",
"kareem": "2.4.1",
"mongodb": "4.8.1",
"mpath": "0.9.0",
"mquery": "4.0.3",
"ms": "2.1.3",
"sift": "16.0.0"
},
"dependencies": {
"mongodb": {
"version": "4.1.3",
"resolved": "https://registry.npmjs.org/mongodb/-/mongodb-4.1.3.tgz",
"integrity": "sha512-lHvTqODBiSpuqjpCj48DOyYWS6Iq6ElJNUiH9HWdQtONyOfjgsKzJULipWduMGsSzaNO4nFi/kmlMFCLvjox/Q==",
"requires": {
"bson": "^4.5.2",
"denque": "^2.0.1",
"mongodb-connection-string-url": "^2.0.0",
"saslprep": "^1.0.3"
}
},
"ms": {
"version": "2.1.2",
"resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz",
"integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w=="
"version": "2.1.3",
"resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz",
"integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA=="
}
}
},
@@ -15645,24 +15557,22 @@
}
},
"mpath": {
"version": "0.8.4",
"resolved": "https://registry.npmjs.org/mpath/-/mpath-0.8.4.tgz",
"integrity": "sha512-DTxNZomBcTWlrMW76jy1wvV37X/cNNxPW1y2Jzd4DZkAaC5ZGsm8bfGfNOthcDuRJujXLqiuS6o3Tpy0JEoh7g=="
"version": "0.9.0",
"resolved": "https://registry.npmjs.org/mpath/-/mpath-0.9.0.tgz",
"integrity": "sha512-ikJRQTk8hw5DEoFVxHG1Gn9T/xcjtdnOKIU1JTmGjZZlg9LST2mBLmcX3/ICIbgJydT2GOc15RnNy5mHmzfSew=="
},
"mquery": {
"version": "4.0.0",
"resolved": "https://registry.npmjs.org/mquery/-/mquery-4.0.0.tgz",
"integrity": "sha512-nGjm89lHja+T/b8cybAby6H0YgA4qYC/lx6UlwvHGqvTq8bDaNeCwl1sY8uRELrNbVWJzIihxVd+vphGGn1vBw==",
"version": "4.0.3",
"resolved": "https://registry.npmjs.org/mquery/-/mquery-4.0.3.tgz",
"integrity": "sha512-J5heI+P08I6VJ2Ky3+33IpCdAvlYGTSUjwTPxkAr8i8EoduPMBX2OY/wa3IKZIQl7MU4SbFk8ndgSKyB/cl1zA==",
"requires": {
"debug": "4.x",
"regexp-clone": "^1.0.0",
"sliced": "1.0.1"
"debug": "4.x"
},
"dependencies": {
"debug": {
"version": "4.3.2",
"resolved": "https://registry.npmjs.org/debug/-/debug-4.3.2.tgz",
"integrity": "sha512-mOp8wKcvj7XxC78zLgw/ZA+6TSgkoE2C/ienthhRD298T7UNwAg9diBpLRxC0mOezLl4B0xV7M0cCO6P/O0Xhw==",
"version": "4.3.4",
"resolved": "https://registry.npmjs.org/debug/-/debug-4.3.4.tgz",
"integrity": "sha512-PRWFHuSU3eDtQJPvnNY7Jcket1j0t5OuOsFzPPzsekD52Zl8qUfFIPEiswXqIvHWGVHOgX+7G/vCNNhehwxfkQ==",
"requires": {
"ms": "2.1.2"
}
@@ -15680,16 +15590,15 @@
"integrity": "sha1-VgiurfwAvmwpAd9fmGF4jeDVl8g="
},
"multer": {
"version": "1.4.4",
"resolved": "https://registry.npmjs.org/multer/-/multer-1.4.4.tgz",
"integrity": "sha512-2wY2+xD4udX612aMqMcB8Ws2Voq6NIUPEtD1be6m411T4uDH/VtL9i//xvcyFlTVfRdaBsk7hV5tgrGQqhuBiw==",
"version": "1.4.5-lts.1",
"resolved": "https://registry.npmjs.org/multer/-/multer-1.4.5-lts.1.tgz",
"integrity": "sha512-ywPWvcDMeH+z9gQq5qYHCCy+ethsk4goepZ45GLD63fOu0YcNecQxi64nDs3qluZB+murG3/D4dJ7+dGctcCQQ==",
"requires": {
"append-field": "^1.0.0",
"busboy": "^0.2.11",
"busboy": "^1.0.0",
"concat-stream": "^1.5.2",
"mkdirp": "^0.5.4",
"object-assign": "^4.1.1",
"on-finished": "^2.3.0",
"type-is": "^1.6.4",
"xtend": "^4.0.0"
},
@@ -16416,11 +16325,6 @@
"picomatch": "^2.2.1"
}
},
"regexp-clone": {
"version": "1.0.0",
"resolved": "https://registry.npmjs.org/regexp-clone/-/regexp-clone-1.0.0.tgz",
"integrity": "sha512-TuAasHQNamyyJ2hb97IuBEif4qBHGjPHBS64sZwytpLEqtBQ1gPJTnOaQ6qmpET16cK14kkjbazl6+p0RRv0yw=="
},
"require-directory": {
"version": "2.1.1",
"resolved": "https://registry.npmjs.org/require-directory/-/require-directory-2.1.1.tgz",
@@ -16605,9 +16509,9 @@
}
},
"sift": {
"version": "13.5.2",
"resolved": "https://registry.npmjs.org/sift/-/sift-13.5.2.tgz",
"integrity": "sha512-+gxdEOMA2J+AI+fVsCqeNn7Tgx3M9ZN9jdi95939l1IJ8cZsqS8sqpJyOkic2SJk+1+98Uwryt/gL6XDaV+UZA=="
"version": "16.0.0",
"resolved": "https://registry.npmjs.org/sift/-/sift-16.0.0.tgz",
"integrity": "sha512-ILTjdP2Mv9V1kIxWMXeMTIRbOBrqKc4JAXmFMnFq3fKeyQ2Qwa3Dw1ubcye3vR+Y6ofA0b9gNDr/y2t6eUeIzQ=="
},
"signal-exit": {
"version": "3.0.3",
@@ -16677,10 +16581,19 @@
"integrity": "sha512-g9Q1haeby36OSStwb4ntCGGGaKsaVSjQ68fBxoQcutl5fS1vuY18H3wSt3jFyFtrkx+Kz0V1G85A4MyAdDMi2Q==",
"dev": true
},
"sliced": {
"version": "1.0.1",
"resolved": "https://registry.npmjs.org/sliced/-/sliced-1.0.1.tgz",
"integrity": "sha1-CzpmK10Ewxd7GSa+qCsD+Dei70E="
"smart-buffer": {
"version": "4.2.0",
"resolved": "https://registry.npmjs.org/smart-buffer/-/smart-buffer-4.2.0.tgz",
"integrity": "sha512-94hK0Hh8rPqQl2xXc3HsaBoOXKV20MToPkcXvwbISWLEs+64sBq5kFgn2kJDHb1Pry9yrP0dxrCI9RRci7RXKg=="
},
"socks": {
"version": "2.7.0",
"resolved": "https://registry.npmjs.org/socks/-/socks-2.7.0.tgz",
"integrity": "sha512-scnOe9y4VuiNUULJN72GrM26BNOjVsfPXI+j+98PkyEfsIXroa5ofyjT+FzGvn/xHs73U2JtoBYAVx9Hl4quSA==",
"requires": {
"ip": "^2.0.0",
"smart-buffer": "^4.2.0"
}
},
"source-map": {
"version": "0.6.1",
@@ -16771,9 +16684,9 @@
}
},
"streamsearch": {
"version": "0.1.2",
"resolved": "https://registry.npmjs.org/streamsearch/-/streamsearch-0.1.2.tgz",
"integrity": "sha1-gIudDlb8Jz2Am6VzOOkpkZoanxo="
"version": "1.1.0",
"resolved": "https://registry.npmjs.org/streamsearch/-/streamsearch-1.1.0.tgz",
"integrity": "sha512-Mcc5wHehp9aXz1ax6bZUyY5afg9u2rv5cqQI3mRrYkGC8rW2hM02jWuwjtL++LS5qinSyhj2QfLyNsuc+VsExg=="
},
"string_decoder": {
"version": "1.3.0",
@@ -17140,6 +17053,7 @@
"version": "2.1.0",
"resolved": "https://registry.npmjs.org/tr46/-/tr46-2.1.0.tgz",
"integrity": "sha512-15Ih7phfcdP5YxqiB+iDtLoaTz4Nd35+IiAv0kQ5FNKHzXgdWqPoTIqEDDJmXceQt4JZk6lVPT8lnDlPpGDppw==",
"dev": true,
"requires": {
"punycode": "^2.1.1"
}
@@ -17456,7 +17370,8 @@
"webidl-conversions": {
"version": "6.1.0",
"resolved": "https://registry.npmjs.org/webidl-conversions/-/webidl-conversions-6.1.0.tgz",
"integrity": "sha512-qBIvFLGiBpLjfwmYAaHPXsn+ho5xZnGvyGvsarywGNc8VyQJUMHJ8OBKGGrPER0okBeMDaan4mNBlgBROxuI8w=="
"integrity": "sha512-qBIvFLGiBpLjfwmYAaHPXsn+ho5xZnGvyGvsarywGNc8VyQJUMHJ8OBKGGrPER0okBeMDaan4mNBlgBROxuI8w==",
"dev": true
},
"whatwg-encoding": {
"version": "1.0.5",

View File

@@ -62,7 +62,7 @@
"mongoose": "^6.0.12",
"mongoose-sequence": "^5.3.1",
"morgan": "^1.10.0",
"multer": "^1.4.3",
"multer": "^1.4.5-lts.1",
"rotating-file-stream": "^3.0.4",
"swagger-ui-express": "4.3.0",
"unzipper": "^0.10.11",

View File

@@ -1,7 +1,6 @@
import express from 'express'
import { Request, Security, Route, Tags, Post, Body } from 'tsoa'
import { ExecuteReturnJson, ExecutionController } from './internal'
import { ExecuteReturnJsonResponse } from '.'
import { ExecutionController } from './internal'
import {
getPreProgramVariables,
getUserAutoExec,
@@ -35,7 +34,7 @@ export class CodeController {
public async executeCode(
@Request() request: express.Request,
@Body() body: ExecuteCodePayload
): Promise<ExecuteReturnJsonResponse> {
): Promise<string | Buffer> {
return executeCode(request, body)
}
}
@@ -51,22 +50,15 @@ const executeCode = async (
: await getUserAutoExec()
try {
const { webout, log, httpHeaders } =
(await new ExecutionController().executeProgram({
program: code,
preProgramVariables: getPreProgramVariables(req),
vars: { ...req.query, _debug: 131 },
otherArgs: { userAutoExec },
returnJson: true,
runTime: runTime
})) as ExecuteReturnJson
const { result } = await new ExecutionController().executeProgram({
program: code,
preProgramVariables: getPreProgramVariables(req),
vars: { ...req.query, _debug: 131 },
otherArgs: { userAutoExec },
runTime: runTime
})
return {
status: 'success',
_webout: webout as string,
log: parseLogToArray(log),
httpHeaders
}
return result
} catch (err: any) {
throw {
code: 400,

View File

@@ -20,12 +20,6 @@ export interface ExecuteReturnRaw {
result: string | Buffer
}
export interface ExecuteReturnJson {
httpHeaders: HTTPHeaders
webout: string | Buffer
log?: string
}
interface ExecuteFileParams {
programPath: string
preProgramVariables: PreProgramVars
@@ -68,10 +62,9 @@ export class ExecutionController {
preProgramVariables,
vars,
otherArgs,
returnJson,
session: sessionByFileUpload,
runTime
}: ExecuteProgramParams): Promise<ExecuteReturnRaw | ExecuteReturnJson> {
}: ExecuteProgramParams): Promise<ExecuteReturnRaw> {
const sessionController = getSessionController(runTime)
const session =
@@ -96,6 +89,7 @@ export class ExecutionController {
vars,
session,
weboutPath,
headersPath,
tokenFile,
runTime,
logPath,
@@ -107,10 +101,7 @@ export class ExecutionController {
? await readFile(headersPath)
: ''
const httpHeaders: HTTPHeaders = extractHeaders(headersContent)
const fileResponse: boolean =
httpHeaders.hasOwnProperty('content-type') &&
!returnJson && // not a POST Request
!isDebugOn(vars) // Debug is not enabled
const fileResponse: boolean = httpHeaders.hasOwnProperty('content-type')
const webout = (await fileExists(weboutPath))
? fileResponse
@@ -121,19 +112,11 @@ export class ExecutionController {
// it should be deleted by scheduleSessionDestroy
session.inUse = false
if (returnJson) {
return {
httpHeaders,
webout,
log: isDebugOn(vars) || session.crashed ? log : undefined
}
}
return {
httpHeaders,
result:
isDebugOn(vars) || session.crashed
? `<html><body>${webout}<div style="text-align:left"><hr /><h2>SAS Log</h2><pre>${log}</pre></div></body></html>`
? `${webout}\n${process.logsUUID}\n${log}`
: webout
}
}

View File

@@ -190,7 +190,39 @@ export class JSSessionController extends SessionController {
}
const headersPath = path.join(session.path, 'stpsrv_header.txt')
await createFile(headersPath, 'Content-type: application/json')
await createFile(headersPath, 'Content-type: text/plain')
this.sessions.push(session)
return session
}
}
export class PythonSessionController extends SessionController {
protected async createSession(): Promise<Session> {
const sessionId = generateUniqueFileName(generateTimestamp())
const sessionFolder = path.join(getSessionsFolder(), sessionId)
const creationTimeStamp = sessionId.split('-').pop() as string
// death time of session is 15 mins from creation
const deathTimeStamp = (
parseInt(creationTimeStamp) +
15 * 60 * 1000 -
1000
).toString()
const session: Session = {
id: sessionId,
ready: true,
inUse: true,
consumed: false,
completed: false,
creationTimeStamp,
deathTimeStamp,
path: sessionFolder
}
const headersPath = path.join(session.path, 'stpsrv_header.txt')
await createFile(headersPath, 'Content-type: text/plain')
this.sessions.push(session)
return session
@@ -199,7 +231,7 @@ export class JSSessionController extends SessionController {
export const getSessionController = (
runTime: RunTimeType
): SASSessionController | JSSessionController => {
): SASSessionController | JSSessionController | PythonSessionController => {
if (runTime === RunTimeType.SAS) {
return getSASSessionController()
}
@@ -208,6 +240,10 @@ export const getSessionController = (
return getJSSessionController()
}
if (runTime === RunTimeType.PY) {
return getPythonSessionController()
}
throw new Error('No Runtime is configured')
}
@@ -227,6 +263,14 @@ const getJSSessionController = (): JSSessionController => {
return process.jsSessionController
}
const getPythonSessionController = (): PythonSessionController => {
if (process.pythonSessionController) return process.pythonSessionController
process.pythonSessionController = new PythonSessionController()
return process.pythonSessionController
}
const autoExecContent = `
data _null_;
/* remove the dummy SYSIN */

View File

@@ -9,6 +9,7 @@ export const createJSProgram = async (
vars: ExecutionVars,
session: Session,
weboutPath: string,
headersPath: string,
tokenFile: string,
otherArgs?: any
) => {
@@ -23,15 +24,16 @@ let _webout = '';
const weboutPath = '${
isWindows() ? weboutPath.replace(/\\/g, '\\\\') : weboutPath
}';
const _sasjs_tokenfile = '${
const _SASJS_TOKENFILE = '${
isWindows() ? tokenFile.replace(/\\/g, '\\\\') : tokenFile
}';
const _sasjs_username = '${preProgramVariables?.username}';
const _sasjs_userid = '${preProgramVariables?.userId}';
const _sasjs_displayname = '${preProgramVariables?.displayName}';
const _metaperson = _sasjs_displayname;
const _metauser = _sasjs_username;
const sasjsprocessmode = 'Stored Program';
const _SASJS_WEBOUT_HEADERS = '${headersPath}';
const _SASJS_USERNAME = '${preProgramVariables?.username}';
const _SASJS_USERID = '${preProgramVariables?.userId}';
const _SASJS_DISPLAYNAME = '${preProgramVariables?.displayName}';
const _METAPERSON = _SASJS_DISPLAYNAME;
const _METAUSER = _SASJS_USERNAME;
const SASJSPROCESSMODE = 'Stored Program';
`
const requiredModules = `const fs = require('fs')`
@@ -55,14 +57,15 @@ if (_webout) {
`
// if no files are uploaded filesNamesMap will be undefined
if (otherArgs?.filesNamesMap) {
const uploadJSCode = await generateFileUploadJSCode(
const uploadJsCode = await generateFileUploadJSCode(
otherArgs.filesNamesMap,
session.path
)
//If js code for the file is generated it will be appended to the top of jsCode
if (uploadJSCode.length > 0) {
program = `${uploadJSCode}\n` + program
// If any files are uploaded, the program needs to be updated with some
// dynamically generated variables (pointers) for ease of ingestion
if (uploadJsCode.length > 0) {
program = `${uploadJsCode}\n` + program
}
}
return requiredModules + program

View File

@@ -0,0 +1,68 @@
import { isWindows } from '@sasjs/utils'
import { PreProgramVars, Session } from '../../types'
import { generateFileUploadPythonCode } from '../../utils'
import { ExecutionVars } from './'
export const createPythonProgram = async (
program: string,
preProgramVariables: PreProgramVars,
vars: ExecutionVars,
session: Session,
weboutPath: string,
headersPath: string,
tokenFile: string,
otherArgs?: any
) => {
const varStatments = Object.keys(vars).reduce(
(computed: string, key: string) => `${computed}${key} = '${vars[key]}';\n`,
''
)
const preProgramVarStatments = `
_SASJS_SESSION_PATH = '${
isWindows() ? session.path.replace(/\\/g, '\\\\') : session.path
}';
_WEBOUT = '${isWindows() ? weboutPath.replace(/\\/g, '\\\\') : weboutPath}';
_SASJS_WEBOUT_HEADERS = '${headersPath}';
_SASJS_TOKENFILE = '${
isWindows() ? tokenFile.replace(/\\/g, '\\\\') : tokenFile
}';
_SASJS_USERNAME = '${preProgramVariables?.username}';
_SASJS_USERID = '${preProgramVariables?.userId}';
_SASJS_DISPLAYNAME = '${preProgramVariables?.displayName}';
_METAPERSON = _SASJS_DISPLAYNAME;
_METAUSER = _SASJS_USERNAME;
SASJSPROCESSMODE = 'Stored Program';
`
const requiredModules = `import os`
program = `
# runtime vars
${varStatments}
# dynamic user-provided vars
${preProgramVarStatments}
# change working directory to session folder
os.chdir(_SASJS_SESSION_PATH)
# actual job code
${program}
`
// if no files are uploaded filesNamesMap will be undefined
if (otherArgs?.filesNamesMap) {
const uploadPythonCode = await generateFileUploadPythonCode(
otherArgs.filesNamesMap,
session.path
)
// If any files are uploaded, the program needs to be updated with some
// dynamically generated variables (pointers) for ease of ingestion
if (uploadPythonCode.length > 0) {
program = `${uploadPythonCode}\n` + program
}
}
return requiredModules + program
}

View File

@@ -23,10 +23,14 @@ export const createSASProgram = async (
%let _sasjs_displayname=${preProgramVariables?.displayName};
%let _sasjs_apiserverurl=${preProgramVariables?.serverUrl};
%let _sasjs_apipath=/SASjsApi/stp/execute;
%let _sasjs_webout_headers=%sysfunc(pathname(work))/../stpsrv_header.txt;
%let _metaperson=&_sasjs_displayname;
%let _metauser=&_sasjs_username;
/* the below is here for compatibility and will be removed in a future release */
%let sasjs_stpsrv_header_loc=&_sasjs_webout_headers;
%let sasjsprocessmode=Stored Program;
%let sasjs_stpsrv_header_loc=%sysfunc(pathname(work))/../stpsrv_header.txt;
%global SYSPROCESSMODE SYSTCPIPHOSTNAME SYSHOSTINFOLONG;
%macro _sasjs_server_init();
@@ -34,6 +38,9 @@ export const createSASProgram = async (
%if "&SYSTCPIPHOSTNAME"="" %then %let SYSTCPIPHOSTNAME=&_sasjs_apiserverurl;
%mend;
%_sasjs_server_init()
proc printto print="%sysfunc(getoption(log))";
run;
`
program = `
@@ -60,7 +67,8 @@ ${program}`
session.path
)
//If sas code for the file is generated it will be appended to the top of sasCode
// If any files are uploaded, the program needs to be updated with some
// dynamically generated variables (pointers) for ease of ingestion
if (uploadSasCode.length > 0) {
program = `${uploadSasCode}` + program
}

View File

@@ -4,4 +4,5 @@ export * from './Execution'
export * from './FileUploadController'
export * from './createSASProgram'
export * from './createJSProgram'
export * from './createPythonProgram'
export * from './processProgram'

View File

@@ -5,7 +5,12 @@ import { once } from 'stream'
import { createFile, moveFile } from '@sasjs/utils'
import { PreProgramVars, Session } from '../../types'
import { RunTimeType } from '../../utils'
import { ExecutionVars, createSASProgram, createJSProgram } from './'
import {
ExecutionVars,
createSASProgram,
createJSProgram,
createPythonProgram
} from './'
export const processProgram = async (
program: string,
@@ -13,6 +18,7 @@ export const processProgram = async (
vars: ExecutionVars,
session: Session,
weboutPath: string,
headersPath: string,
tokenFile: string,
runTime: RunTimeType,
logPath: string,
@@ -25,6 +31,7 @@ export const processProgram = async (
vars,
session,
weboutPath,
headersPath,
tokenFile,
otherArgs
)
@@ -47,6 +54,43 @@ export const processProgram = async (
// copy the code.js program to log and end write stream
writeStream.end(program)
session.completed = true
console.log('session completed', session)
} catch (err: any) {
session.completed = true
session.crashed = err.toString()
console.log('session crashed', session.id, session.crashed)
}
} else if (runTime === RunTimeType.PY) {
program = await createPythonProgram(
program,
preProgramVariables,
vars,
session,
weboutPath,
headersPath,
tokenFile,
otherArgs
)
const codePath = path.join(session.path, 'code.py')
try {
await createFile(codePath, program)
// create a stream that will write to console outputs to log file
const writeStream = fs.createWriteStream(logPath)
// waiting for the open event so that we can have underlying file descriptor
await once(writeStream, 'open')
execFileSync(process.pythonLoc!, [codePath], {
stdio: ['ignore', writeStream, writeStream]
})
// copy the code.py program to log and end write stream
writeStream.end(program)
session.completed = true
console.log('session completed', session)
} catch (err: any) {

View File

@@ -1,33 +1,16 @@
import express from 'express'
import {
Request,
Security,
Route,
Tags,
Post,
Body,
Get,
Query,
Example
} from 'tsoa'
import {
ExecuteReturnJson,
ExecuteReturnRaw,
ExecutionController,
ExecutionVars
} from './internal'
import { Request, Security, Route, Tags, Post, Body, Get, Query } from 'tsoa'
import { ExecutionController, ExecutionVars } from './internal'
import {
getPreProgramVariables,
HTTPHeaders,
isDebugOn,
LogLine,
makeFilesNamesMap,
parseLogToArray,
getRunTimeAndFilePath
} from '../utils'
import { MulterFile } from '../types/Upload'
interface ExecuteReturnJsonPayload {
interface ExecutePostRequestPayload {
/**
* Location of SAS program
* @example "/Public/somefolder/some.file"
@@ -35,17 +18,6 @@ interface ExecuteReturnJsonPayload {
_program?: string
}
interface IRecordOfAny {
[key: string]: any
}
export interface ExecuteReturnJsonResponse {
status: string
_webout: string | IRecordOfAny
log: LogLine[]
message?: string
httpHeaders: HTTPHeaders
}
@Security('bearerAuth')
@Route('SASjsApi/stp')
@Tags('STP')
@@ -62,11 +34,12 @@ export class STPController {
* @example _program "/Projects/myApp/some/program"
*/
@Get('/execute')
public async executeReturnRaw(
public async executeGetRequest(
@Request() request: express.Request,
@Query() _program: string
): Promise<string | Buffer> {
return executeReturnRaw(request, _program)
const vars = request.query as ExecutionVars
return execute(request, _program, vars)
}
/**
@@ -87,50 +60,42 @@ export class STPController {
* @param _program Location of SAS or JS code
* @example _program "/Projects/myApp/some/program"
*/
@Example<ExecuteReturnJsonResponse>({
status: 'success',
_webout: 'webout content',
log: [],
httpHeaders: {
'Content-type': 'application/zip',
'Cache-Control': 'public, max-age=1000'
}
})
@Post('/execute')
public async executeReturnJson(
public async executePostRequest(
@Request() request: express.Request,
@Body() body?: ExecuteReturnJsonPayload,
@Body() body?: ExecutePostRequestPayload,
@Query() _program?: string
): Promise<ExecuteReturnJsonResponse> {
): Promise<string | Buffer> {
const program = _program ?? body?._program
return executeReturnJson(request, program!)
const vars = { ...request.query, ...request.body }
const filesNamesMap = request.files?.length
? makeFilesNamesMap(request.files as MulterFile[])
: null
const otherArgs = { filesNamesMap: filesNamesMap }
return execute(request, program!, vars, otherArgs)
}
}
const executeReturnRaw = async (
const execute = async (
req: express.Request,
_program: string
_program: string,
vars: ExecutionVars,
otherArgs?: any
): Promise<string | Buffer> => {
const query = req.query as ExecutionVars
try {
const { codePath, runTime } = await getRunTimeAndFilePath(_program)
const { result, httpHeaders } =
(await new ExecutionController().executeFile({
const { result, httpHeaders } = await new ExecutionController().executeFile(
{
programPath: codePath,
runTime,
preProgramVariables: getPreProgramVariables(req),
vars: query,
runTime
})) as ExecuteReturnRaw
// Should over-ride response header for debug
// on GET request to see entire log rendering on browser.
if (isDebugOn(query)) {
httpHeaders['content-type'] = 'text/plain'
}
req.res?.set(httpHeaders)
vars,
otherArgs,
session: req.sasjsSession
}
)
if (result instanceof Buffer) {
;(req as any).sasHeaders = httpHeaders
@@ -146,48 +111,3 @@ const executeReturnRaw = async (
}
}
}
const executeReturnJson = async (
req: express.Request,
_program: string
): Promise<ExecuteReturnJsonResponse> => {
const filesNamesMap = req.files?.length
? makeFilesNamesMap(req.files as MulterFile[])
: null
try {
const { codePath, runTime } = await getRunTimeAndFilePath(_program)
const { webout, log, httpHeaders } =
(await new ExecutionController().executeFile({
programPath: codePath,
preProgramVariables: getPreProgramVariables(req),
vars: { ...req.query, ...req.body },
otherArgs: { filesNamesMap: filesNamesMap },
returnJson: true,
session: req.sasjsSession,
runTime
})) as ExecuteReturnJson
let weboutRes: string | IRecordOfAny = webout
if (httpHeaders['content-type']?.toLowerCase() === 'application/json') {
try {
weboutRes = JSON.parse(webout as string)
} catch (_) {}
}
return {
status: 'success',
_webout: weboutRes,
log: parseLogToArray(log),
httpHeaders
}
} catch (err: any) {
throw {
code: 400,
status: 'failure',
message: 'Job execution failed.',
error: typeof err === 'object' ? err.toString() : err
}
}
}

View File

@@ -22,7 +22,8 @@ import {
import { createFile, generateTimestamp, deleteFolder } from '@sasjs/utils'
import {
SASSessionController,
JSSessionController
JSSessionController,
PythonSessionController
} from '../../../controllers/internal'
import * as ProcessProgramModule from '../../../controllers/internal/processProgram'
import { Session } from '../../../types'
@@ -39,14 +40,17 @@ const user = {
const sampleSasProgram = '%put hello world!;'
const sampleJsProgram = `console.log('hello world!/')`
const samplePyProgram = `print('hello world!/')`
const filesFolder = getFilesFolder()
const testFilesFolder = `test-stp-${generateTimestamp()}`
let app: Express
let accessToken: string
describe('stp', () => {
let app: Express
let con: Mongoose
let mongoServer: MongoMemoryServer
let accessToken: string
const userController = new UserController()
const permissionController = new PermissionController()
@@ -72,8 +76,6 @@ describe('stp', () => {
})
describe('execute', () => {
const testFilesFolder = `test-stp-${generateTimestamp()}`
describe('get', () => {
describe('with runtime js', () => {
const testFilesFolder = `test-stp-${generateTimestamp()}`
@@ -93,41 +95,45 @@ describe('stp', () => {
})
it('should execute js program when both js and sas program are present', async () => {
const programPath = path.join(testFilesFolder, 'program')
const sasProgramPath = path.join(filesFolder, `${programPath}.sas`)
const jsProgramPath = path.join(filesFolder, `${programPath}.js`)
await createFile(sasProgramPath, sampleSasProgram)
await createFile(jsProgramPath, sampleJsProgram)
await request(app)
.get(`/SASjsApi/stp/execute?_program=${programPath}`)
.auth(accessToken, { type: 'bearer' })
.send()
.expect(200)
expect(ProcessProgramModule.processProgram).toHaveBeenCalledWith(
expect.anything(),
expect.anything(),
expect.anything(),
expect.anything(),
expect.anything(),
expect.anything(),
RunTimeType.JS,
expect.anything(),
undefined
await makeRequestAndAssert(
[RunTimeType.JS, RunTimeType.SAS],
200,
RunTimeType.JS
)
})
it('should throw error when js program is not present but sas program exists', async () => {
const programPath = path.join(testFilesFolder, 'program')
const sasProgramPath = path.join(filesFolder, `${programPath}.sas`)
await createFile(sasProgramPath, sampleSasProgram)
await makeRequestAndAssert([], 400)
})
})
await request(app)
.get(`/SASjsApi/stp/execute?_program=${programPath}`)
.auth(accessToken, { type: 'bearer' })
.send()
.expect(400)
describe('with runtime py', () => {
const testFilesFolder = `test-stp-${generateTimestamp()}`
beforeAll(() => {
process.runTimes = [RunTimeType.PY]
})
beforeEach(() => {
jest.resetModules() // it clears the cache
setupMocks()
})
afterEach(async () => {
jest.resetAllMocks()
await deleteFolder(path.join(filesFolder, testFilesFolder))
})
it('should execute python program when python, js and sas programs are present', async () => {
await makeRequestAndAssert(
[RunTimeType.PY, RunTimeType.SAS, RunTimeType.JS],
200,
RunTimeType.PY
)
})
it('should throw error when py program is not present but js or sas program exists', async () => {
await makeRequestAndAssert([], 400)
})
})
@@ -147,41 +153,11 @@ describe('stp', () => {
})
it('should execute sas program when both sas and js programs are present', async () => {
const programPath = path.join(testFilesFolder, 'program')
const sasProgramPath = path.join(filesFolder, `${programPath}.sas`)
const jsProgramPath = path.join(filesFolder, `${programPath}.js`)
await createFile(sasProgramPath, sampleSasProgram)
await createFile(jsProgramPath, sampleJsProgram)
await request(app)
.get(`/SASjsApi/stp/execute?_program=${programPath}`)
.auth(accessToken, { type: 'bearer' })
.send()
.expect(200)
expect(ProcessProgramModule.processProgram).toHaveBeenCalledWith(
expect.anything(),
expect.anything(),
expect.anything(),
expect.anything(),
expect.anything(),
expect.anything(),
RunTimeType.SAS,
expect.anything(),
undefined
)
await makeRequestAndAssert([RunTimeType.SAS], 200, RunTimeType.SAS)
})
it('should throw error when sas program do not exit but js exists', async () => {
const programPath = path.join(testFilesFolder, 'program')
const jsProgramPath = path.join(filesFolder, `${programPath}.js`)
await createFile(jsProgramPath, sampleJsProgram)
await request(app)
.get(`/SASjsApi/stp/execute?_program=${programPath}`)
.auth(accessToken, { type: 'bearer' })
.send()
.expect(400)
await makeRequestAndAssert([], 400)
})
})
@@ -201,63 +177,51 @@ describe('stp', () => {
})
it('should execute js program when both js and sas program are present', async () => {
const programPath = path.join(testFilesFolder, 'program')
const sasProgramPath = path.join(filesFolder, `${programPath}.sas`)
const jsProgramPath = path.join(filesFolder, `${programPath}.js`)
await createFile(sasProgramPath, sampleSasProgram)
await createFile(jsProgramPath, sampleJsProgram)
await request(app)
.get(`/SASjsApi/stp/execute?_program=${programPath}`)
.auth(accessToken, { type: 'bearer' })
.send()
.expect(200)
expect(ProcessProgramModule.processProgram).toHaveBeenCalledWith(
expect.anything(),
expect.anything(),
expect.anything(),
expect.anything(),
expect.anything(),
expect.anything(),
RunTimeType.JS,
expect.anything(),
undefined
await makeRequestAndAssert(
[RunTimeType.SAS, RunTimeType.JS],
200,
RunTimeType.JS
)
})
it('should execute sas program when js program is not present but sas program exists', async () => {
const programPath = path.join(testFilesFolder, 'program')
const sasProgramPath = path.join(filesFolder, `${programPath}.sas`)
await createFile(sasProgramPath, sampleSasProgram)
await request(app)
.get(`/SASjsApi/stp/execute?_program=${programPath}`)
.auth(accessToken, { type: 'bearer' })
.send()
.expect(200)
expect(ProcessProgramModule.processProgram).toHaveBeenCalledWith(
expect.anything(),
expect.anything(),
expect.anything(),
expect.anything(),
expect.anything(),
expect.anything(),
RunTimeType.SAS,
expect.anything(),
undefined
)
await makeRequestAndAssert([RunTimeType.SAS], 200, RunTimeType.SAS)
})
it('should throw error when both sas and js programs do not exist', async () => {
const programPath = path.join(testFilesFolder, 'program')
await makeRequestAndAssert([], 400)
})
})
await request(app)
.get(`/SASjsApi/stp/execute?_program=${programPath}`)
.auth(accessToken, { type: 'bearer' })
.send()
.expect(400)
describe('with runtime py and sas', () => {
beforeAll(() => {
process.runTimes = [RunTimeType.PY, RunTimeType.SAS]
})
beforeEach(() => {
jest.resetModules() // it clears the cache
setupMocks()
})
afterEach(async () => {
jest.resetAllMocks()
await deleteFolder(path.join(filesFolder, testFilesFolder))
})
it('should execute python program when both python and sas program are present', async () => {
await makeRequestAndAssert(
[RunTimeType.PY, RunTimeType.SAS],
200,
RunTimeType.PY
)
})
it('should execute sas program when python program is not present but sas program exists', async () => {
await makeRequestAndAssert([RunTimeType.SAS], 200, RunTimeType.SAS)
})
it('should throw error when both sas and js programs do not exist', async () => {
await makeRequestAndAssert([], 400)
})
})
@@ -277,76 +241,220 @@ describe('stp', () => {
})
it('should execute sas program when both sas and js programs exist', async () => {
const programPath = path.join(testFilesFolder, 'program')
const sasProgramPath = path.join(filesFolder, `${programPath}.sas`)
const jsProgramPath = path.join(filesFolder, `${programPath}.js`)
await createFile(sasProgramPath, sampleSasProgram)
await createFile(jsProgramPath, sampleJsProgram)
await request(app)
.get(`/SASjsApi/stp/execute?_program=${programPath}`)
.auth(accessToken, { type: 'bearer' })
.send()
.expect(200)
expect(ProcessProgramModule.processProgram).toHaveBeenCalledWith(
expect.anything(),
expect.anything(),
expect.anything(),
expect.anything(),
expect.anything(),
expect.anything(),
RunTimeType.SAS,
expect.anything(),
undefined
await makeRequestAndAssert(
[RunTimeType.SAS, RunTimeType.JS],
200,
RunTimeType.SAS
)
})
it('should execute js program when sas program is not present but js program exists', async () => {
const programPath = path.join(testFilesFolder, 'program')
const jsProgramPath = path.join(filesFolder, `${programPath}.js`)
await createFile(jsProgramPath, sampleJsProgram)
await request(app)
.get(`/SASjsApi/stp/execute?_program=${programPath}`)
.auth(accessToken, { type: 'bearer' })
.send()
.expect(200)
expect(ProcessProgramModule.processProgram).toHaveBeenCalledWith(
expect.anything(),
expect.anything(),
expect.anything(),
expect.anything(),
expect.anything(),
expect.anything(),
RunTimeType.JS,
expect.anything(),
undefined
)
await makeRequestAndAssert([RunTimeType.JS], 200, RunTimeType.JS)
})
it('should throw error when both sas and js programs do not exist', async () => {
const programPath = path.join(testFilesFolder, 'program')
await makeRequestAndAssert([], 400)
})
})
await request(app)
.get(`/SASjsApi/stp/execute?_program=${programPath}`)
.auth(accessToken, { type: 'bearer' })
.send()
.expect(400)
describe('with runtime sas and py', () => {
beforeAll(() => {
process.runTimes = [RunTimeType.SAS, RunTimeType.PY]
})
beforeEach(() => {
jest.resetModules() // it clears the cache
setupMocks()
})
afterEach(async () => {
jest.resetAllMocks()
await deleteFolder(path.join(filesFolder, testFilesFolder))
})
it('should execute sas program when both sas and python programs exist', async () => {
await makeRequestAndAssert(
[RunTimeType.SAS, RunTimeType.PY],
200,
RunTimeType.SAS
)
})
it('should execute python program when sas program is not present but python program exists', async () => {
await makeRequestAndAssert([RunTimeType.PY], 200, RunTimeType.PY)
})
it('should throw error when both sas and python programs do not exist', async () => {
await makeRequestAndAssert([], 400)
})
})
describe('with runtime sas, js and py', () => {
beforeAll(() => {
process.runTimes = [RunTimeType.SAS, RunTimeType.JS, RunTimeType.PY]
})
beforeEach(() => {
jest.resetModules() // it clears the cache
setupMocks()
})
afterEach(async () => {
jest.resetAllMocks()
await deleteFolder(path.join(filesFolder, testFilesFolder))
})
it('should execute sas program when it exists, no matter js and python programs exist or not', async () => {
await makeRequestAndAssert(
[RunTimeType.SAS, RunTimeType.PY, RunTimeType.JS],
200,
RunTimeType.SAS
)
})
it('should execute js program when sas program is absent but js and python programs are present', async () => {
await makeRequestAndAssert(
[RunTimeType.JS, RunTimeType.PY],
200,
RunTimeType.JS
)
})
it('should execute python program when both sas and js programs are not present', async () => {
await makeRequestAndAssert([RunTimeType.PY], 200, RunTimeType.PY)
})
it('should throw error when no program exists', async () => {
await makeRequestAndAssert([], 400)
})
})
describe('with runtime js, sas and py', () => {
beforeAll(() => {
process.runTimes = [RunTimeType.JS, RunTimeType.SAS, RunTimeType.PY]
})
beforeEach(() => {
jest.resetModules() // it clears the cache
setupMocks()
})
afterEach(async () => {
jest.resetAllMocks()
await deleteFolder(path.join(filesFolder, testFilesFolder))
})
it('should execute js program when it exists, no matter sas and python programs exist or not', async () => {
await makeRequestAndAssert(
[RunTimeType.JS, RunTimeType.SAS, RunTimeType.PY],
200,
RunTimeType.JS
)
})
it('should execute sas program when js program is absent but sas and python programs are present', async () => {
await makeRequestAndAssert(
[RunTimeType.SAS, RunTimeType.PY],
200,
RunTimeType.SAS
)
})
it('should execute python program when both sas and js programs are not present', async () => {
await makeRequestAndAssert([RunTimeType.PY], 200, RunTimeType.PY)
})
it('should throw error when no program exists', async () => {
await makeRequestAndAssert([], 400)
})
})
describe('with runtime py, sas and js', () => {
beforeAll(() => {
process.runTimes = [RunTimeType.PY, RunTimeType.SAS, RunTimeType.JS]
})
beforeEach(() => {
jest.resetModules() // it clears the cache
setupMocks()
})
afterEach(async () => {
jest.resetAllMocks()
await deleteFolder(path.join(filesFolder, testFilesFolder))
})
it('should execute python program when it exists, no matter sas and js programs exist or not', async () => {
await makeRequestAndAssert(
[RunTimeType.PY, RunTimeType.SAS, RunTimeType.JS],
200,
RunTimeType.PY
)
})
it('should execute sas program when python program is absent but sas and js programs are present', async () => {
await makeRequestAndAssert(
[RunTimeType.SAS, RunTimeType.JS],
200,
RunTimeType.SAS
)
})
it('should execute js program when both sas and python programs are not present', async () => {
await makeRequestAndAssert([RunTimeType.JS], 200, RunTimeType.JS)
})
it('should throw error when no program exists', async () => {
await makeRequestAndAssert([], 400)
})
})
})
})
})
const generateSaveTokenAndCreateUser = async (
someUser: any
): Promise<string> => {
const userController = new UserController()
const dbUser = await userController.createUser(someUser)
const makeRequestAndAssert = async (
programTypes: RunTimeType[],
expectedStatusCode: number,
expectedRuntime?: RunTimeType
) => {
const programPath = path.join(testFilesFolder, 'program')
for (const programType of programTypes) {
if (programType === RunTimeType.JS)
await createFile(
path.join(filesFolder, `${programPath}.js`),
sampleJsProgram
)
else if (programType === RunTimeType.PY)
await createFile(
path.join(filesFolder, `${programPath}.py`),
samplePyProgram
)
else if (programType === RunTimeType.SAS)
await createFile(
path.join(filesFolder, `${programPath}.sas`),
sampleSasProgram
)
}
return generateAndSaveToken(dbUser.id)
await request(app)
.get(`/SASjsApi/stp/execute?_program=${programPath}`)
.auth(accessToken, { type: 'bearer' })
.send()
.expect(expectedStatusCode)
if (expectedRuntime)
expect(ProcessProgramModule.processProgram).toHaveBeenCalledWith(
expect.anything(),
expect.anything(),
expect.anything(),
expect.anything(),
expect.anything(),
expect.anything(),
expect.anything(),
expectedRuntime,
expect.anything(),
undefined
)
}
const generateAndSaveToken = async (userId: number) => {
@@ -367,6 +475,10 @@ const setupMocks = async () => {
.spyOn(JSSessionController.prototype, 'getSession')
.mockImplementation(mockedGetSession)
jest
.spyOn(PythonSessionController.prototype, 'getSession')
.mockImplementation(mockedGetSession)
jest
.spyOn(ProcessProgramModule, 'processProgram')
.mockImplementation(() => Promise.resolve())

View File

@@ -13,7 +13,7 @@ stpRouter.get('/execute', async (req, res) => {
if (error) return res.status(400).send(error.details[0].message)
try {
const response = await controller.executeReturnRaw(req, query._program)
const response = await controller.executeGetRequest(req, query._program)
if (response instanceof Buffer) {
res.writeHead(200, (req as any).sasHeaders)
@@ -42,7 +42,7 @@ stpRouter.post(
// if (errQ && errB) return res.status(400).send(errB.details[0].message)
try {
const response = await controller.executeReturnJson(
const response = await controller.executePostRequest(
req,
req.body,
req.query?._program as string

View File

@@ -2,10 +2,13 @@ declare namespace NodeJS {
export interface Process {
sasLoc?: string
nodeLoc?: string
pythonLoc?: string
driveLoc: string
logsLoc: string
logsUUID: string
sasSessionController?: import('../../controllers/internal').SASSessionController
jsSessionController?: import('../../controllers/internal').JSSessionController
pythonSessionController?: import('../../controllers/internal').PythonSessionController
appStreamConfig: import('../').AppStreamConfig
logger: import('@sasjs/utils/logger').Logger
runTimes: import('../../utils').RunTimeType[]

View File

@@ -4,9 +4,9 @@ import { createFolder, fileExists, folderExists, isWindows } from '@sasjs/utils'
import { RunTimeType } from './verifyEnvVariables'
export const getDesktopFields = async () => {
const { SAS_PATH, NODE_PATH } = process.env
const { SAS_PATH, NODE_PATH, PYTHON_PATH } = process.env
let sasLoc, nodeLoc
let sasLoc, nodeLoc, pythonLoc
if (process.runTimes.includes(RunTimeType.SAS)) {
sasLoc = SAS_PATH ?? (await getSASLocation())
@@ -16,7 +16,11 @@ export const getDesktopFields = async () => {
nodeLoc = NODE_PATH ?? (await getNodeLocation())
}
return { sasLoc, nodeLoc }
if (process.runTimes.includes(RunTimeType.JS)) {
pythonLoc = PYTHON_PATH ?? (await getPythonLocation())
}
return { sasLoc, nodeLoc, pythonLoc }
}
const getDriveLocation = async (): Promise<string> => {
@@ -91,3 +95,25 @@ const getNodeLocation = async (): Promise<string> => {
return targetName
}
const getPythonLocation = async (): Promise<string> => {
const validator = async (filePath: string) => {
if (!filePath) return 'Path to Python executable is required.'
if (!(await fileExists(filePath))) {
return 'No file found at provided path.'
}
return true
}
const defaultLocation = isWindows() ? 'C:\\Python' : '/usr/bin/python'
const targetName = await getString(
'Please enter full path to a Python executable: ',
validator,
defaultLocation
)
return targetName
}

View File

@@ -5,7 +5,7 @@ import { RunTimeType } from '.'
export const getRunTimeAndFilePath = async (programPath: string) => {
const ext = path.extname(programPath)
// If programPath (_program) is provided with a ".sas" or ".js" extension
// If programPath (_program) is provided with a ".sas", ".js" or ".py" extension
// we should use that extension to determine the appropriate runTime
if (ext && Object.values(RunTimeType).includes(ext.slice(1) as RunTimeType)) {
const runTime = ext.slice(1)

View File

@@ -28,11 +28,13 @@ export const setProcessVariables = async () => {
if (MODE === ModeType.Server) {
process.sasLoc = process.env.SAS_PATH
process.nodeLoc = process.env.NODE_PATH
process.pythonLoc = process.env.PYTHON_PATH
} else {
const { sasLoc, nodeLoc } = await getDesktopFields()
const { sasLoc, nodeLoc, pythonLoc } = await getDesktopFields()
process.sasLoc = sasLoc
process.nodeLoc = nodeLoc
process.pythonLoc = pythonLoc
}
const { SASJS_ROOT } = process.env
@@ -48,6 +50,8 @@ export const setProcessVariables = async () => {
await createFolder(absLogsPath)
process.logsLoc = getRealPath(absLogsPath)
process.logsUUID = 'SASJS_LOGS_SEPARATOR_163ee17b6ff24f028928972d80a26784'
console.log('sasLoc: ', process.sasLoc)
console.log('sasDrive: ', process.driveLoc)
console.log('sasLogs: ', process.logsLoc)

View File

@@ -126,9 +126,34 @@ export const generateFileUploadJSCode = async (
}
})
if (fileCount) {
uploadCode = `\nconst _WEBIN_FILE_COUNT = ${fileCount}` + uploadCode
}
uploadCode += `\nconst _WEBIN_FILE_COUNT = ${fileCount}`
return uploadCode
}
/**
* Generates the python code that references uploaded files in the concurrent request
* @param filesNamesMap object that maps hashed file names and original file names
* @param sessionFolder name of the folder that is created for the purpose of files in concurrent request
* @returns generated python code
*/
export const generateFileUploadPythonCode = async (
filesNamesMap: FilenamesMap,
sessionFolder: string
) => {
let uploadCode = ''
let fileCount = 0
const sessionFolderList: string[] = await listFilesInFolder(sessionFolder)
sessionFolderList.forEach(async (fileName) => {
if (fileName.includes('req_file')) {
fileCount++
uploadCode += `\n_WEBIN_FILENAME${fileCount} = '${filesNamesMap[fileName].originalName}'`
uploadCode += `\n_WEBIN_NAME${fileCount} = '${filesNamesMap[fileName].fieldName}'`
}
})
uploadCode += `\n_WEBIN_FILE_COUNT = ${fileCount}`
return uploadCode
}

View File

@@ -28,7 +28,8 @@ export enum LOG_FORMAT_MORGANType {
export enum RunTimeType {
SAS = 'sas',
JS = 'js'
JS = 'js',
PY = 'py'
}
export enum ReturnCode {
@@ -228,7 +229,7 @@ const verifyRUN_TIMES = (): string[] => {
const verifyExecutablePaths = () => {
const errors: string[] = []
const { RUN_TIMES, SAS_PATH, NODE_PATH, MODE } = process.env
const { RUN_TIMES, SAS_PATH, NODE_PATH, PYTHON_PATH, MODE } = process.env
if (MODE === ModeType.Server) {
const runTimes = RUN_TIMES?.split(',')
@@ -240,6 +241,10 @@ const verifyExecutablePaths = () => {
if (runTimes?.includes(RunTimeType.JS) && !NODE_PATH) {
errors.push(`- NODE_PATH is required for ${RunTimeType.JS} run time`)
}
if (runTimes?.includes(RunTimeType.PY) && !PYTHON_PATH) {
errors.push(`- PYTHON_PATH is required for ${RunTimeType.PY} run time`)
}
}
return errors

331
web/package-lock.json generated
View File

@@ -2284,6 +2284,58 @@
"node": ">=8"
}
},
"node_modules/@jridgewell/gen-mapping": {
"version": "0.3.2",
"resolved": "https://registry.npmjs.org/@jridgewell/gen-mapping/-/gen-mapping-0.3.2.tgz",
"integrity": "sha512-mh65xKQAzI6iBcFzwv28KVWSmCkdRBWoOh+bYQGW3+6OZvbbN3TqMGo5hqYxQniRcH9F2VZIoJCm4pa3BPDK/A==",
"dependencies": {
"@jridgewell/set-array": "^1.0.1",
"@jridgewell/sourcemap-codec": "^1.4.10",
"@jridgewell/trace-mapping": "^0.3.9"
},
"engines": {
"node": ">=6.0.0"
}
},
"node_modules/@jridgewell/resolve-uri": {
"version": "3.1.0",
"resolved": "https://registry.npmjs.org/@jridgewell/resolve-uri/-/resolve-uri-3.1.0.tgz",
"integrity": "sha512-F2msla3tad+Mfht5cJq7LSXcdudKTWCVYUgw6pLFOOHSTtZlj6SWNYAp+AhuqLmWdBO2X5hPrLcu8cVP8fy28w==",
"engines": {
"node": ">=6.0.0"
}
},
"node_modules/@jridgewell/set-array": {
"version": "1.1.2",
"resolved": "https://registry.npmjs.org/@jridgewell/set-array/-/set-array-1.1.2.tgz",
"integrity": "sha512-xnkseuNADM0gt2bs+BvhO0p78Mk762YnZdsuzFV018NoG1Sj1SCQvpSqa7XUaTam5vAGasABV9qXASMKnFMwMw==",
"engines": {
"node": ">=6.0.0"
}
},
"node_modules/@jridgewell/source-map": {
"version": "0.3.2",
"resolved": "https://registry.npmjs.org/@jridgewell/source-map/-/source-map-0.3.2.tgz",
"integrity": "sha512-m7O9o2uR8k2ObDysZYzdfhb08VuEml5oWGiosa1VdaPZ/A6QyPkAJuwN0Q1lhULOf6B7MtQmHENS743hWtCrgw==",
"dependencies": {
"@jridgewell/gen-mapping": "^0.3.0",
"@jridgewell/trace-mapping": "^0.3.9"
}
},
"node_modules/@jridgewell/sourcemap-codec": {
"version": "1.4.14",
"resolved": "https://registry.npmjs.org/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.4.14.tgz",
"integrity": "sha512-XPSJHWmi394fuUuzDnGz1wiKqWfo1yXecHQMRf2l6hztTO+nPru658AyDngaBe7isIxEkRsPR3FZh+s7iVa4Uw=="
},
"node_modules/@jridgewell/trace-mapping": {
"version": "0.3.14",
"resolved": "https://registry.npmjs.org/@jridgewell/trace-mapping/-/trace-mapping-0.3.14.tgz",
"integrity": "sha512-bJWEfQ9lPTvm3SneWwRFVLzrh6nhjwqw7TUFFBEMzwvg7t7PCDenf2lDwqo4NQXzdpgBXyFgDWnQA+2vkruksQ==",
"dependencies": {
"@jridgewell/resolve-uri": "^3.0.3",
"@jridgewell/sourcemap-codec": "^1.4.10"
}
},
"node_modules/@mui/core": {
"version": "5.0.0-alpha.54",
"resolved": "https://registry.npmjs.org/@mui/core/-/core-5.0.0-alpha.54.tgz",
@@ -3937,11 +3989,9 @@
}
},
"node_modules/acorn": {
"version": "7.4.1",
"resolved": "https://registry.npmjs.org/acorn/-/acorn-7.4.1.tgz",
"integrity": "sha512-nQyp0o1/mNdbTO1PO6kHkwSrmgZ0MT/jCCpNiwbUjGoRN4dlBhqJtoQuCnEOKzgTVwg0ZWiCoQy6SxMebQVh8A==",
"dev": true,
"peer": true,
"version": "8.8.0",
"resolved": "https://registry.npmjs.org/acorn/-/acorn-8.8.0.tgz",
"integrity": "sha512-QOxyigPVrpZ2GXT+PFyZTl6TtOFc5egxHIP9IlQ+RbupQuX4RkT/Bee4/kQuC02Xkzg84JcT7oLYtDIQxp+v7w==",
"bin": {
"acorn": "bin/acorn"
},
@@ -6522,18 +6572,6 @@
"node": "^12.22.0 || ^14.17.0 || >=16.0.0"
}
},
"node_modules/espree/node_modules/acorn": {
"version": "8.7.0",
"resolved": "https://registry.npmjs.org/acorn/-/acorn-8.7.0.tgz",
"integrity": "sha512-V/LGr1APy+PXIwKebEWrkZPwoeoF+w1jiOBUmuxuiUIaOHtob8Qc9BTrYo7VuI5fR8tqsy+buA2WFooR5olqvQ==",
"dev": true,
"bin": {
"acorn": "bin/acorn"
},
"engines": {
"node": ">=0.4.0"
}
},
"node_modules/espree/node_modules/eslint-visitor-keys": {
"version": "3.3.0",
"resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-3.3.0.tgz",
@@ -7197,20 +7235,6 @@
"node": ">=12"
}
},
"node_modules/html-minifier-terser/node_modules/acorn": {
"version": "8.7.0",
"resolved": "https://registry.npmjs.org/acorn/-/acorn-8.7.0.tgz",
"integrity": "sha512-V/LGr1APy+PXIwKebEWrkZPwoeoF+w1jiOBUmuxuiUIaOHtob8Qc9BTrYo7VuI5fR8tqsy+buA2WFooR5olqvQ==",
"dev": true,
"optional": true,
"peer": true,
"bin": {
"acorn": "bin/acorn"
},
"engines": {
"node": ">=0.4.0"
}
},
"node_modules/html-minifier-terser/node_modules/commander": {
"version": "8.3.0",
"resolved": "https://registry.npmjs.org/commander/-/commander-8.3.0.tgz",
@@ -7220,46 +7244,6 @@
"node": ">= 12"
}
},
"node_modules/html-minifier-terser/node_modules/source-map": {
"version": "0.7.3",
"resolved": "https://registry.npmjs.org/source-map/-/source-map-0.7.3.tgz",
"integrity": "sha512-CkCj6giN3S+n9qrYiBTX5gystlENnRW5jZeNLHpe6aue+SrHcG5VYwujhW9s4dY31mEGsxBDrHR6oI69fTXsaQ==",
"dev": true,
"engines": {
"node": ">= 8"
}
},
"node_modules/html-minifier-terser/node_modules/terser": {
"version": "5.10.0",
"resolved": "https://registry.npmjs.org/terser/-/terser-5.10.0.tgz",
"integrity": "sha512-AMmF99DMfEDiRJfxfY5jj5wNH/bYO09cniSqhfoyxc8sFoYIgkJy86G04UoZU5VjlpnplVu0K6Tx6E9b5+DlHA==",
"dev": true,
"dependencies": {
"commander": "^2.20.0",
"source-map": "~0.7.2",
"source-map-support": "~0.5.20"
},
"bin": {
"terser": "bin/terser"
},
"engines": {
"node": ">=10"
},
"peerDependencies": {
"acorn": "^8.5.0"
},
"peerDependenciesMeta": {
"acorn": {
"optional": true
}
}
},
"node_modules/html-minifier-terser/node_modules/terser/node_modules/commander": {
"version": "2.20.3",
"resolved": "https://registry.npmjs.org/commander/-/commander-2.20.3.tgz",
"integrity": "sha512-GpVkmM8vF2vQUkj2LvZmD35JxeJOLCwJ9cUkugyk2nuhbv3+mJvpLYYt+0+USMxE+oj+ey/lJEnhZw75x/OMcQ==",
"dev": true
},
"node_modules/html-webpack-plugin": {
"version": "5.5.0",
"resolved": "https://registry.npmjs.org/html-webpack-plugin/-/html-webpack-plugin-5.5.0.tgz",
@@ -10287,6 +10271,28 @@
"node": ">=6"
}
},
"node_modules/terser": {
"version": "5.14.2",
"resolved": "https://registry.npmjs.org/terser/-/terser-5.14.2.tgz",
"integrity": "sha512-oL0rGeM/WFQCUd0y2QrWxYnq7tfSuKBiqTjRPWrRgB46WD/kiwHwF8T23z78H6Q6kGCuuHcPB+KULHRdxvVGQA==",
"dependencies": {
"@jridgewell/source-map": "^0.3.2",
"acorn": "^8.5.0",
"commander": "^2.20.0",
"source-map-support": "~0.5.20"
},
"bin": {
"terser": "bin/terser"
},
"engines": {
"node": ">=10"
}
},
"node_modules/terser/node_modules/commander": {
"version": "2.20.3",
"resolved": "https://registry.npmjs.org/commander/-/commander-2.20.3.tgz",
"integrity": "sha512-GpVkmM8vF2vQUkj2LvZmD35JxeJOLCwJ9cUkugyk2nuhbv3+mJvpLYYt+0+USMxE+oj+ey/lJEnhZw75x/OMcQ=="
},
"node_modules/text-table": {
"version": "0.2.0",
"resolved": "https://registry.npmjs.org/text-table/-/text-table-0.2.0.tgz",
@@ -11124,17 +11130,6 @@
"node": ">=10.0.0"
}
},
"node_modules/webpack/node_modules/acorn": {
"version": "8.7.0",
"resolved": "https://registry.npmjs.org/acorn/-/acorn-8.7.0.tgz",
"integrity": "sha512-V/LGr1APy+PXIwKebEWrkZPwoeoF+w1jiOBUmuxuiUIaOHtob8Qc9BTrYo7VuI5fR8tqsy+buA2WFooR5olqvQ==",
"bin": {
"acorn": "bin/acorn"
},
"engines": {
"node": ">=0.4.0"
}
},
"node_modules/webpack/node_modules/acorn-import-assertions": {
"version": "1.8.0",
"resolved": "https://registry.npmjs.org/acorn-import-assertions/-/acorn-import-assertions-1.8.0.tgz",
@@ -11143,11 +11138,6 @@
"acorn": "^8"
}
},
"node_modules/webpack/node_modules/commander": {
"version": "2.20.3",
"resolved": "https://registry.npmjs.org/commander/-/commander-2.20.3.tgz",
"integrity": "sha512-GpVkmM8vF2vQUkj2LvZmD35JxeJOLCwJ9cUkugyk2nuhbv3+mJvpLYYt+0+USMxE+oj+ey/lJEnhZw75x/OMcQ=="
},
"node_modules/webpack/node_modules/has-flag": {
"version": "4.0.0",
"resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz",
@@ -11216,30 +11206,6 @@
"url": "https://github.com/chalk/supports-color?sponsor=1"
}
},
"node_modules/webpack/node_modules/terser": {
"version": "5.10.0",
"resolved": "https://registry.npmjs.org/terser/-/terser-5.10.0.tgz",
"integrity": "sha512-AMmF99DMfEDiRJfxfY5jj5wNH/bYO09cniSqhfoyxc8sFoYIgkJy86G04UoZU5VjlpnplVu0K6Tx6E9b5+DlHA==",
"dependencies": {
"commander": "^2.20.0",
"source-map": "~0.7.2",
"source-map-support": "~0.5.20"
},
"bin": {
"terser": "bin/terser"
},
"engines": {
"node": ">=10"
},
"peerDependencies": {
"acorn": "^8.5.0"
},
"peerDependenciesMeta": {
"acorn": {
"optional": true
}
}
},
"node_modules/webpack/node_modules/terser-webpack-plugin": {
"version": "5.3.1",
"resolved": "https://registry.npmjs.org/terser-webpack-plugin/-/terser-webpack-plugin-5.3.1.tgz",
@@ -11273,14 +11239,6 @@
}
}
},
"node_modules/webpack/node_modules/terser/node_modules/source-map": {
"version": "0.7.3",
"resolved": "https://registry.npmjs.org/source-map/-/source-map-0.7.3.tgz",
"integrity": "sha512-CkCj6giN3S+n9qrYiBTX5gystlENnRW5jZeNLHpe6aue+SrHcG5VYwujhW9s4dY31mEGsxBDrHR6oI69fTXsaQ==",
"engines": {
"node": ">= 8"
}
},
"node_modules/webpack/node_modules/webpack-sources": {
"version": "3.2.3",
"resolved": "https://registry.npmjs.org/webpack-sources/-/webpack-sources-3.2.3.tgz",
@@ -12914,6 +12872,49 @@
}
}
},
"@jridgewell/gen-mapping": {
"version": "0.3.2",
"resolved": "https://registry.npmjs.org/@jridgewell/gen-mapping/-/gen-mapping-0.3.2.tgz",
"integrity": "sha512-mh65xKQAzI6iBcFzwv28KVWSmCkdRBWoOh+bYQGW3+6OZvbbN3TqMGo5hqYxQniRcH9F2VZIoJCm4pa3BPDK/A==",
"requires": {
"@jridgewell/set-array": "^1.0.1",
"@jridgewell/sourcemap-codec": "^1.4.10",
"@jridgewell/trace-mapping": "^0.3.9"
}
},
"@jridgewell/resolve-uri": {
"version": "3.1.0",
"resolved": "https://registry.npmjs.org/@jridgewell/resolve-uri/-/resolve-uri-3.1.0.tgz",
"integrity": "sha512-F2msla3tad+Mfht5cJq7LSXcdudKTWCVYUgw6pLFOOHSTtZlj6SWNYAp+AhuqLmWdBO2X5hPrLcu8cVP8fy28w=="
},
"@jridgewell/set-array": {
"version": "1.1.2",
"resolved": "https://registry.npmjs.org/@jridgewell/set-array/-/set-array-1.1.2.tgz",
"integrity": "sha512-xnkseuNADM0gt2bs+BvhO0p78Mk762YnZdsuzFV018NoG1Sj1SCQvpSqa7XUaTam5vAGasABV9qXASMKnFMwMw=="
},
"@jridgewell/source-map": {
"version": "0.3.2",
"resolved": "https://registry.npmjs.org/@jridgewell/source-map/-/source-map-0.3.2.tgz",
"integrity": "sha512-m7O9o2uR8k2ObDysZYzdfhb08VuEml5oWGiosa1VdaPZ/A6QyPkAJuwN0Q1lhULOf6B7MtQmHENS743hWtCrgw==",
"requires": {
"@jridgewell/gen-mapping": "^0.3.0",
"@jridgewell/trace-mapping": "^0.3.9"
}
},
"@jridgewell/sourcemap-codec": {
"version": "1.4.14",
"resolved": "https://registry.npmjs.org/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.4.14.tgz",
"integrity": "sha512-XPSJHWmi394fuUuzDnGz1wiKqWfo1yXecHQMRf2l6hztTO+nPru658AyDngaBe7isIxEkRsPR3FZh+s7iVa4Uw=="
},
"@jridgewell/trace-mapping": {
"version": "0.3.14",
"resolved": "https://registry.npmjs.org/@jridgewell/trace-mapping/-/trace-mapping-0.3.14.tgz",
"integrity": "sha512-bJWEfQ9lPTvm3SneWwRFVLzrh6nhjwqw7TUFFBEMzwvg7t7PCDenf2lDwqo4NQXzdpgBXyFgDWnQA+2vkruksQ==",
"requires": {
"@jridgewell/resolve-uri": "^3.0.3",
"@jridgewell/sourcemap-codec": "^1.4.10"
}
},
"@mui/core": {
"version": "5.0.0-alpha.54",
"resolved": "https://registry.npmjs.org/@mui/core/-/core-5.0.0-alpha.54.tgz",
@@ -14110,11 +14111,9 @@
}
},
"acorn": {
"version": "7.4.1",
"resolved": "https://registry.npmjs.org/acorn/-/acorn-7.4.1.tgz",
"integrity": "sha512-nQyp0o1/mNdbTO1PO6kHkwSrmgZ0MT/jCCpNiwbUjGoRN4dlBhqJtoQuCnEOKzgTVwg0ZWiCoQy6SxMebQVh8A==",
"dev": true,
"peer": true
"version": "8.8.0",
"resolved": "https://registry.npmjs.org/acorn/-/acorn-8.8.0.tgz",
"integrity": "sha512-QOxyigPVrpZ2GXT+PFyZTl6TtOFc5egxHIP9IlQ+RbupQuX4RkT/Bee4/kQuC02Xkzg84JcT7oLYtDIQxp+v7w=="
},
"acorn-jsx": {
"version": "5.3.2",
@@ -16061,12 +16060,6 @@
"eslint-visitor-keys": "^3.3.0"
},
"dependencies": {
"acorn": {
"version": "8.7.0",
"resolved": "https://registry.npmjs.org/acorn/-/acorn-8.7.0.tgz",
"integrity": "sha512-V/LGr1APy+PXIwKebEWrkZPwoeoF+w1jiOBUmuxuiUIaOHtob8Qc9BTrYo7VuI5fR8tqsy+buA2WFooR5olqvQ==",
"dev": true
},
"eslint-visitor-keys": {
"version": "3.3.0",
"resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-3.3.0.tgz",
@@ -16585,44 +16578,11 @@
"terser": "^5.10.0"
},
"dependencies": {
"acorn": {
"version": "8.7.0",
"resolved": "https://registry.npmjs.org/acorn/-/acorn-8.7.0.tgz",
"integrity": "sha512-V/LGr1APy+PXIwKebEWrkZPwoeoF+w1jiOBUmuxuiUIaOHtob8Qc9BTrYo7VuI5fR8tqsy+buA2WFooR5olqvQ==",
"dev": true,
"optional": true,
"peer": true
},
"commander": {
"version": "8.3.0",
"resolved": "https://registry.npmjs.org/commander/-/commander-8.3.0.tgz",
"integrity": "sha512-OkTL9umf+He2DZkUq8f8J9of7yL6RJKI24dVITBmNfZBmri9zYZQrKkuXiKhyfPSu8tUhnVBB1iKXevvnlR4Ww==",
"dev": true
},
"source-map": {
"version": "0.7.3",
"resolved": "https://registry.npmjs.org/source-map/-/source-map-0.7.3.tgz",
"integrity": "sha512-CkCj6giN3S+n9qrYiBTX5gystlENnRW5jZeNLHpe6aue+SrHcG5VYwujhW9s4dY31mEGsxBDrHR6oI69fTXsaQ==",
"dev": true
},
"terser": {
"version": "5.10.0",
"resolved": "https://registry.npmjs.org/terser/-/terser-5.10.0.tgz",
"integrity": "sha512-AMmF99DMfEDiRJfxfY5jj5wNH/bYO09cniSqhfoyxc8sFoYIgkJy86G04UoZU5VjlpnplVu0K6Tx6E9b5+DlHA==",
"dev": true,
"requires": {
"commander": "^2.20.0",
"source-map": "~0.7.2",
"source-map-support": "~0.5.20"
},
"dependencies": {
"commander": {
"version": "2.20.3",
"resolved": "https://registry.npmjs.org/commander/-/commander-2.20.3.tgz",
"integrity": "sha512-GpVkmM8vF2vQUkj2LvZmD35JxeJOLCwJ9cUkugyk2nuhbv3+mJvpLYYt+0+USMxE+oj+ey/lJEnhZw75x/OMcQ==",
"dev": true
}
}
}
}
},
@@ -18903,6 +18863,24 @@
"resolved": "https://registry.npmjs.org/tapable/-/tapable-2.2.1.tgz",
"integrity": "sha512-GNzQvQTOIP6RyTfE2Qxb8ZVlNmw0n88vp1szwWRimP02mnTsx3Wtn5qRdqY9w2XduFNUgvOwhNnQsjwCp+kqaQ=="
},
"terser": {
"version": "5.14.2",
"resolved": "https://registry.npmjs.org/terser/-/terser-5.14.2.tgz",
"integrity": "sha512-oL0rGeM/WFQCUd0y2QrWxYnq7tfSuKBiqTjRPWrRgB46WD/kiwHwF8T23z78H6Q6kGCuuHcPB+KULHRdxvVGQA==",
"requires": {
"@jridgewell/source-map": "^0.3.2",
"acorn": "^8.5.0",
"commander": "^2.20.0",
"source-map-support": "~0.5.20"
},
"dependencies": {
"commander": {
"version": "2.20.3",
"resolved": "https://registry.npmjs.org/commander/-/commander-2.20.3.tgz",
"integrity": "sha512-GpVkmM8vF2vQUkj2LvZmD35JxeJOLCwJ9cUkugyk2nuhbv3+mJvpLYYt+0+USMxE+oj+ey/lJEnhZw75x/OMcQ=="
}
}
},
"text-table": {
"version": "0.2.0",
"resolved": "https://registry.npmjs.org/text-table/-/text-table-0.2.0.tgz",
@@ -19259,22 +19237,12 @@
"webpack-sources": "^3.2.2"
},
"dependencies": {
"acorn": {
"version": "8.7.0",
"resolved": "https://registry.npmjs.org/acorn/-/acorn-8.7.0.tgz",
"integrity": "sha512-V/LGr1APy+PXIwKebEWrkZPwoeoF+w1jiOBUmuxuiUIaOHtob8Qc9BTrYo7VuI5fR8tqsy+buA2WFooR5olqvQ=="
},
"acorn-import-assertions": {
"version": "1.8.0",
"resolved": "https://registry.npmjs.org/acorn-import-assertions/-/acorn-import-assertions-1.8.0.tgz",
"integrity": "sha512-m7VZ3jwz4eK6A4Vtt8Ew1/mNbP24u0FhdyfA7fSvnJR6LMdfOYnmuIrrJAgrYfYJ10F/otaHTtrtrtmHdMNzEw==",
"requires": {}
},
"commander": {
"version": "2.20.3",
"resolved": "https://registry.npmjs.org/commander/-/commander-2.20.3.tgz",
"integrity": "sha512-GpVkmM8vF2vQUkj2LvZmD35JxeJOLCwJ9cUkugyk2nuhbv3+mJvpLYYt+0+USMxE+oj+ey/lJEnhZw75x/OMcQ=="
},
"has-flag": {
"version": "4.0.0",
"resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz",
@@ -19321,23 +19289,6 @@
"has-flag": "^4.0.0"
}
},
"terser": {
"version": "5.10.0",
"resolved": "https://registry.npmjs.org/terser/-/terser-5.10.0.tgz",
"integrity": "sha512-AMmF99DMfEDiRJfxfY5jj5wNH/bYO09cniSqhfoyxc8sFoYIgkJy86G04UoZU5VjlpnplVu0K6Tx6E9b5+DlHA==",
"requires": {
"commander": "^2.20.0",
"source-map": "~0.7.2",
"source-map-support": "~0.5.20"
},
"dependencies": {
"source-map": {
"version": "0.7.3",
"resolved": "https://registry.npmjs.org/source-map/-/source-map-0.7.3.tgz",
"integrity": "sha512-CkCj6giN3S+n9qrYiBTX5gystlENnRW5jZeNLHpe6aue+SrHcG5VYwujhW9s4dY31mEGsxBDrHR6oI69fTXsaQ=="
}
}
},
"terser-webpack-plugin": {
"version": "5.3.1",
"resolved": "https://registry.npmjs.org/terser-webpack-plugin/-/terser-webpack-plugin-5.3.1.tgz",

View File

@@ -23,7 +23,7 @@ const FilePathInputModal = ({
const handleChange = (event: React.ChangeEvent<HTMLInputElement>) => {
const value = event.target.value
const specialChars = /[`!@#$%^&*()_+\-=[\]{};':"\\|,<>?~]/
const specialChars = /[`!@#$%^&*()+\-=[\]{};':"\\|,<>?~]/
const fileExtension = /\.(exe|sh|htaccess)$/i
if (specialChars.test(value)) {

View File

@@ -44,7 +44,7 @@ const NameInputModal = ({
const value = event.target.value
const folderNameRegex = /[`!@#$%^&*()_+\-=[\]{};':"\\|,.<>/?~]/
const fileNameRegex = /[`!@#$%^&*()_+\-=[\]{};':"\\|,<>/?~]/
const fileNameRegex = /[`!@#$%^&*()+\-=[\]{};':"\\|,<>/?~]/
const fileNameExtensionRegex = /.(exe|sh|htaccess)$/i
const specialChars = isFolder ? folderNameRegex : fileNameRegex

View File

@@ -4,7 +4,8 @@ import React, {
useEffect,
useRef,
useState,
useContext
useContext,
useCallback
} from 'react'
import axios from 'axios'
@@ -70,6 +71,8 @@ type SASjsEditorProps = {
}
const baseUrl = window.location.origin
const SASJS_LOGS_SEPARATOR =
'SASJS_LOGS_SEPARATOR_163ee17b6ff24f028928972d80a26784'
const SASjsEditor = ({
selectedFilePath,
@@ -137,6 +140,88 @@ const SASjsEditor = ({
prevFileContent !== fileContent && !!selectedFilePath
)
const saveFile = useCallback(
(filePath?: string) => {
setIsLoading(true)
if (filePath) {
filePath = filePath.startsWith('/') ? filePath : `/${filePath}`
}
const formData = new FormData()
const stringBlob = new Blob([fileContent], { type: 'text/plain' })
formData.append('file', stringBlob)
formData.append('filePath', filePath ?? selectedFilePath)
const axiosPromise = filePath
? axios.post('/SASjsApi/drive/file', formData)
: axios.patch('/SASjsApi/drive/file', formData)
axiosPromise
.then(() => {
if (filePath && fileContent === prevFileContent) {
// when fileContent and prevFileContent is same,
// callback function in setPrevFileContent method is not called
// because behind the scene useEffect hook is being used
// for calling callback function, and it's only fired when the
// new value is not equal to old value.
// So, we'll have to explicitly update the selected file path
setSelectedFilePath(filePath, true)
} else {
setPrevFileContent(fileContent, () => {
if (filePath) {
setSelectedFilePath(filePath, true)
}
})
}
setSnackbarMessage('File saved!')
setSnackbarSeverity(AlertSeverityType.Success)
setOpenSnackbar(true)
})
.catch((err) => {
setModalTitle('Abort')
setModalPayload(
typeof err.response.data === 'object'
? JSON.stringify(err.response.data)
: err.response.data
)
setOpenModal(true)
})
.finally(() => {
setIsLoading(false)
})
},
[
fileContent,
prevFileContent,
selectedFilePath,
setPrevFileContent,
setSelectedFilePath
]
)
useEffect(() => {
editorRef.current.addAction({
// An unique identifier of the contributed action.
id: 'save-file',
// A label of the action that will be presented to the user.
label: 'Save',
// An optional array of keybindings for the action.
keybindings: [monaco.KeyMod.CtrlCmd | monaco.KeyCode.KeyS],
// Method that will be executed when the action is triggered.
// @param editor The editor instance is passed in as a convenience
run: () => {
if (!selectedFilePath) return setOpenFilePathInputModal(true)
if (prevFileContent !== fileContent) return saveFile()
}
})
}, [fileContent, prevFileContent, selectedFilePath, saveFile])
useEffect(() => {
setRunTimes(Object.values(appContext.runTimes))
}, [appContext.runTimes])
@@ -203,13 +288,8 @@ const SASjsEditor = ({
axios
.post(`/SASjsApi/code/execute`, { code, runTime: selectedRunTime })
.then((res: any) => {
const parsedLog = res?.data?.log
.map((logLine: any) => logLine.line)
.join('\n')
setLog(parsedLog)
setWebout(`${res.data?._webout}`)
setWebout(res.data.split(SASJS_LOGS_SEPARATOR)[0] ?? '')
setLog(res.data.split(SASJS_LOGS_SEPARATOR)[1] ?? '')
setTab('log')
// Scroll to bottom of log
@@ -252,59 +332,6 @@ const SASjsEditor = ({
saveFile(filePath)
}
const saveFile = (filePath?: string) => {
setIsLoading(true)
if (filePath) {
filePath = filePath.startsWith('/') ? filePath : `/${filePath}`
}
const formData = new FormData()
const stringBlob = new Blob([fileContent], { type: 'text/plain' })
formData.append('file', stringBlob, 'filename.sas')
formData.append('filePath', filePath ?? selectedFilePath)
const axiosPromise = filePath
? axios.post('/SASjsApi/drive/file', formData)
: axios.patch('/SASjsApi/drive/file', formData)
axiosPromise
.then(() => {
if (filePath && fileContent === prevFileContent) {
// when fileContent and prevFileContent is same,
// callback function in setPrevFileContent method is not called
// because behind the scene useEffect hook is being used
// for calling callback function, and it's only fired when the
// new value is not equal to old value.
// So, we'll have to explicitly update the selected file path
setSelectedFilePath(filePath, true)
} else {
setPrevFileContent(fileContent, () => {
if (filePath) {
setSelectedFilePath(filePath, true)
}
})
}
setSnackbarMessage('File saved!')
setSnackbarSeverity(AlertSeverityType.Success)
setOpenSnackbar(true)
})
.catch((err) => {
setModalTitle('Abort')
setModalPayload(
typeof err.response.data === 'object'
? JSON.stringify(err.response.data)
: err.response.data
)
setOpenModal(true)
})
.finally(() => {
setIsLoading(false)
})
}
return (
<Box sx={{ width: '100%', typography: 'body1', marginTop: '50px' }}>
<Backdrop