diff --git a/.prettierrc b/.prettierrc index 544138b..b643436 100644 --- a/.prettierrc +++ b/.prettierrc @@ -1,3 +1,9 @@ { - "singleQuote": true + "singleQuote": false, + "trailingComma": "all", + "useTabs": false, + "tabWidth": 4, + "printWidth": 120, + "importOrderSeparation": true, + "importOrder": ["", "^[./]"] } diff --git a/.vscode/launch.json b/.vscode/launch.json new file mode 100644 index 0000000..8b718ec --- /dev/null +++ b/.vscode/launch.json @@ -0,0 +1,29 @@ +{ + // Use IntelliSense to learn about possible attributes. + // Hover to view descriptions of existing attributes. + // For more information, visit: https://go.microsoft.com/fwlink/?linkid=830387 + "version": "0.2.0", + "configurations": [ + + { + "type": "node", + "request": "launch", + "name": "Launch mail merge cli", + "skipFiles": [ + "/**" + ], + "program": "${workspaceFolder}/packages/email/docsoc-mail-merge/src/cli.ts", + "cwd": "${workspaceFolder}/packages/email/docsoc-mail-merge", + "outFiles": [ + "${workspaceFolder}/packages/email/docsoc-mail-merge/dist/**/*.js", + "${workspaceFolder}/packages/email/docsoc-mail-merge/dist/**/*.js.map" + ], + "preLaunchTask": "nx: nx run docsoc-mail-merge:build", + "sourceMaps": true, + "sourceMapPathOverrides": { + "../../../../../packages/email/docsoc-mail-merge/**/*": "${workspaceFolder}/dist/packages/email/docsoc-mail-merge/**/*", + }, + "console": "integratedTerminal" + } + ] +} \ No newline at end of file diff --git a/.vscode/settings.json b/.vscode/settings.json index a0dd9cd..b9d7f71 100644 --- a/.vscode/settings.json +++ b/.vscode/settings.json @@ -13,5 +13,8 @@ ], "files.watcherExclude": { "**/target": true - } + }, + "editor.tabSize": 4, + "editor.insertSpaces": true, + "editor.detectIndentation": false } \ No newline at end of file diff --git a/.vscode/tasks.json b/.vscode/tasks.json new file mode 100644 index 0000000..7b2b80a --- /dev/null +++ b/.vscode/tasks.json @@ -0,0 +1,14 @@ +{ + "version": "2.0.0", + "tasks": [ + { + "type": "nx", + "project": "", + "command": "run", + "positional": "docsoc-mail-merge:build", + "flags": [], + "problemMatcher": [], + "label": "nx: nx run docsoc-mail-merge:build" + } + ] +} \ No newline at end of file diff --git a/package-lock.json b/package-lock.json index a80dd2d..21d3593 100644 --- a/package-lock.json +++ b/package-lock.json @@ -29,6 +29,7 @@ "@swc-node/register": "~1.9.1", "@swc/core": "~1.5.7", "@swc/helpers": "~0.5.11", + "@trivago/prettier-plugin-sort-imports": "^4.3.0", "@types/jest": "^29.4.0", "@types/node": "~18.16.9", "@typescript-eslint/eslint-plugin": "^7.3.0", @@ -4444,6 +4445,115 @@ "@swc/counter": "^0.1.3" } }, + "node_modules/@trivago/prettier-plugin-sort-imports": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/@trivago/prettier-plugin-sort-imports/-/prettier-plugin-sort-imports-4.3.0.tgz", + "integrity": "sha512-r3n0onD3BTOVUNPhR4lhVK4/pABGpbA7bW3eumZnYdKaHkf1qEC+Mag6DPbGNuuh0eG8AaYj+YqmVHSiGslaTQ==", + "dev": true, + "dependencies": { + "@babel/generator": "7.17.7", + "@babel/parser": "^7.20.5", + "@babel/traverse": "7.23.2", + "@babel/types": "7.17.0", + "javascript-natural-sort": "0.7.1", + "lodash": "^4.17.21" + }, + "peerDependencies": { + "@vue/compiler-sfc": "3.x", + "prettier": "2.x - 3.x" + }, + "peerDependenciesMeta": { + "@vue/compiler-sfc": { + "optional": true + } + } + }, + "node_modules/@trivago/prettier-plugin-sort-imports/node_modules/@babel/generator": { + "version": "7.17.7", + "resolved": "https://registry.npmjs.org/@babel/generator/-/generator-7.17.7.tgz", + "integrity": "sha512-oLcVCTeIFadUoArDTwpluncplrYBmTCCZZgXCbgNGvOBBiSDDK3eWO4b/+eOTli5tKv1lg+a5/NAXg+nTcei1w==", + "dev": true, + "dependencies": { + "@babel/types": "^7.17.0", + "jsesc": "^2.5.1", + "source-map": "^0.5.0" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@trivago/prettier-plugin-sort-imports/node_modules/@babel/traverse": { + "version": "7.23.2", + "resolved": "https://registry.npmjs.org/@babel/traverse/-/traverse-7.23.2.tgz", + "integrity": "sha512-azpe59SQ48qG6nu2CzcMLbxUudtN+dOM9kDbUqGq3HXUJRlo7i8fvPoxQUzYgLZ4cMVmuZgm8vvBpNeRhd6XSw==", + "dev": true, + "dependencies": { + "@babel/code-frame": "^7.22.13", + "@babel/generator": "^7.23.0", + "@babel/helper-environment-visitor": "^7.22.20", + "@babel/helper-function-name": "^7.23.0", + "@babel/helper-hoist-variables": "^7.22.5", + "@babel/helper-split-export-declaration": "^7.22.6", + "@babel/parser": "^7.23.0", + "@babel/types": "^7.23.0", + "debug": "^4.1.0", + "globals": "^11.1.0" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@trivago/prettier-plugin-sort-imports/node_modules/@babel/traverse/node_modules/@babel/generator": { + "version": "7.24.10", + "resolved": "https://registry.npmjs.org/@babel/generator/-/generator-7.24.10.tgz", + "integrity": "sha512-o9HBZL1G2129luEUlG1hB4N/nlYNWHnpwlND9eOMclRqqu1YDy2sSYVCFUZwl8I1Gxh+QSRrP2vD7EpUmFVXxg==", + "dev": true, + "dependencies": { + "@babel/types": "^7.24.9", + "@jridgewell/gen-mapping": "^0.3.5", + "@jridgewell/trace-mapping": "^0.3.25", + "jsesc": "^2.5.1" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@trivago/prettier-plugin-sort-imports/node_modules/@babel/traverse/node_modules/@babel/types": { + "version": "7.24.9", + "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.24.9.tgz", + "integrity": "sha512-xm8XrMKz0IlUdocVbYJe0Z9xEgidU7msskG8BbhnTPK/HZ2z/7FP7ykqPgrUH+C+r414mNfNWam1f2vqOjqjYQ==", + "dev": true, + "dependencies": { + "@babel/helper-string-parser": "^7.24.8", + "@babel/helper-validator-identifier": "^7.24.7", + "to-fast-properties": "^2.0.0" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@trivago/prettier-plugin-sort-imports/node_modules/@babel/types": { + "version": "7.17.0", + "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.17.0.tgz", + "integrity": "sha512-TmKSNO4D5rzhL5bjWFcVHHLETzfQ/AmbKpKPOSjlP0WoHZ6L911fgoOKY4Alp/emzG4cHJdyN49zpgkbXFEHHw==", + "dev": true, + "dependencies": { + "@babel/helper-validator-identifier": "^7.16.7", + "to-fast-properties": "^2.0.0" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@trivago/prettier-plugin-sort-imports/node_modules/source-map": { + "version": "0.5.7", + "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.5.7.tgz", + "integrity": "sha512-LbrmJOMUSdEVxIKvdcJzQC+nQhe8FUZQTXQy6+I75skNgn3OoQ0DZA8YnFa7gp8tqtL3KPf1kmo0R5DoApeSGQ==", + "dev": true, + "engines": { + "node": ">=0.10.0" + } + }, "node_modules/@tsconfig/node10": { "version": "1.0.11", "resolved": "https://registry.npmjs.org/@tsconfig/node10/-/node10-1.0.11.tgz", @@ -7401,6 +7511,12 @@ "node": "*" } }, + "node_modules/javascript-natural-sort": { + "version": "0.7.1", + "resolved": "https://registry.npmjs.org/javascript-natural-sort/-/javascript-natural-sort-0.7.1.tgz", + "integrity": "sha512-nO6jcEfZWQXDhOiBtG2KvKyEptz7RVbpGP4vTD2hLBdmNQSsCiicO2Ioinv6UI4y9ukqnBpy+XZ9H6uLNgJTlw==", + "dev": true + }, "node_modules/jest": { "version": "29.7.0", "resolved": "https://registry.npmjs.org/jest/-/jest-29.7.0.tgz", @@ -8209,6 +8325,12 @@ "node": ">=8" } }, + "node_modules/lodash": { + "version": "4.17.21", + "resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.21.tgz", + "integrity": "sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg==", + "dev": true + }, "node_modules/lodash.debounce": { "version": "4.0.8", "resolved": "https://registry.npmjs.org/lodash.debounce/-/lodash.debounce-4.0.8.tgz", diff --git a/package.json b/package.json index 704da16..cb50f72 100644 --- a/package.json +++ b/package.json @@ -17,6 +17,7 @@ "@swc-node/register": "~1.9.1", "@swc/core": "~1.5.7", "@swc/helpers": "~0.5.11", + "@trivago/prettier-plugin-sort-imports": "^4.3.0", "@types/jest": "^29.4.0", "@types/node": "~18.16.9", "@typescript-eslint/eslint-plugin": "^7.3.0", diff --git a/packages/email/docsoc-mail-merge/project.json b/packages/email/docsoc-mail-merge/project.json index 314df81..360afb9 100644 --- a/packages/email/docsoc-mail-merge/project.json +++ b/packages/email/docsoc-mail-merge/project.json @@ -2,17 +2,18 @@ "name": "docsoc-mail-merge", "$schema": "../../../node_modules/nx/schemas/project-schema.json", "sourceRoot": "packages/email/docsoc-mail-merge/src", - "projectType": "library", + "projectType": "application", "tags": [], "targets": { "build": { "executor": "@nx/js:tsc", "outputs": ["{options.outputPath}"], "options": { - "outputPath": "dist/packages/email/docsoc-mail-merge", - "main": "packages/email/docsoc-mail-merge/src/index.ts", + "outputPath": "packages/email/docsoc-mail-merge/dist", + "main": "packages/email/docsoc-mail-merge/src/cli.ts", "tsConfig": "packages/email/docsoc-mail-merge/tsconfig.lib.json", - "assets": ["packages/email/docsoc-mail-merge/*.md"] + "assets": [], + "rootDir": "packages/email/docsoc-mail-merge/src" } }, "run": { diff --git a/packages/email/docsoc-mail-merge/src/cli-testing.ts b/packages/email/docsoc-mail-merge/src/cli-testing.ts new file mode 100644 index 0000000..e054d03 --- /dev/null +++ b/packages/email/docsoc-mail-merge/src/cli-testing.ts @@ -0,0 +1,49 @@ +import Mailer from "./mailer/mailer"; +import createLogger from "./util/logger"; + +import { promises as fs } from "fs"; +import nunjucks from "nunjucks"; +import markdownit from "markdown-it"; + +import "dotenv/config"; // load .env +import { join } from "path"; +import { renderMarkdownTemplate, renderMarkdownToHtml } from "./markdown/template"; +import { parse } from "csv-parse"; +import { defaultMailer, getDefaultMailer } from "./mailer/defaultMailer"; + +const logger = createLogger("docsoc"); + +async function main() { + logger.info("Starting DoCSoc Mail Merge"); + logger.info("Loading template..."); + + const template = await fs.readFile(join(__dirname, "../templates/TEMPLATE.md.njk"), "utf-8"); + const csv = await fs.readFile(join(__dirname, "../data/names.csv"), "utf-8"); + const templateCompiled = nunjucks.compile( + template, + nunjucks.configure({ + throwOnUndefined: true, + }), + ); + const htmlWrapper = await fs.readFile(join(__dirname, "../templates/wrapper.html.njk"), "utf-8"); + const htmlWrapperCompiled = nunjucks.compile(htmlWrapper, nunjucks.configure({ autoescape: false })); + const mailer = getDefaultMailer(); + // read the params from the nunjucks template + + const csvData = parse(csv, { columns: true }); + for await (const record of csvData) { + const expanded = renderMarkdownTemplate(templateCompiled, { + name: record["name"], + }); + const html = renderMarkdownToHtml(expanded); + + // wrap the html in the wrapper + const wrapped = htmlWrapperCompiled.render({ content: html }); + + console.log(wrapped); + + await defaultMailer([record["email"]], "DoCSoc Mail Merge Test", wrapped, mailer); + } +} + +main(); diff --git a/packages/email/docsoc-mail-merge/src/cli.ts b/packages/email/docsoc-mail-merge/src/cli.ts index 904192f..1cb5db3 100644 --- a/packages/email/docsoc-mail-merge/src/cli.ts +++ b/packages/email/docsoc-mail-merge/src/cli.ts @@ -1,49 +1,44 @@ -import Mailer from './mailer/mailer'; -import createLogger from './util/logger'; - -import { promises as fs } from 'fs'; -import nunjucks from 'nunjucks'; -import markdownit from 'markdown-it'; - -import 'dotenv/config'; // load .env -import { join } from "path"; -import { renderMarkdownTemplate, renderMarkdownToHtml } from "./markdown/template"; import { parse } from "csv-parse"; -import { defaultMailer, getDefaultMailer } from "./mailer/defaultMailer"; - -const logger = createLogger('docsoc'); - - -async function main() { - logger.info('Starting DoCSoc Mail Merge'); - logger.info("Loading template...") - - const template = await fs.readFile(join(__dirname, '../templates/TEMPLATE.md.njk'), 'utf-8'); - const csv = await fs.readFile(join(__dirname, '../data/names.csv'), 'utf-8'); - const templateCompiled = nunjucks.compile(template, nunjucks.configure({ - throwOnUndefined: true, - })); - const htmlWrapper = await fs.readFile(join(__dirname, '../templates/wrapper.html.njk'), 'utf-8'); - const htmlWrapperCompiled = nunjucks.compile(htmlWrapper, nunjucks.configure({autoescape: false})); - const mailer = getDefaultMailer(); - // read the params from the nunjucks template - - const csvData = parse(csv, {columns: true}); - for await (const record of csvData) { - - const expanded = renderMarkdownTemplate(templateCompiled, { - name: record["name"] - }) - const html = renderMarkdownToHtml(expanded); - - // wrap the html in the wrapper - const wrapped = htmlWrapperCompiled.render({content: html}); - - console.log(wrapped) - - await defaultMailer([record["email"]], "DoCSoc Mail Merge Test", wrapped, mailer); - } +import "dotenv/config"; +import { promises as fs } from "fs"; +import markdownit from "markdown-it"; +import nunjucks from "nunjucks"; +// load .env +import { join } from "path"; +import packageJSON from "../package.json"; +import createLogger from "./util/logger"; +import { CliOptions } from "./util/types"; +import { stopIfCriticalFsError } from "./util/files"; + +const logger = createLogger("docsoc"); + +const opts: CliOptions = { + csvFile: "./data/names.csv", +}; + +async function main(opts: CliOptions) { + logger.info("DoCSoc Mail Merge"); + logger.info(`v${packageJSON.version}`); + + // 1: Load the CSV + logger.info("Loading CSV..."); + const csvRaw = await stopIfCriticalFsError(fs.readFile(join(__dirname, "../data/names.csv"), "utf-8")); + logger.debug("Parsing & loading CSV..."); + const csvParsed = parse(csvRaw, { columns: true }); + const records = []; + for await (const record of csvParsed) { + records.push(record); + } + logger.info(`Loaded ${records.length} records`); + + // 3: Grab fields + if (records.length === 0) { + logger.error("No records found in CSV"); + throw new Error("No records found in CSV"); + } + const fields = Object.keys(records[0]); + logger.info(`Fields: ${fields.join(", ")}`); } -main(); +main(opts); diff --git a/packages/email/docsoc-mail-merge/src/mailer/defaultMailer.ts b/packages/email/docsoc-mail-merge/src/mailer/defaultMailer.ts index 608acda..3e6b1bf 100644 --- a/packages/email/docsoc-mail-merge/src/mailer/defaultMailer.ts +++ b/packages/email/docsoc-mail-merge/src/mailer/defaultMailer.ts @@ -4,26 +4,28 @@ import Mailer from "./mailer"; /** * Default mailer that uses the env vars `DOCSOC_SMTP_SERVER`, `DOCSOC_SMTP_USERNAME`, `DOCSOC_SMTP_PASSWORD` to create a mailer. */ -export const getDefaultMailer = () => new Mailer( - process.env['DOCSOC_SMTP_SERVER'] ?? 'smtp-mail.outlook.com', - 587, - process.env['DOCSOC_SMTP_USERNAME'] ?? 'docsoc@ic.ac.uk', - process.env['DOCSOC_SMTP_PASSWORD'] ?? 'password' -); +export const getDefaultMailer = () => + new Mailer( + process.env["DOCSOC_SMTP_SERVER"] ?? "smtp-mail.outlook.com", + 587, + process.env["DOCSOC_SMTP_USERNAME"] ?? "docsoc@ic.ac.uk", + process.env["DOCSOC_SMTP_PASSWORD"] ?? "password", + ); /** * The default mailer function for DoCSoc mail merge: sends an email to a list of recipients using the appriopritate env vars to populate fields. - * + * * Pass it an instance of a Mailer from {@link getDefaultMailer} to use the default mailer. */ -export const defaultMailer = (to: EmailString[], subject: string, html: string, mailer: Mailer): Promise => mailer.sendMail( - Mailer.makeFromLineFromEmail( - process.env['DOCSOC_SENDER_NAME'] ?? 'DoCSoc', - Mailer.validateEmail(process.env['DOCSOC_SENDER_EMAIL']) - ? process.env['DOCSOC_SENDER_EMAIL'] ?? 'docsoc@ic.ac.uk' - : 'docsoc@ic.ac.uk' - ), - to, - subject, - html, -); \ No newline at end of file +export const defaultMailer = (to: EmailString[], subject: string, html: string, mailer: Mailer): Promise => + mailer.sendMail( + Mailer.makeFromLineFromEmail( + process.env["DOCSOC_SENDER_NAME"] ?? "DoCSoc", + Mailer.validateEmail(process.env["DOCSOC_SENDER_EMAIL"]) + ? process.env["DOCSOC_SENDER_EMAIL"] ?? "docsoc@ic.ac.uk" + : "docsoc@ic.ac.uk", + ), + to, + subject, + html, + ); diff --git a/packages/email/docsoc-mail-merge/src/mailer/mailer.ts b/packages/email/docsoc-mail-merge/src/mailer/mailer.ts index 8a2d380..c14ff8c 100644 --- a/packages/email/docsoc-mail-merge/src/mailer/mailer.ts +++ b/packages/email/docsoc-mail-merge/src/mailer/mailer.ts @@ -1,66 +1,62 @@ -import nodemailer from 'nodemailer'; -import { EmailString, FromEmail } from '../util/types'; +import nodemailer from "nodemailer"; +import { EmailString, FromEmail } from "../util/types"; import { convert } from "html-to-text"; import { validate } from "email-validator"; import createLogger from "../util/logger"; -const logger = createLogger('mailer'); +const logger = createLogger("mailer"); /** * Core abstraction for sending emails: make a instance of this class, and call `sendMail` to send an email. - * + * * @param host SMTP server host, e.g. 'smtp-mail.outlook.com' * @param port SMTP server port, e.g. 587 (assumed to use TLS) * @param username SMTP server username (usually your microsoft 365 email) * @param password SMTP server password (usually your microsoft 365 password) */ export default class Mailer { - constructor( - private host: string, - private port: number, - private username: string, - private password: string, - ) {} - - private transporter = nodemailer.createTransport({ - host: this.host, - port: this.port, - secure: false, // Use `true` for port 465, `false` for all other ports - auth: { - user: this.username, - pass: this.password, - }, - }); - - async sendMail( - from: FromEmail, - to: string[], - subject: string, - html: string, - text: string = convert(html), - ): Promise { - const info = await this.transporter.sendMail({ - from, // sender address - to, // list of receivers - subject, // Subject line - text: text, // plain text body - html: html, // html body - }); - - logger.debug(`Sent email to ${to.join(', ')}, from ${from}, subject: ${subject}, message id: ${info.messageId}`); - } - - /** Helper function to check an email is a valid email address (and also tells the TS compiler we have a valid EmailString) */ - static validateEmail(email?: string): email is EmailString { - if (!email) return false; - return validate(email); - } - - - /** - * Create a FromEmail string from a name and email address (i.e. `"Name" `) - */ - static makeFromLineFromEmail(name: string, email: EmailString): FromEmail { - return `"${name}" <${email}>`; - } -} \ No newline at end of file + constructor(private host: string, private port: number, private username: string, private password: string) {} + + private transporter = nodemailer.createTransport({ + host: this.host, + port: this.port, + secure: false, // Use `true` for port 465, `false` for all other ports + auth: { + user: this.username, + pass: this.password, + }, + }); + + async sendMail( + from: FromEmail, + to: string[], + subject: string, + html: string, + text: string = convert(html), + ): Promise { + const info = await this.transporter.sendMail({ + from, // sender address + to, // list of receivers + subject, // Subject line + text: text, // plain text body + html: html, // html body + }); + + logger.debug( + `Sent email to ${to.join(", ")}, from ${from}, subject: ${subject}, message id: ${info.messageId}`, + ); + } + + /** Helper function to check an email is a valid email address (and also tells the TS compiler we have a valid EmailString) */ + static validateEmail(email?: string): email is EmailString { + if (!email) return false; + return validate(email); + } + + /** + * Create a FromEmail string from a name and email address (i.e. `"Name" `) + */ + static makeFromLineFromEmail(name: string, email: EmailString): FromEmail { + return `"${name}" <${email}>`; + } +} diff --git a/packages/email/docsoc-mail-merge/src/markdown/template.ts b/packages/email/docsoc-mail-merge/src/markdown/template.ts index 353e4e0..d8779e3 100644 --- a/packages/email/docsoc-mail-merge/src/markdown/template.ts +++ b/packages/email/docsoc-mail-merge/src/markdown/template.ts @@ -3,24 +3,23 @@ */ import { Template } from "nunjucks"; -import markdownit from 'markdown-it'; - +import markdownit from "markdown-it"; const md = markdownit({ - html: true, - linkify: true, - typographer: false, - breaks: true, -}) + html: true, + linkify: true, + typographer: false, + breaks: true, +}); /** * Render a markdown template to markdown with the fields filled */ export const renderMarkdownTemplate = (template: Template, params: Record): string => { - return template.render(params); -} + return template.render(params); +}; /** * Render a markdown string to HTML, applying any custom transformations we want */ -export const renderMarkdownToHtml = (markdown: string): string => md.render(markdown) \ No newline at end of file +export const renderMarkdownToHtml = (markdown: string): string => md.render(markdown); diff --git a/packages/email/docsoc-mail-merge/src/util/constants.ts b/packages/email/docsoc-mail-merge/src/util/constants.ts index 5723a1a..7f2928d 100644 --- a/packages/email/docsoc-mail-merge/src/util/constants.ts +++ b/packages/email/docsoc-mail-merge/src/util/constants.ts @@ -1 +1 @@ -export const DOCSOC_DEFAULT_FROM_LINE = `"DoCSoc" ` \ No newline at end of file +export const DOCSOC_DEFAULT_FROM_LINE = `"DoCSoc" `; diff --git a/packages/email/docsoc-mail-merge/src/util/files.ts b/packages/email/docsoc-mail-merge/src/util/files.ts new file mode 100644 index 0000000..69404c1 --- /dev/null +++ b/packages/email/docsoc-mail-merge/src/util/files.ts @@ -0,0 +1,31 @@ +import createLogger from "./logger"; + +const logger = createLogger("docsoc.utils"); + +function isErrnoException(e: any): e is NodeJS.ErrnoException { + return e.code !== undefined; +} + +/** + * Handles error thrown by fs.promises functions & will stop the program + * @param promise Promise returned by a fs.promises function + * @example await handleCriticalFsError(fs.readFile("file.txt", "utf-8")); + */ +export async function stopIfCriticalFsError(promise: Promise) { + try { + return await promise; + } catch (e) { + if (e instanceof Error && isErrnoException(e)) { + if (e?.code === "ENOENT") { + logger.error("File not found: ", e.message); + } else if (e?.code === "EACCES") { + logger.error("Permission denied: ", e.message); + } else { + logger.error("Critical error occurred while reading file: ", e.message); + } + } else { + logger.error("Critical error occurred while reading file: ", e); + } + process.exit(1); + } +} \ No newline at end of file diff --git a/packages/email/docsoc-mail-merge/src/util/logger.ts b/packages/email/docsoc-mail-merge/src/util/logger.ts index 902a9f2..137dae3 100644 --- a/packages/email/docsoc-mail-merge/src/util/logger.ts +++ b/packages/email/docsoc-mail-merge/src/util/logger.ts @@ -1,24 +1,25 @@ - import chalk from "chalk"; import winston from "winston"; const { combine, colorize, printf, timestamp } = winston.format; export default function createLogger(moduleName: string) { - const options: winston.LoggerOptions = { - transports: [ - new winston.transports.Console({ - format: combine( - colorize(), - timestamp(), - printf((info) => { - return `${chalk.grey(info["timestamp"])} ${chalk.magenta(moduleName)} ${info.level} ${info.message}`; - }), - ), - level: "debug", - }), - ], - }; + const options: winston.LoggerOptions = { + transports: [ + new winston.transports.Console({ + format: combine( + colorize(), + timestamp(), + printf((info) => { + return `${chalk.grey(info["timestamp"])} ${chalk.magenta(moduleName)} ${info.level} ${ + info.message + }`; + }), + ), + level: "debug", + }), + ], + }; - return winston.createLogger(options); -} \ No newline at end of file + return winston.createLogger(options); +} diff --git a/packages/email/docsoc-mail-merge/src/util/types.ts b/packages/email/docsoc-mail-merge/src/util/types.ts index 456af00..de18548 100644 --- a/packages/email/docsoc-mail-merge/src/util/types.ts +++ b/packages/email/docsoc-mail-merge/src/util/types.ts @@ -1,2 +1,6 @@ -export type EmailString = `${string}@${string}` -export type FromEmail = `"${string}" <${EmailString}>` +export type EmailString = `${string}@${string}`; +export type FromEmail = `"${string}" <${EmailString}>`; + +export interface CliOptions { + csvFile: string; +} \ No newline at end of file diff --git a/packages/email/docsoc-mail-merge/tsconfig.lib.json b/packages/email/docsoc-mail-merge/tsconfig.lib.json index 8f9c818..776b38c 100644 --- a/packages/email/docsoc-mail-merge/tsconfig.lib.json +++ b/packages/email/docsoc-mail-merge/tsconfig.lib.json @@ -1,10 +1,11 @@ { "extends": "./tsconfig.json", "compilerOptions": { - "outDir": "../../../dist/out-tsc", + "rootDir": "./src", + "outDir": "./dist", "declaration": true, "types": ["node"] }, "include": ["src/**/*.ts"], - "exclude": ["src/**/*.spec.ts", "src/**/*.test.ts"] + "exclude": ["src/**/*.spec.ts", "src/**/*.test.ts", "package.json"] } diff --git a/tsconfig.base.json b/tsconfig.base.json index 662ffb4..070599d 100644 --- a/tsconfig.base.json +++ b/tsconfig.base.json @@ -14,6 +14,8 @@ "skipLibCheck": true, "skipDefaultLibCheck": true, "baseUrl": ".", + "resolveJsonModule": true, + "esModuleInterop": true, "paths": { "docsoc-mail-merge": ["packages/email/docsoc-mail-merge/src/index.ts"] }