Skip to content

Commit

Permalink
Feat: Sidecar files
Browse files Browse the repository at this point in the history
  • Loading branch information
Gum-Joe committed Jul 31, 2024
1 parent ae15958 commit 4c8b4e7
Show file tree
Hide file tree
Showing 8 changed files with 106 additions and 17 deletions.
3 changes: 2 additions & 1 deletion packages/email/docsoc-mail-merge/.gitignore
Original file line number Diff line number Diff line change
@@ -1 +1,2 @@
data
data
output
35 changes: 28 additions & 7 deletions packages/email/docsoc-mail-merge/src/cli.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,10 +7,12 @@ import { join } from "path";

import packageJSON from "../package.json";
import { ENGINES_MAP } from "./engines";
import { TemplatePreview } from "./engines/types";
import { TemplatePreviews } from "./engines/types";
import { getFileNameSchemeInteractively } from "./interactivity/getFileNameSchemeInteractively";
import getRunNameInteractively from "./interactivity/getRunNameInteractively";
import mapCSVFieldsInteractive from "./interactivity/mapCSVFieldsInteractive";
import { getRecordPreviewPrefixForIndividual, getRecordPreviewPrefixForMetadata } from "./sideCardData";
import { SidecardData } from "./sideCardData/types";
import { stopIfCriticalFsError } from "./util/files";
import createLogger from "./util/logger";
import { CliOptions, CSVRecord } from "./util/types";
Expand Down Expand Up @@ -81,7 +83,7 @@ async function main(opts: CliOptions) {

// 8: Render intermediate results
logger.info("Rendering template previews/intermediates...");
const previews: [TemplatePreview, CSVRecord][] = await Promise.all(
const previews: [TemplatePreviews, CSVRecord][] = await Promise.all(
records.map(async (csvRecord) => {
const preparedRecord = Object.fromEntries(
Object.entries(csvRecord).map(([key, value]) => {
Expand All @@ -100,16 +102,35 @@ async function main(opts: CliOptions) {
await mkdirp(previewsRoot);
logger.debug("Writing files...");
await Promise.all(
previews.flatMap(async ([previews, record]) =>
previews.map(async (preview) => {
const fileName = fileNamer(record);
previews.flatMap(async ([previews, record]) => {
const operations = previews.map(async (preview) => {
const fileName = getRecordPreviewPrefixForIndividual(record, fileNamer, opts.templateEngine, preview);
logger.debug(`Writing ${fileName}__${opts.templateEngine}__${preview.name}`);
await fs.writeFile(
join(previewsRoot, `${fileName}__${opts.templateEngine}__${preview.name}`),
preview.content,
);
}),
),
});

const sidecar: SidecardData = {
record: record,
engine: opts.templateEngine,
engineOptions: opts.templateOptions,
files: previews.map((preview) => ({
filename: getRecordPreviewPrefixForIndividual(record, fileNamer, opts.templateEngine, preview),
engineData: {
...preview,
content: undefined,
},
})),
};

const metadataFile = getRecordPreviewPrefixForMetadata(record, fileNamer);
logger.debug(`Writing metadata for ${fileNamer(record)} to ${metadataFile}`);
operations.push(fs.writeFile(join(previewsRoot, metadataFile), JSON.stringify(sidecar, null, 4)));

return operations;
}),
);

logger.info("Done! Review previews and then send.");
Expand Down
4 changes: 3 additions & 1 deletion packages/email/docsoc-mail-merge/src/engines/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,9 @@
import nunjucksEngine from "./nunjucks-md";
import { TemplateEngineConstructor } from "./types";

export type TEMPLATE_ENGINE = "nunjucks";

/** Map of engine names (provided on the CLI) to constructors for those engines */
export const ENGINES_MAP: Record<string, TemplateEngineConstructor> = {
export const ENGINES_MAP: Record<TEMPLATE_ENGINE, TemplateEngineConstructor> = {
nunjucks: nunjucksEngine,
};
Original file line number Diff line number Diff line change
Expand Up @@ -76,12 +76,16 @@ export default class NunjucksMarkdownEngine extends TemplateEngine {
{
name: "Preview-Markdown.md",
content: expanded, // you can edit this and re-render
metadata: {},
metadata: {
type: "markdown",
},
},
{
name: "Preview-HTML.html",
content: wrapped, // this is what will be sent - do not edit it, re-rendering will overwrite it
metadata: {},
metadata: {
type: "html",
},
},
];
}
Expand Down
20 changes: 16 additions & 4 deletions packages/email/docsoc-mail-merge/src/engines/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -51,9 +51,9 @@ export abstract class TemplateEngine {
/**
* Render a preview of the template with the given record.
* @param record - The record to render the template with, where the keys correspond to the fields extracted in {@link TemplateEngine.extractFields}
* @returns A promise that resolves to an array of {@link TemplatePreview} objects - check the type for more information
* @returns A promise that resolves to an array of {@link TemplatePreviews} objects - check the type for more information
*/
abstract renderPreview(record: CSVRecord): Promise<TemplatePreview>;
abstract renderPreview(record: CSVRecord): Promise<TemplatePreviews>;
}

/**
Expand All @@ -62,10 +62,22 @@ export abstract class TemplateEngine {
* Additionally we write a JSON file containing the metadata info next to the files, for reading back in to generate re-renders.
*/
export type TemplatePreview = {
/** Name of the preview, for you to recognise on re-render/load-back-in. As this will be the last part of the filename, you should include a file extension */
/**
* Name of the preview, for you to recognise on re-render/load-back-in.
*
* This should be related to its content, and _should not be unique_ - we will append a unique identifier to the filename based on the CSV record.
*
* As this will be the last part of the filename, you should include a file extension
* @example
* "markdown-preview.md" // good
* "preview.html" // good
* `preview-${record["name"]}.html` // bad! We will add record specific parts to the filename
*/
name: string;
/** The content of the preview, to be written to a file */
content: string;
/** Metadata about the preview, to be written to a JSON file, that will be given back to you on re-render/load-back-in */
metadata: Record<string, unknown>;
}[];
};

export type TemplatePreviews = TemplatePreview[];
26 changes: 26 additions & 0 deletions packages/email/docsoc-mail-merge/src/sideCardData/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
/**
* Handle "sidecar" data - these are JSON files that sit next to rendered template previews,
* containing metadata about the preview, so that we can re-render them later.
*
* @packageDocumentation
*/
import { TemplatePreview } from "../engines/types";
import { CSVRecord } from "../util/types";

const PARTS_SEPARATOR = "__";

export const getRecordPreviewPrefix = (record: CSVRecord, fileNamer: (record: CSVRecord) => string) =>
`${fileNamer(record)}`;

/** Generate predicable prefixes for preview names */
export const getRecordPreviewPrefixForIndividual = (
record: CSVRecord,
fileNamer: (record: CSVRecord) => string,
templateEngine: string,
preview: TemplatePreview,
) => [getRecordPreviewPrefix(record, fileNamer), templateEngine, preview.name].join(PARTS_SEPARATOR);

export const getRecordPreviewPrefixForMetadata = (record: CSVRecord, fileNamer: (record: CSVRecord) => string) =>
`${getRecordPreviewPrefix(record, fileNamer)}-metadata.json`;

// export const writeSidecardData = async () => {
23 changes: 23 additions & 0 deletions packages/email/docsoc-mail-merge/src/sideCardData/types.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
import { ENGINES_MAP } from "../engines";
import { TemplateEngineOptions, TemplatePreview } from "../engines/types";
import { CSVRecord } from "../util/types";

/**
* Outputted to JSON files next to rendered template previews, containing metadata about the preview.
*/
export interface SidecardData {
/** Record associated with the template rendered */
record: CSVRecord;
/** Engine used */
engine: keyof typeof ENGINES_MAP;
/** Options given to the engine */
engineOptions: TemplateEngineOptions;
/** Data about the files rendered */
files: Array<{
filename: string;
/** Data returned from {@link TemplateEngine.renderPreview} */
engineData: Omit<TemplatePreview, "content"> & {
content: never | undefined;
};
}>;
}
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import inquirer from "inquirer";

import mapInteractive from "../../src/interactivity/mapInteractive";
import mapInteractive from "../../src/interactivity/mapCSVFieldsInteractive";
import createLogger from "../../src/util/logger";

jest.mock("inquirer");
Expand All @@ -11,7 +11,7 @@ jest.mock("../../src/util/logger", () => {
warn: jest.fn(),
error: jest.fn(),
};
return () => logger
return () => logger;
});

describe("mapInteractive", () => {
Expand Down

0 comments on commit 4c8b4e7

Please sign in to comment.