chore: init
This commit is contained in:
commit
847c57ea20
15 changed files with 3888 additions and 0 deletions
8
.gitignore
vendored
Normal file
8
.gitignore
vendored
Normal file
|
@ -0,0 +1,8 @@
|
||||||
|
# Build output
|
||||||
|
/dist/
|
||||||
|
|
||||||
|
# SVR.JS
|
||||||
|
/svrjs/
|
||||||
|
|
||||||
|
# Dependencies
|
||||||
|
node_modules
|
2
.husky/commit-msg
Executable file
2
.husky/commit-msg
Executable file
|
@ -0,0 +1,2 @@
|
||||||
|
#!/bin/sh
|
||||||
|
npx --no -- commitlint --edit "$1"
|
2
.husky/pre-commit
Executable file
2
.husky/pre-commit
Executable file
|
@ -0,0 +1,2 @@
|
||||||
|
#!/bin/sh
|
||||||
|
npx lint-staged
|
21
LICENSE
Normal file
21
LICENSE
Normal file
|
@ -0,0 +1,21 @@
|
||||||
|
MIT License
|
||||||
|
|
||||||
|
Copyright (c) 2018-2024 SVR.JS
|
||||||
|
|
||||||
|
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||||
|
of this software and associated documentation files (the "Software"), to deal
|
||||||
|
in the Software without restriction, including without limitation the rights
|
||||||
|
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||||
|
copies of the Software, and to permit persons to whom the Software is
|
||||||
|
furnished to do so, subject to the following conditions:
|
||||||
|
|
||||||
|
The above copyright notice and this permission notice shall be included in all
|
||||||
|
copies or substantial portions of the Software.
|
||||||
|
|
||||||
|
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||||
|
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||||
|
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||||
|
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||||
|
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||||
|
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||||
|
SOFTWARE.
|
29
README
Normal file
29
README
Normal file
|
@ -0,0 +1,29 @@
|
||||||
|
This repository contains SVR.JS mod starter code and its build system
|
||||||
|
The mod will work for SVR.JS Nightly-GitNext.
|
||||||
|
|
||||||
|
Before doing anything, run "npm install".
|
||||||
|
To build SVR.JS mod, run "npm run build".
|
||||||
|
To check SVR.JS mod code for errors with ESLint, run "npm run lint".
|
||||||
|
To fix and beautify SVR.JS mod code with ESLint and Prettier, run "npm run lint:fix".
|
||||||
|
To perform unit tests with Jest, run "npm test".
|
||||||
|
|
||||||
|
To test the mod:
|
||||||
|
1. Clone the SVR.JS repository with "git clone https://git.svrjs.org/svrjs/svrjs.git" command.
|
||||||
|
2. Change the working directory to "svrjs" using "cd svrjs".
|
||||||
|
3. Build SVR.JS by first running "npm install" and then running "npm run build".
|
||||||
|
4. Copy the mod into mods directory in the dist directory using "cp ../dist/mod.js dist/mods" (GNU/Linux, Unix, BSD) or "copy ..\dist\mod.js dist\mods" (Windows).
|
||||||
|
5. Do the necessary mod configuration if the mod requires it.
|
||||||
|
6. Run SVR.JS by running "npm start".
|
||||||
|
7. Do some requests to the endpoints covered by the mod.
|
||||||
|
|
||||||
|
Structure:
|
||||||
|
- dist - contains the built SVR.JS mod
|
||||||
|
- src - contains SVR.JS mod source code
|
||||||
|
- index.js - entry point
|
||||||
|
- utils - utility functions
|
||||||
|
- tests - Jest unit tests
|
||||||
|
- utils - unit tests for utility functions
|
||||||
|
- esbuild.config.js - the build script
|
||||||
|
- eslint.config.js - ESLint configuration
|
||||||
|
- jest.config.js - Jest configuration
|
||||||
|
- modInfo.json - SVR.JS mod name and version
|
3
commitlint.config.js
Normal file
3
commitlint.config.js
Normal file
|
@ -0,0 +1,3 @@
|
||||||
|
module.exports = {
|
||||||
|
extends: ["@commitlint/config-conventional"]
|
||||||
|
};
|
11
esbuild.config.js
Normal file
11
esbuild.config.js
Normal file
|
@ -0,0 +1,11 @@
|
||||||
|
const esbuild = require("esbuild");
|
||||||
|
|
||||||
|
esbuild.build({
|
||||||
|
entryPoints: ["src/index.js"],
|
||||||
|
bundle: true,
|
||||||
|
outfile: "dist/mod.js", // Mod output file
|
||||||
|
platform: "node",
|
||||||
|
target: "es2017",
|
||||||
|
}).catch((err) => {
|
||||||
|
throw err;
|
||||||
|
});
|
21
eslint.config.js
Normal file
21
eslint.config.js
Normal file
|
@ -0,0 +1,21 @@
|
||||||
|
const globals = require("globals");
|
||||||
|
const pluginJs = require("@eslint/js");
|
||||||
|
const eslintPluginPrettierRecommended = require("eslint-plugin-prettier/recommended");
|
||||||
|
|
||||||
|
module.exports = [
|
||||||
|
{
|
||||||
|
files: ["**/*.js"],
|
||||||
|
languageOptions: {
|
||||||
|
sourceType: "commonjs"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
languageOptions: {
|
||||||
|
globals: {
|
||||||
|
...globals.node
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
pluginJs.configs.recommended,
|
||||||
|
eslintPluginPrettierRecommended
|
||||||
|
];
|
5
jest.config.js
Normal file
5
jest.config.js
Normal file
|
@ -0,0 +1,5 @@
|
||||||
|
module.exports = {
|
||||||
|
testEnvironment: 'node',
|
||||||
|
testMatch: ['**/tests/**/*.test.js'],
|
||||||
|
verbose: true,
|
||||||
|
};
|
3
lint-staged.config.js
Normal file
3
lint-staged.config.js
Normal file
|
@ -0,0 +1,3 @@
|
||||||
|
module.exports = {
|
||||||
|
"src/**/*.js": "eslint --cache --fix"
|
||||||
|
};
|
4
modInfo.json
Normal file
4
modInfo.json
Normal file
|
@ -0,0 +1,4 @@
|
||||||
|
{
|
||||||
|
"name": "MERNMail integration for SVR.JS",
|
||||||
|
"version": "0.0.0"
|
||||||
|
}
|
3611
package-lock.json
generated
Normal file
3611
package-lock.json
generated
Normal file
File diff suppressed because it is too large
Load diff
31
package.json
Normal file
31
package.json
Normal file
|
@ -0,0 +1,31 @@
|
||||||
|
{
|
||||||
|
"name": "svrjs-mod-starter",
|
||||||
|
"version": "0.0.0",
|
||||||
|
"private": true,
|
||||||
|
"scripts": {
|
||||||
|
"build": "node esbuild.config.js",
|
||||||
|
"cz": "commitizen",
|
||||||
|
"lint": "eslint --no-error-on-unmatched-pattern src/**/*.js src/*.js tests/**/*.test.js tests/**/*.js",
|
||||||
|
"lint:fix": "npm run lint -- --fix",
|
||||||
|
"prepare": "husky"
|
||||||
|
},
|
||||||
|
"devDependencies": {
|
||||||
|
"@commitlint/cli": "^19.5.0",
|
||||||
|
"@commitlint/config-conventional": "^19.5.0",
|
||||||
|
"@eslint/js": "^9.9.1",
|
||||||
|
"commitizen": "^4.3.1",
|
||||||
|
"cz-conventional-changelog": "^3.3.0",
|
||||||
|
"esbuild": "^0.23.1",
|
||||||
|
"eslint": "^9.9.1",
|
||||||
|
"eslint-config-prettier": "^9.1.0",
|
||||||
|
"eslint-plugin-prettier": "^5.2.1",
|
||||||
|
"globals": "^15.9.0",
|
||||||
|
"husky": "^9.1.6",
|
||||||
|
"prettier": "^3.3.3"
|
||||||
|
},
|
||||||
|
"config": {
|
||||||
|
"commitizen": {
|
||||||
|
"path": "./node_modules/cz-conventional-changelog"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
47
src/index.js
Normal file
47
src/index.js
Normal file
|
@ -0,0 +1,47 @@
|
||||||
|
const modInfo = require("../modInfo.json"); // SVR.JS mod information
|
||||||
|
const parseURL = require("./utils/urlParser.js"); // URL parser from SVR.JS
|
||||||
|
|
||||||
|
let handler = null;
|
||||||
|
let handlerError = null;
|
||||||
|
try {
|
||||||
|
handler = require(process.cwd() + "/dist/handler.js");
|
||||||
|
} catch (err) {
|
||||||
|
handlerError = err;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Exported SVR.JS mod callback
|
||||||
|
module.exports = (req, res, logFacilities, config, next) => {
|
||||||
|
if (handlerError) {
|
||||||
|
// Respond with 500 Internal Server Error code, if MERNMail fails to load
|
||||||
|
res.error(500, "mernmail-integration", handlerError);
|
||||||
|
} else if (req.parsedURL.pathname.match(/^\/api(?:$|[/?#])/)) {
|
||||||
|
// Use MERNMail handler
|
||||||
|
handler(req, res);
|
||||||
|
} else {
|
||||||
|
// Rewrite the URL and use SVR.JS built-in static file serving functionality
|
||||||
|
try {
|
||||||
|
const rewrittenAgainURL =
|
||||||
|
"/frontend/dist" +
|
||||||
|
req.parsedURL.pathname +
|
||||||
|
(req.parsedURL.search ? req.parsedURL.search : "") +
|
||||||
|
(req.parsedURL.hash ? req.parsedURL.hash : "");
|
||||||
|
req.url = rewrittenAgainURL;
|
||||||
|
req.parsedURL = parseURL(
|
||||||
|
req.url,
|
||||||
|
`http${req.socket.encrypted ? "s" : ""}://${
|
||||||
|
req.headers.host
|
||||||
|
? req.headers.host
|
||||||
|
: config.domain
|
||||||
|
? config.domain
|
||||||
|
: "unknown.invalid"
|
||||||
|
}`,
|
||||||
|
);
|
||||||
|
next();
|
||||||
|
} catch (err) {
|
||||||
|
res.error(500, "mernmail-integration", err);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
module.exports.modInfo = modInfo;
|
90
src/utils/urlParser.js
Normal file
90
src/utils/urlParser.js
Normal file
|
@ -0,0 +1,90 @@
|
||||||
|
const url = require("url");
|
||||||
|
|
||||||
|
// SVR.JS URL parser function (compatible with legacy Node.JS URL parsing function)
|
||||||
|
function parseURL(uri, prepend) {
|
||||||
|
// Replace newline characters with its respective URL encodings
|
||||||
|
uri = uri.replace(/\r/g, "%0D").replace(/\n/g, "%0A");
|
||||||
|
|
||||||
|
// If URL begins with a slash, prepend a string if available
|
||||||
|
if (prepend && uri[0] == "/") uri = prepend.replace(/\/+$/, "") + uri;
|
||||||
|
|
||||||
|
// Determine if URL has slashes
|
||||||
|
let hasSlashes = uri.indexOf("/") != -1;
|
||||||
|
|
||||||
|
// Parse the URL using regular expression
|
||||||
|
let parsedURI = uri.match(
|
||||||
|
/^(?:([^:]+:)(\/\/)?)?(?:([^@/?#*]+)@)?([^:/?#*]+|\[[^*\]/]\])?(?::([0-9]+))?(\*|\/[^?#]*)?(\?[^#]*)?(#[\S\s]*)?/,
|
||||||
|
);
|
||||||
|
// Match 1: protocol
|
||||||
|
// Match 2: slashes after protocol
|
||||||
|
// Match 3: authentication credentials
|
||||||
|
// Match 4: host name
|
||||||
|
// Match 5: port
|
||||||
|
// Match 6: path name
|
||||||
|
// Match 7: query string
|
||||||
|
// Match 8: hash
|
||||||
|
|
||||||
|
// If regular expression didn't match the entire URL, throw an error
|
||||||
|
if (parsedURI[0].length != uri.length) throw new Error("Invalid URL: " + uri);
|
||||||
|
|
||||||
|
// If match 1 is not empty, set the slash variable based on state of match 2
|
||||||
|
if (parsedURI[1]) hasSlashes = parsedURI[2] == "//";
|
||||||
|
|
||||||
|
// If match 6 is empty and URL has slashes, set it to a slash.
|
||||||
|
if (hasSlashes && !parsedURI[6]) parsedURI[6] = "/";
|
||||||
|
|
||||||
|
// If match 4 contains Unicode characters, convert it to Punycode. If the result is an empty string, throw an error
|
||||||
|
if (parsedURI[4] && !parsedURI[4].match(/^[a-zA-Z0-9.-]+$/)) {
|
||||||
|
parsedURI[4] = url.domainToASCII(parsedURI[4]);
|
||||||
|
if (!parsedURI[4]) throw new Error("Invalid URL: " + uri);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Create a new URL object
|
||||||
|
let uobject = new url.Url();
|
||||||
|
|
||||||
|
// Populate a URL object
|
||||||
|
if (hasSlashes) uobject.slashes = true;
|
||||||
|
if (parsedURI[1]) uobject.protocol = parsedURI[1];
|
||||||
|
if (parsedURI[3]) uobject.auth = parsedURI[3];
|
||||||
|
if (parsedURI[4]) {
|
||||||
|
uobject.host = parsedURI[4] + (parsedURI[5] ? ":" + parsedURI[5] : "");
|
||||||
|
if (parsedURI[4][0] == "[")
|
||||||
|
uobject.hostname = parsedURI[4].substring(1, parsedURI[4].length - 1);
|
||||||
|
else uobject.hostname = parsedURI[4];
|
||||||
|
}
|
||||||
|
if (parsedURI[5]) uobject.port = parsedURI[5];
|
||||||
|
if (parsedURI[6]) uobject.pathname = parsedURI[6];
|
||||||
|
if (parsedURI[7]) {
|
||||||
|
uobject.search = parsedURI[7];
|
||||||
|
// Parse query strings
|
||||||
|
let qobject = Object.create(null);
|
||||||
|
const parsedQuery = parsedURI[7]
|
||||||
|
.substring(1)
|
||||||
|
.match(/([^&=]*)(?:=([^&]*))?/g);
|
||||||
|
parsedQuery.forEach((qp) => {
|
||||||
|
if (qp.length > 0) {
|
||||||
|
let parsedQP = qp.match(/([^&=]*)(?:=([^&]*))?/);
|
||||||
|
if (parsedQP) {
|
||||||
|
qobject[parsedQP[1]] = parsedQP[2] ? parsedQP[2] : "";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
uobject.query = qobject;
|
||||||
|
} else {
|
||||||
|
uobject.query = Object.create(null);
|
||||||
|
}
|
||||||
|
if (parsedURI[8]) uobject.hash = parsedURI[8];
|
||||||
|
if (uobject.pathname)
|
||||||
|
uobject.path = uobject.pathname + (uobject.search ? uobject.search : "");
|
||||||
|
uobject.href =
|
||||||
|
(uobject.protocol ? uobject.protocol + (uobject.slashes ? "//" : "") : "") +
|
||||||
|
(uobject.auth ? uobject.auth + "@" : "") +
|
||||||
|
(uobject.hostname ? uobject.hostname : "") +
|
||||||
|
(uobject.port ? ":" + uobject.port : "") +
|
||||||
|
(uobject.path ? uobject.path : "") +
|
||||||
|
(uobject.hash ? uobject.hash : "");
|
||||||
|
|
||||||
|
return uobject;
|
||||||
|
}
|
||||||
|
|
||||||
|
module.exports = parseURL;
|
Loading…
Reference in a new issue