Skip to content

Add React Email #20

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Draft
wants to merge 4 commits into
base: main
Choose a base branch
from
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion email/mailmerge-cli/package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "@docsoc/mailmerge-cli",
"version": "1.2.0",
"version": "1.3.0-beta.1",
"publishConfig": {
"access": "public"
},
Expand Down
124 changes: 124 additions & 0 deletions email/mailmerge-cli/src/commands/generate/react.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,124 @@
import {
CSVBackend,
DataSource,
GenerateOptions,
generatePreviews,
JSONBackend,
JSONSidecarsBackend,
ReactEmailEngineOptions,
ReactEmailEngine,
} from "@docsoc/mailmerge";
import { Args, Command, Flags } from "@oclif/core";
import { mkdirp } from "mkdirp";
import { join } from "path";

import { getFileNameSchemeInteractively } from "../../interactivity/getFileNameSchemeInteractively.js";
import { getKeysForAttachments } from "../../interactivity/getKeysForAttachments.js";
import { getRunNameInteractively } from "../../interactivity/getRunNameInteractively.js";
import { mapFieldsInteractive } from "../../interactivity/mapFieldsInteractive.js";
import { DEFAULT_DIRS } from "../../util/constant.js";

export default class GenerateReact extends Command {
static override args = {
dataFile: Args.string({
description: "Path to the CSV/JSON file to use as the data source for the mailmerge",
required: true,
}),
template: Args.string({
description: "Path to the React component to use to generate emails",
required: true,
default: join(DEFAULT_DIRS.TEMPLATES, "template.md.njk"),
}),
};

static override description = "Start a mailmerge from a CSV & React template";

static override examples = ["<%= config.bin %> <%= command.id %>"];

static MODE_CSV = "csv";
static MODE_JSON = "json";

static override flags = {
// // flag with no value (-f, --force)
// force: Flags.boolean({ char: "f" }),
// // flag with a value (-n, --name=VALUE)
// name: Flags.string({ char: "n", description: "name to print" }),
sourceType: Flags.string({
char: "s",
description: "Type of data source to use (csv/json)",
options: [GenerateReact.MODE_CSV, GenerateReact.MODE_JSON],
default: "csv",
}),
output: Flags.string({
char: "o",
description: "Path to the directory to output the generated email previews to",
default: DEFAULT_DIRS.PREVIEWS,
}),
attachment: Flags.string({
multiple: true,
char: "a",
description:
"Attachments to add to the email, relative to mailmerge workspace root. Can specify multiple.",
default: [],
}),
cc: Flags.boolean({
char: "c",
description: "Enable CC mapping from CSV - column values must be a space separate list",
}),
bcc: Flags.boolean({
char: "b",
description:
"Enable BCC mapping from CSV - column values must be a space separate list",
}),
name: Flags.string({
char: "n",
description: "Name of the run, created as a subdirectory in the output directory",
}),
};

private getDataSource(
flags: Awaited<ReturnType<typeof this.parse>>["flags"],
args: Awaited<ReturnType<typeof this.parse>>["args"],
): DataSource {
switch (flags["sourceType"]) {
case GenerateReact.MODE_CSV:
return new CSVBackend(join(process.cwd(), args["dataFile"]));
case GenerateReact.MODE_JSON:
return new JSONBackend(join(process.cwd(), args["dataFile"]));
default:
throw new Error("Invalid source type");
}
}

public async run(): Promise<void> {
const { args, flags } = await this.parse(GenerateReact);
const engineOptions: ReactEmailEngineOptions = {
templatePath: args.template,
};
const runName = flags.name ?? (await getRunNameInteractively());
const outputRoot = join(flags.output, runName);
await mkdirp(outputRoot);
const options: GenerateOptions = {
engineInfo: {
options: engineOptions,
name: "react",
engine: new ReactEmailEngine(engineOptions),
},
attachments: flags.attachment,
features: {
enableBCC: flags.bcc,
enableCC: flags.cc,
},
dataSource: this.getDataSource(flags, args),
storageBackend: new JSONSidecarsBackend(outputRoot, {
type: "dynamic",
namer: getFileNameSchemeInteractively,
}),
mappings: {
headersToTemplateMap: mapFieldsInteractive,
keysForAttachments: getKeysForAttachments,
},
};
await generatePreviews(options);
}
}
6 changes: 6 additions & 0 deletions email/mailmerge-cli/src/commands/send.ts
Original file line number Diff line number Diff line change
Expand Up @@ -39,6 +39,11 @@ export default class Send extends Command {
char: "t",
description: "Send the top X emails to this email as a test. Requires --only to be set",
}),
inlineImages: Flags.string({
char: "i",
description:
"Path to a JSON file containing informationa about inline images - see InlineImagesSpec type for format",
}),
};

public async run(): Promise<void> {
Expand Down Expand Up @@ -76,6 +81,7 @@ export default class Send extends Command {
sleepBetween: flags.sleepBetween,
onlySend: flags.only,
testSendTo,
inlineImages: flags.inlineImages,
},
);
}
Expand Down
6 changes: 3 additions & 3 deletions email/mailmerge/jest.config.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,11 +4,11 @@ export default {
preset: "../../jest.preset.js",
testEnvironment: "node",
transform: {
"^.+\\.ts$": ["ts-jest", { tsconfig: "<rootDir>/tsconfig.spec.json" }],
"^.+\\.(ts|tsx)$": ["ts-jest", { tsconfig: "<rootDir>/tsconfig.spec.json" }],
"^(.*).js$": ["babel-jest"],
},
transformIgnorePatterns: ["/node_modules/(?!(@docsoc|chalk)/)", "\\.pnp\\.[^\\/]+$"],
moduleFileExtensions: ["ts", "js", "html"],
moduleFileExtensions: ["ts", "js", "html", "tsx"],
coverageDirectory: "../../coverage/email/mailmerge-cli",
testMatch: ["<rootDir>/test/**/*.ts", "<rootDir>/src/**/*.spec.ts"],
testMatch: ["<rootDir>/test/**/*.(ts|tsx)", "<rootDir>/src/**/*.spec.ts"],
};
2 changes: 2 additions & 0 deletions email/mailmerge/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@
"@babel/preset-env": "^7.25.3",
"@docsoc/util": "^1.2.0",
"@microsoft/microsoft-graph-client": "^3.0.7",
"@react-email/render": "1.0.4",
"@types/chalk": "^2.2.0",
"@types/cli-progress": "^3.11.6",
"@types/csv-parse": "^1.2.2",
Expand Down Expand Up @@ -46,6 +47,7 @@
"typings": "./index.d.ts",
"devDependencies": {
"@jest/globals": "^29.7.0",
"@types/react": "^19.0.7",
"babel-jest": "^29.7.0",
"jest-mock-extended": "^3.0.7",
"ts-node": "^10.9.2"
Expand Down
15 changes: 11 additions & 4 deletions email/mailmerge/project.json
Original file line number Diff line number Diff line change
Expand Up @@ -15,19 +15,26 @@
"targets": {
"build": {
"executor": "@nx/js:tsc",
"outputs": ["{options.outputPath}"],
"outputs": [
"{options.outputPath}"
],
"options": {
"outputPath": "dist/email/mailmerge",
"main": "email/mailmerge/src/index.ts",
"tsConfig": "email/mailmerge/tsconfig.lib.json",
"assets": ["email/mailmerge/*.md"],
"assets": [
"email/mailmerge/*.md"
],
"rootDir": "email/mailmerge/src"
}
},
"inputs": [
"{projectRoot}/src/**/*.ts"
]
},
"nx-release-publish": {
"options": {
"packageRoot": "dist/{projectRoot}"
}
}
}
}
}
6 changes: 5 additions & 1 deletion email/mailmerge/src/engines/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -37,9 +37,10 @@
* @module
*/
import nunjucksEngine from "./nunjucks-md/index.js";
import reactEngine from "./react/index.js";
import { TemplateEngineConstructor } from "./types.js";

export type TEMPLATE_ENGINES = "nunjucks";
export type TEMPLATE_ENGINES = "nunjucks" | "react";

/**
* Default map of engine names (provided on the CLI) to constructors for those engines.
Expand All @@ -48,8 +49,11 @@ export type TEMPLATE_ENGINES = "nunjucks";
*/
export const ENGINES_MAP: Record<TEMPLATE_ENGINES, TemplateEngineConstructor> = {
nunjucks: nunjucksEngine,
react: reactEngine,
};

export * from "./types.js";
export * from "./nunjucks-md/index.js";
export * from "./react/index.js";
export { default as NunjucksMarkdownEngine } from "./nunjucks-md/index.js";
export { default as ReactEmailEngine } from "./react/index.js";
Loading
Loading