#!/usr/bin/env node import assert from "node:assert/strict"; import fs from "node:fs/promises"; import path from "node:path"; import url from "node:url"; import timers from "node:timers/promises"; import os from "node:os"; import * as commander from "commander"; import express from "express"; import nodemailer from "nodemailer"; import sql, { Database } from "@leafac/sqlite"; import html, { HTML } from "@leafac/html"; import css, { localCSS } from "@leafac/css"; import javascript, { localJavaScript } from "@leafac/javascript"; import lodash from "lodash"; import { execa, ExecaChildProcess } from "execa"; import caddyfile from "dedent"; import dedent from "dedent"; if (process.env.TEST === "kill-the-newsletter") { delete process.env.TEST; // import { jest, test, expect } from "@jest/globals"; // import os from "os"; // import path from "path"; // import fs from "fs"; // import * as got from "got"; // import nodemailer from "nodemailer"; // import html from "@leafac/html"; // import killTheNewsletter from "."; // jest.setTimeout(300_000); // test("Kill the Newsletter!", async () => { // // Start servers // const rootDirectory = fs.mkdtempSync( // path.join(os.tmpdir(), "kill-the-newsletter--test--") // ); // const { webApplication, emailApplication } = killTheNewsletter(rootDirectory); // const webServer = webApplication.listen( // new URL(webApplication.get("url")).port // ); // const emailServer = emailApplication.listen( // new URL(webApplication.get("email")).port // ); // const webClient = got.default.extend({ // prefixUrl: webApplication.get("url"), // }); // const emailClient = nodemailer.createTransport(webApplication.get("email")); // const emailHostname = new URL(webApplication.get("url")).hostname; // // Create feed // const create = (await webClient.post("", { form: { name: "A newsletter" } })) // .body; // expect(create).toMatch(`“A newsletter” inbox created`); // const feedReference = create.match(/\/feeds\/([a-z0-9]{16})\.xml/)![1]; // // Test feed properties // const feedOriginal = await webClient.get(`feeds/${feedReference}.xml`); // expect(feedOriginal.headers["content-type"]).toMatch("application/atom+xml"); // expect(feedOriginal.headers["x-robots-tag"]).toBe("noindex"); // expect(feedOriginal.body).toMatch(html`
Some HTML
`, // }); // const feedWithHTMLEntry = (await webClient.get(`feeds/${feedReference}.xml`)) // .body; // expect(feedWithHTMLEntry.match(/Some HTML
`}\nA link: https://kill-the-newsletter.com
`}Lorem ipsum dolor sit amet, consectetur adipiscing elit. Aenean dictum dui quis magna mollis, vel interdum felis consectetur.
Lorem ipsum dolor sit amet, consectetur adipiscing elit. Aenean dictum dui quis magna mollis, vel interdum felis consectetur.
Lorem ipsum dolor sit amet, consectetur adipiscing elit. Aenean dictum dui quis magna mollis, vel interdum felis consectetur.
Lorem ipsum dolor sit amet, consectetur adipiscing elit. Aenean dictum dui quis magna mollis, vel interdum felis consectetur.
Lorem ipsum dolor sit amet, consectetur adipiscing elit. Aenean dictum dui quis magna mollis, vel interdum felis consectetur.
Lorem ipsum dolor sit amet, consectetur adipiscing elit. Aenean dictum dui quis magna mollis, vel interdum felis consectetur.
Lorem ipsum dolor sit amet, consectetur adipiscing elit. Aenean dictum dui quis magna mollis, vel interdum felis consectetur.
Lorem ipsum dolor sit amet, consectetur adipiscing elit. Aenean dictum dui quis magna mollis, vel interdum felis consectetur.
Lorem ipsum dolor sit amet, consectetur adipiscing elit. Aenean dictum dui quis magna mollis, vel interdum felis consectetur.
Lorem ipsum dolor sit amet, consectetur adipiscing elit. Aenean dictum dui quis magna mollis, vel interdum felis consectetur.
Lorem ipsum dolor sit amet, consectetur adipiscing elit. Aenean dictum dui quis magna mollis, vel interdum felis consectetur.
`, }) ); } ); // #!/usr/bin/env node // import path from "path"; // import express from "express"; // import { SMTPServer } from "smtp-server"; // import mailparser from "mailparser"; // import fs from "fs-extra"; // import cryptoRandomString from "crypto-random-string"; // import { html, HTML } from "@leafac/html"; // import { css, process as processCSS } from "@leafac/css"; // import javascript from "tagged-template-noop"; // import { sql, Database } from "@leafac/sqlite"; // import databaseMigrate from "@leafac/sqlite-migration"; // 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", "smtp://localhost:2525"); // webApplication.set("administrator", "mailto:kill-the-newsletter@leafac.com"); // fs.ensureDirSync(rootDirectory); // const database = new Database( // path.join(rootDirectory, "kill-the-newsletter.db") // ); // databaseMigrate(database, [ // ]); // webApplication.use(express.static(path.join(__dirname, "../public"))); // webApplication.use(express.urlencoded({ extended: true })); // const logo = fs.readFileSync(path.join(__dirname, "../public/logo.svg")); // function layout(body: HTML): HTML { // return processCSS(html` // // // // // // // //Convert email newsletters into Atom feeds
//// $${logo} //
//// Error: Missing newsletter name. // Try again. //
// ` // ) // ); // const feedReference = newReference(); // const welcomeTitle = `“${req.body.name}” inbox created`; // const welcomeContent = html` //
// Sign up for the newsletter with
// ${feedReference}@${new URL(webApplication.get("email"))
// .hostname}
//
// Subscribe to the Atom feed at
// ${webApplication.get("url")}/feeds/${feedReference}.xml
//
// Don’t share these addresses.
// They contain an identifier that other people could use to send you spam
// and to control your newsletter subscriptions.
//
Enjoy your readings!
//// Create another inbox //
// `; // database.executeTransaction(() => { // const feedId = database.run( // sql`INSERT INTO "feeds" ("reference", "title") VALUES (${feedReference}, ${req.body.name})` // ).lastInsertRowid; // database.run( // sql` // INSERT INTO "entries" ("reference", "feed", "title", "author", "content") // VALUES ( // ${newReference()}, // ${feedId}, // ${welcomeTitle}, // ${"Kill the Newsletter!"}, // ${welcomeContent} // ) // ` // ); // }); // res.send( // layout(html` //${welcomeTitle}
// $${welcomeContent} // `) // ); // }); // function renderFeed(feedReference: string): HTML | undefined { // const feed = database.get<{ // id: number; // updatedAt: string; // title: string; // }>( // sql`SELECT "id", "updatedAt", "title" FROM "feeds" WHERE "reference" = ${feedReference}` // ); // if (feed === undefined) return; // const entries = database.all<{ // createdAt: string; // reference: string; // title: string; // author: string; // content: string; // }>( // sql` // SELECT "createdAt", "reference", "title", "author", "content" // FROM "entries" // WHERE "feed" = ${feed.id} // ORDER BY "id" DESC // ` // ); // return html` // //404 Not found
//// Create a new inbox //
// `) // ); // }); // const emailApplication = new SMTPServer({ // disabledCommands: ["AUTH", "STARTTLS"], // async onData(stream, session, callback) { // try { // const email = await mailparser.simpleParser(stream); // const from = email.from?.text ?? ""; // const subject = email.subject ?? ""; // const body = // typeof email.html === "string" ? email.html : email.textAsHtml ?? ""; // database.executeTransaction(() => { // for (const address of new Set( // session.envelope.rcptTo.map( // (smtpServerAddress) => smtpServerAddress.address // ) // )) { // const addressParts = address.split("@"); // if (addressParts.length !== 2) continue; // const [feedReference, hostname] = addressParts; // if (hostname !== new URL(webApplication.get("email")).hostname) // continue; // const feed = database.get<{ id: number }>( // sql`SELECT "id" FROM "feeds" WHERE "reference" = ${feedReference}` // ); // if (feed === undefined) continue; // database.run( // sql` // INSERT INTO "entries" ("reference", "feed", "title", "author", "content") // VALUES ( // ${newReference()}, // ${feed.id}, // ${subject}, // ${from}, // ${body} // ) // ` // ); // database.run( // sql`UPDATE "feeds" SET "updatedAt" = CURRENT_TIMESTAMP WHERE "id" = ${feed.id}` // ); // while (renderFeed(feedReference)!.length > 500_000) // database.run( // sql`DELETE FROM "entries" WHERE "feed" = ${feed.id} ORDER BY "id" ASC LIMIT 1` // ); // } // }); // callback(); // } catch (error) { // console.error( // `Failed to receive message: ‘${JSON.stringify(session, null, 2)}’` // ); // console.error(error); // stream.resume(); // callback(new Error("Failed to receive message. Please try again.")); // } // }, // }); // function newReference(): string { // return cryptoRandomString({ // length: 16, // characters: "abcdefghijklmnopqrstuvwxyz0123456789", // }); // } // return { webApplication, emailApplication }; // } // if (require.main === module) { // console.log(`Kill the Newsletter!/${VERSION}`); // if (process.argv[2] === undefined) { // const { webApplication, emailApplication } = killTheNewsletter( // path.join(process.cwd(), "data") // ); // webApplication.listen(new URL(webApplication.get("url")).port, () => { // console.log(`Web server started at ${webApplication.get("url")}`); // }); // emailApplication.listen(new URL(webApplication.get("email")).port, () => { // console.log(`Email server started at ${webApplication.get("email")}`); // }); // } else { // const configurationFile = path.resolve(process.argv[2]); // require(configurationFile)(require); // console.log(`Configuration loaded from ‘${configurationFile}’.`); // } // } switch (application.process.type) { case "main": { const childProcesses = new Set