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