diff --git a/configuration.local.js b/configuration.local.js new file mode 100644 index 0000000..a0b7dfb --- /dev/null +++ b/configuration.local.js @@ -0,0 +1,14 @@ +module.exports = (require) => { + const killTheNewsletter = require(".").default; + const { webApplication, emailApplication } = killTheNewsletter(); + + webApplication.listen(new URL(webApplication.get("url")).port, () => { + console.log(`Web server started at ${webApplication.get("url")}`); + }); + + emailApplication.listen(webApplication.get("email port"), () => { + console.log( + `Email server started on port ${webApplication.get("email port")}` + ); + }); +}; diff --git a/package-lock.json b/package-lock.json index a69fd67..177966b 100644 --- a/package-lock.json +++ b/package-lock.json @@ -16,6 +16,7 @@ "crypto-random-string": "^3.3.1", "escape-string-regexp": "^4.0.0", "express": "^4.17.1", + "fs-extra": "^9.1.0", "mailparser": "^3.1.0", "smtp-server": "^3.8.0" }, @@ -24,6 +25,7 @@ }, "devDependencies": { "@types/express": "^4.17.11", + "@types/fs-extra": "^9.0.8", "@types/jest": "^26.0.20", "@types/mailparser": "^3.0.2", "@types/node": "^14.14.33", @@ -1072,6 +1074,15 @@ "@types/range-parser": "*" } }, + "node_modules/@types/fs-extra": { + "version": "9.0.8", + "resolved": "https://registry.npmjs.org/@types/fs-extra/-/fs-extra-9.0.8.tgz", + "integrity": "sha512-bnlTVTwq03Na7DpWxFJ1dvnORob+Otb8xHyUqUWhqvz/Ksg8+JXPlR52oeMSZ37YEOa5PyccbgUNutiQdi13TA==", + "dev": true, + "dependencies": { + "@types/node": "*" + } + }, "node_modules/@types/graceful-fs": { "version": "4.1.5", "resolved": "https://registry.npmjs.org/@types/graceful-fs/-/graceful-fs-4.1.5.tgz", @@ -1557,7 +1568,6 @@ "version": "1.0.0", "resolved": "https://registry.npmjs.org/at-least-node/-/at-least-node-1.0.0.tgz", "integrity": "sha512-+q/t7Ekv1EDY2l6Gda6LLiX14rU9TV20Wa3ofeQmwPFZbOMo9DXrLbOjFaaclkXKWidIaopwAObQDqwWtGUjqg==", - "dev": true, "engines": { "node": ">= 4.0.0" } @@ -3481,7 +3491,6 @@ "version": "9.1.0", "resolved": "https://registry.npmjs.org/fs-extra/-/fs-extra-9.1.0.tgz", "integrity": "sha512-hcg3ZmepS30/7BSFqRvoo3DOMQu7IjqxO5nCDt+zM9XWjb33Wg7ziNT+Qvqbuc3+gWpzO02JubVyk2G4Zvo1OQ==", - "dev": true, "dependencies": { "at-least-node": "^1.0.0", "graceful-fs": "^4.2.0", @@ -3496,7 +3505,6 @@ "version": "2.0.0", "resolved": "https://registry.npmjs.org/universalify/-/universalify-2.0.0.tgz", "integrity": "sha512-hAZsKq7Yy11Zu1DE0OzWjw7nnLZmJZYTDZZyEFHZdUhV8FkH5MCfoU1XMaxXovpyW5nq5scPqq0ZDP9Zyl04oQ==", - "dev": true, "engines": { "node": ">= 10.0.0" } @@ -3722,8 +3730,7 @@ "node_modules/graceful-fs": { "version": "4.2.6", "resolved": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-4.2.6.tgz", - "integrity": "sha512-nTnJ528pbqxYanhpDYsi4Rd8MAeaBA67+RZ10CM1m3bTAVFEDcd5AuA4a6W5YkGZ1iNXHzZz8T6TBKLeBuNriQ==", - "dev": true + "integrity": "sha512-nTnJ528pbqxYanhpDYsi4Rd8MAeaBA67+RZ10CM1m3bTAVFEDcd5AuA4a6W5YkGZ1iNXHzZz8T6TBKLeBuNriQ==" }, "node_modules/growly": { "version": "1.3.0", @@ -5116,7 +5123,6 @@ "version": "6.1.0", "resolved": "https://registry.npmjs.org/jsonfile/-/jsonfile-6.1.0.tgz", "integrity": "sha512-5dgndWOriYSm5cnYaJNhalLNDKOqFwyDB/rr1E9ZsGciGvKPs8R2xYGCacuf3z6K1YKDz182fd+fY3cn3pMqXQ==", - "dev": true, "dependencies": { "universalify": "^2.0.0" }, @@ -5128,7 +5134,6 @@ "version": "2.0.0", "resolved": "https://registry.npmjs.org/universalify/-/universalify-2.0.0.tgz", "integrity": "sha512-hAZsKq7Yy11Zu1DE0OzWjw7nnLZmJZYTDZZyEFHZdUhV8FkH5MCfoU1XMaxXovpyW5nq5scPqq0ZDP9Zyl04oQ==", - "dev": true, "engines": { "node": ">= 10.0.0" } @@ -9894,6 +9899,15 @@ "@types/range-parser": "*" } }, + "@types/fs-extra": { + "version": "9.0.8", + "resolved": "https://registry.npmjs.org/@types/fs-extra/-/fs-extra-9.0.8.tgz", + "integrity": "sha512-bnlTVTwq03Na7DpWxFJ1dvnORob+Otb8xHyUqUWhqvz/Ksg8+JXPlR52oeMSZ37YEOa5PyccbgUNutiQdi13TA==", + "dev": true, + "requires": { + "@types/node": "*" + } + }, "@types/graceful-fs": { "version": "4.1.5", "resolved": "https://registry.npmjs.org/@types/graceful-fs/-/graceful-fs-4.1.5.tgz", @@ -10308,8 +10322,7 @@ "at-least-node": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/at-least-node/-/at-least-node-1.0.0.tgz", - "integrity": "sha512-+q/t7Ekv1EDY2l6Gda6LLiX14rU9TV20Wa3ofeQmwPFZbOMo9DXrLbOjFaaclkXKWidIaopwAObQDqwWtGUjqg==", - "dev": true + "integrity": "sha512-+q/t7Ekv1EDY2l6Gda6LLiX14rU9TV20Wa3ofeQmwPFZbOMo9DXrLbOjFaaclkXKWidIaopwAObQDqwWtGUjqg==" }, "atob": { "version": "2.1.2", @@ -11793,7 +11806,6 @@ "version": "9.1.0", "resolved": "https://registry.npmjs.org/fs-extra/-/fs-extra-9.1.0.tgz", "integrity": "sha512-hcg3ZmepS30/7BSFqRvoo3DOMQu7IjqxO5nCDt+zM9XWjb33Wg7ziNT+Qvqbuc3+gWpzO02JubVyk2G4Zvo1OQ==", - "dev": true, "requires": { "at-least-node": "^1.0.0", "graceful-fs": "^4.2.0", @@ -11804,8 +11816,7 @@ "universalify": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/universalify/-/universalify-2.0.0.tgz", - "integrity": "sha512-hAZsKq7Yy11Zu1DE0OzWjw7nnLZmJZYTDZZyEFHZdUhV8FkH5MCfoU1XMaxXovpyW5nq5scPqq0ZDP9Zyl04oQ==", - "dev": true + "integrity": "sha512-hAZsKq7Yy11Zu1DE0OzWjw7nnLZmJZYTDZZyEFHZdUhV8FkH5MCfoU1XMaxXovpyW5nq5scPqq0ZDP9Zyl04oQ==" } } }, @@ -11971,8 +11982,7 @@ "graceful-fs": { "version": "4.2.6", "resolved": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-4.2.6.tgz", - "integrity": "sha512-nTnJ528pbqxYanhpDYsi4Rd8MAeaBA67+RZ10CM1m3bTAVFEDcd5AuA4a6W5YkGZ1iNXHzZz8T6TBKLeBuNriQ==", - "dev": true + "integrity": "sha512-nTnJ528pbqxYanhpDYsi4Rd8MAeaBA67+RZ10CM1m3bTAVFEDcd5AuA4a6W5YkGZ1iNXHzZz8T6TBKLeBuNriQ==" }, "growly": { "version": "1.3.0", @@ -13045,7 +13055,6 @@ "version": "6.1.0", "resolved": "https://registry.npmjs.org/jsonfile/-/jsonfile-6.1.0.tgz", "integrity": "sha512-5dgndWOriYSm5cnYaJNhalLNDKOqFwyDB/rr1E9ZsGciGvKPs8R2xYGCacuf3z6K1YKDz182fd+fY3cn3pMqXQ==", - "dev": true, "requires": { "graceful-fs": "^4.1.6", "universalify": "^2.0.0" @@ -13054,8 +13063,7 @@ "universalify": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/universalify/-/universalify-2.0.0.tgz", - "integrity": "sha512-hAZsKq7Yy11Zu1DE0OzWjw7nnLZmJZYTDZZyEFHZdUhV8FkH5MCfoU1XMaxXovpyW5nq5scPqq0ZDP9Zyl04oQ==", - "dev": true + "integrity": "sha512-hAZsKq7Yy11Zu1DE0OzWjw7nnLZmJZYTDZZyEFHZdUhV8FkH5MCfoU1XMaxXovpyW5nq5scPqq0ZDP9Zyl04oQ==" } } }, diff --git a/package.json b/package.json index 5262bd6..f1c745c 100644 --- a/package.json +++ b/package.json @@ -16,7 +16,7 @@ "bugs": "https://github.com/leafac/kill-the-newsletter/issues", "homepage": "https://github.com/leafac/kill-the-newsletter#readme", "scripts": { - "start": "ts-node-dev --poll src/index.ts", + "start": "ts-node-dev --poll src/index.ts configuration.local.js", "test": "prettier --check \"src/**/*\" --end-of-line auto && jest", "prepare": "tsc" }, @@ -28,11 +28,13 @@ "crypto-random-string": "^3.3.1", "escape-string-regexp": "^4.0.0", "express": "^4.17.1", + "fs-extra": "^9.1.0", "mailparser": "^3.1.0", "smtp-server": "^3.8.0" }, "devDependencies": { "@types/express": "^4.17.11", + "@types/fs-extra": "^9.0.8", "@types/jest": "^26.0.20", "@types/mailparser": "^3.0.2", "@types/node": "^14.14.33", diff --git a/src/index.ts b/src/index.ts index 2949c9c..1547328 100644 --- a/src/index.ts +++ b/src/index.ts @@ -1,22 +1,41 @@ +import path from "path"; import express from "express"; import { SMTPServer } from "smtp-server"; import mailparser from "mailparser"; -import * as sanitizeXMLString from "sanitize-xml-string"; -import * as entities from "entities"; -import R from "escape-string-regexp"; -import { JSDOM } from "jsdom"; -import { promises as fs } from "fs"; -import writeFileAtomic from "write-file-atomic"; +import escapeStringRegexp from "escape-string-regexp"; +import fs from "fs-extra"; import cryptoRandomString from "crypto-random-string"; -import html from "tagged-template-noop"; -export const WEB_PORT = process.env.WEB_PORT ?? 8000; -export const EMAIL_PORT = process.env.EMAIL_PORT ?? 2525; -export const BASE_URL = process.env.BASE_URL ?? "http://localhost:8000"; -export const EMAIL_DOMAIN = process.env.EMAIL_DOMAIN ?? "localhost"; -export const ISSUE_REPORT = - process.env.ISSUE_REPORT ?? "mailto:kill-the-newsletter@leafac.com"; +const VERSION = require("../package.json").version; +export default function killTheNewsletter( + rootDirectory: string +): { webApplication: express.Express; emailApplication: SMTPServer } { + const webApplication = express(); + + webApplication.set("url", "http://localhost:4000"); + webApplication.set("email port", 2525); + webApplication.set("email host", "localhost"); + webApplication.set("administrator", "kill-the-newsletter@leafac.com"); + + const emailApplication = new SMTPServer(); + + return { webApplication, emailApplication }; +} + +if (require.main === module) { + console.log(`Kill the Newsletter!/${VERSION}`); + + const configurationFile = path.resolve( + process.argv[2] ?? path.join(process.cwd(), "configuration.js") + ); + + require(configurationFile)(require); + + console.log(`Configuration file loaded from ‘${configurationFile}’.`); +} + +/* export const webServer = express() .use(["/feeds", "/alternate"], (req, res, next) => { res.header("X-Robots-Tag", "noindex"); @@ -97,7 +116,9 @@ export const emailServer = new SMTPServer({ session.envelope.rcptTo.map(({ address }) => address) )) { const match = address.match( - new RegExp(`^(?\\w+)@${R(EMAIL_DOMAIN)}$`) + new RegExp( + `^(?\\w+)@${escapeStringRegexp(EMAIL_DOMAIN)}$` + ) ); if (match?.groups === undefined) continue; const identifier = match.groups.identifier.toLowerCase(); @@ -340,3 +361,4 @@ function X(string: string): string { function H(string: string): string { return entities.encodeHTML(sanitizeXMLString.sanitize(string)); } +*/