Skip to content
Merged
Show file tree
Hide file tree
Changes from 7 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: 2 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -10,3 +10,5 @@ node_modules
.idea
.vscode
.iterm-workspace

*.preview.html
6 changes: 4 additions & 2 deletions packages/api/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -42,6 +42,7 @@
"jsdom": "^22.0.0",
"json2csv": "^5.0.6",
"jsonwebtoken": "^9.0.0",
"liquidjs": "^10.24.0",
"lodash": "^4.17.23",
"mime-types": "^2.1.35",
"mixin-deep": "^2.0.1",
Expand Down Expand Up @@ -143,6 +144,7 @@
"test:integration:all": "yarn mocha -r ts-node/register --require ./test/globalFixtures.ts --require ./test/bootstrap.int.ts './server/**/*.int.spec.ts' --bail --exit",
"lint": "eslint --max-warnings=0 --ext .ts",
"lint:all": "eslint --no-error-on-unmatched-pattern --max-warnings=0 --ext .ts server",
"emails:build": "ts-node server/mails/build.ts"
"emails:build": "ts-node server/mails/build.ts",
"emails:preview": "ts-node server/mails/preview.ts"
}
}
}
89 changes: 89 additions & 0 deletions packages/api/server/mails/preview.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,89 @@
import fs from 'node:fs';
import path from 'node:path';
import mjml2html from 'mjml';
import { Liquid } from 'liquidjs';

const args = process.argv.slice(2);

const getArg = (names: string[]): string | undefined => {
const index = args.findIndex(arg => names.includes(arg));
if (index === -1) {
return undefined;
}

return args[index + 1];
};

const templateName = getArg(['--template', '-t']);
const variablesPath = getArg(['--variables', '-v']);
const outputPathArg = getArg(['--output', '-o']);
const strictMode = args.includes('--strict');

if (!templateName || !variablesPath) {
// eslint-disable-next-line no-console
console.error('Usage: yarn emails:preview --template <name> --variables <path/to/variables.json> [--output <path/to/output.html>]');
process.exit(1);
}

const normalizeMailjetSyntax = (template: string): string => template
.replaceAll(/\bvar:([A-Za-z0-9_.]+)/g, 'var.$1')
.replaceAll(/{%\s*elseif\b/g, '{% elsif');

const run = async (): Promise<void> => {
const templatePath = path.resolve(__dirname, 'src', `${templateName}.mjml`);

if (!fs.existsSync(templatePath)) {
throw new Error(`Template introuvable: ${templatePath}`);
}

const absoluteVariablesPath = path.resolve(process.cwd(), variablesPath);
if (!fs.existsSync(absoluteVariablesPath)) {
throw new Error(`Fichier de variables introuvable: ${absoluteVariablesPath}`);
}

const variables = JSON.parse(fs.readFileSync(absoluteVariablesPath, 'utf-8'));
const mjmlContent = fs.readFileSync(templatePath, 'utf-8');
const compiled = mjml2html(mjmlContent, { filePath: templatePath });

if (compiled.errors.length > 0) {
compiled.errors.forEach((error) => {
// eslint-disable-next-line no-console
console.warn(`[MJML] ${error.formattedMessage}`);
});
if (strictMode) {
process.exit(1);
}
}

const engine = new Liquid({
cache: false,
strictVariables: false,
});
try {
const html = await engine.parseAndRender(normalizeMailjetSyntax(compiled.html), {
var: variables,
...variables,
});

const outputDir = path.resolve(__dirname, 'preview');
fs.mkdirSync(outputDir, { recursive: true });

const outputPath = outputPathArg
? path.resolve(process.cwd(), outputPathArg)
: path.join(outputDir, `${templateName}.preview.html`);

fs.writeFileSync(outputPath, html, 'utf-8');
/* eslint-disable no-console */
console.log(`\n✅ Preview générée avec succès: ${outputPath}`);
console.log(`Fichier: ${outputPath}`);
/* eslint-disable no-console */
} catch (err) {
/* eslint-disable no-console */
console.error('❌ Erreur lors du rendu Liquid :');
console.error(err);
/* eslint-disable no-console */
process.exit(1);
}
};

run();
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
{
"from": "01",
"to": "07 janvier 2026",
"recipientName": "Utilisateur Test",
"showDetails": false,
"show_question_summary": false,
"questions_with_answers_length": 0,
"questions_with_answers": [],
"questions_without_answers_length": 0,
"questions_without_answers": [],
"wwwUrl": "https://www.resorption-bidonvilles.gouv.fr",
"webappUrl": "https://app.resorption-bidonvilles.gouv.fr",
"backUrl": "https://api.resorption-bidonvilles.gouv.fr",
"utm": "utm_source=test&utm_medium=email&utm_campaign=preview",
"summaries": [
{
"code": "75",
"name": "Paris",
"has_activity": false,
"new_shantytowns": [],
"new_shantytowns_length": 0,
"closed_shantytowns": [],
"closed_shantytowns_length": 0,
"updated_shantytowns": [],
"updated_shantytowns_length": 0,
"new_comments": [],
"new_comments_length": 0,
"new_users": [],
"new_users_length": 0,
"shantytowns_total": 10,
"population_total": 120,
"updated_shantytowns_6_months": 9
}
]
}
13 changes: 13 additions & 0 deletions yarn.lock
Original file line number Diff line number Diff line change
Expand Up @@ -5940,6 +5940,7 @@ __metadata:
json2csv: ^5.0.6
jsonwebtoken: ^9.0.0
lint-staged: ^13.2.2
liquidjs: ^10.24.0
lodash: ^4.17.23
mime-types: ^2.1.35
mixin-deep: ^2.0.1
Expand Down Expand Up @@ -21512,6 +21513,18 @@ __metadata:
languageName: node
linkType: hard

"liquidjs@npm:^10.24.0":
version: 10.24.0
resolution: "liquidjs@npm:10.24.0"
dependencies:
commander: ^10.0.0
bin:
liquid: bin/liquid.js
liquidjs: bin/liquid.js
checksum: a25638d1a52e34d64ad0ff8deb04d42dd2276913b277742b389b9f5e1a8ec8fc3b923228eb701d28f48b36e199c15466eb5bfee4d38464806a05050ff2c507ce
languageName: node
linkType: hard

"listenercount@npm:~1.0.1":
version: 1.0.1
resolution: "listenercount@npm:1.0.1"
Expand Down
Loading