chore: init

This commit is contained in:
Dorian Niemiec 2024-11-08 16:32:47 +01:00
commit 847c57ea20
15 changed files with 3888 additions and 0 deletions

8
.gitignore vendored Normal file
View file

@ -0,0 +1,8 @@
# Build output
/dist/
# SVR.JS
/svrjs/
# Dependencies
node_modules

2
.husky/commit-msg Executable file
View file

@ -0,0 +1,2 @@
#!/bin/sh
npx --no -- commitlint --edit "$1"

2
.husky/pre-commit Executable file
View file

@ -0,0 +1,2 @@
#!/bin/sh
npx lint-staged

21
LICENSE Normal file
View 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
View 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
View file

@ -0,0 +1,3 @@
module.exports = {
extends: ["@commitlint/config-conventional"]
};

11
esbuild.config.js Normal file
View 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
View 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
View file

@ -0,0 +1,5 @@
module.exports = {
testEnvironment: 'node',
testMatch: ['**/tests/**/*.test.js'],
verbose: true,
};

3
lint-staged.config.js Normal file
View file

@ -0,0 +1,3 @@
module.exports = {
"src/**/*.js": "eslint --cache --fix"
};

4
modInfo.json Normal file
View file

@ -0,0 +1,4 @@
{
"name": "MERNMail integration for SVR.JS",
"version": "0.0.0"
}

3611
package-lock.json generated Normal file

File diff suppressed because it is too large Load diff

31
package.json Normal file
View 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
View 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
View 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;