@technomoron/mail-magic-client 1.0.28 → 1.0.30
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGES +7 -0
- package/dist/cjs/mail-magic-client.js +9 -13
- package/dist/cli-version.js +33 -0
- package/dist/cli.js +2 -12
- package/dist/esm/mail-magic-client.js +9 -13
- package/dist/mail-magic-client.js +9 -13
- package/dist/preprocess.js +28 -37
- package/package.json +1 -1
package/CHANGES
CHANGED
|
@@ -1,3 +1,10 @@
|
|
|
1
|
+
Version 1.0.30 (2026-02-17)
|
|
2
|
+
|
|
3
|
+
- Refactor template preprocess compilation to use per-invocation configuration
|
|
4
|
+
(remove module-level mutable config state).
|
|
5
|
+
- Add regression coverage to ensure preprocess options (such as
|
|
6
|
+
`inline_includes`) do not leak between compile calls.
|
|
7
|
+
|
|
1
8
|
Version 1.0.29 (2026-02-11)
|
|
2
9
|
|
|
3
10
|
- Expand client coverage for mail-magic-owned endpoints:
|
|
@@ -30,13 +30,11 @@ class templateClient {
|
|
|
30
30
|
headers['Content-Type'] = 'application/json';
|
|
31
31
|
options.body = JSON.stringify(body);
|
|
32
32
|
}
|
|
33
|
-
// console.log(JSON.stringify({ options, url }));
|
|
34
33
|
const response = await fetch(url, options);
|
|
35
34
|
const j = await response.json();
|
|
36
35
|
if (response.ok) {
|
|
37
36
|
return j;
|
|
38
37
|
}
|
|
39
|
-
// console.log(JSON.stringify(j, undefined, 2));
|
|
40
38
|
if (j && j.message) {
|
|
41
39
|
throw new Error(`FETCH FAILED: ${response.status} ${j.message}`);
|
|
42
40
|
}
|
|
@@ -75,10 +73,16 @@ class templateClient {
|
|
|
75
73
|
}
|
|
76
74
|
validateTemplate(template) {
|
|
77
75
|
try {
|
|
78
|
-
const env = new nunjucks_1.default.Environment(
|
|
79
|
-
|
|
76
|
+
const env = new nunjucks_1.default.Environment(null, { autoescape: true });
|
|
77
|
+
const compiled = nunjucks_1.default.compile(template, env);
|
|
78
|
+
compiled.render({});
|
|
80
79
|
}
|
|
81
80
|
catch (error) {
|
|
81
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
82
|
+
// Syntax validation should not require local template loaders.
|
|
83
|
+
if (/template not found|no loader|unable to find template/i.test(message)) {
|
|
84
|
+
return;
|
|
85
|
+
}
|
|
82
86
|
if (error instanceof Error) {
|
|
83
87
|
throw new Error(`Template validation failed: ${error.message}`);
|
|
84
88
|
}
|
|
@@ -144,13 +148,7 @@ class templateClient {
|
|
|
144
148
|
throw new Error(`FETCH FAILED: ${response.status} ${response.statusText}`);
|
|
145
149
|
}
|
|
146
150
|
async storeTemplate(td) {
|
|
147
|
-
|
|
148
|
-
throw new Error('No template data provided');
|
|
149
|
-
}
|
|
150
|
-
this.validateTemplate(td.template);
|
|
151
|
-
if (td.sender) {
|
|
152
|
-
this.validateSender(td.sender);
|
|
153
|
-
}
|
|
151
|
+
// Backward-compatible alias for transactional template storage.
|
|
154
152
|
return this.storeTxTemplate(td);
|
|
155
153
|
}
|
|
156
154
|
async sendTemplate(std) {
|
|
@@ -177,7 +175,6 @@ class templateClient {
|
|
|
177
175
|
if (invalid.length > 0) {
|
|
178
176
|
throw new Error('Invalid email address(es): ' + invalid.join(','));
|
|
179
177
|
}
|
|
180
|
-
// this.validateTemplate(template);
|
|
181
178
|
const body = {
|
|
182
179
|
name: std.name,
|
|
183
180
|
rcpt: std.rcpt,
|
|
@@ -187,7 +184,6 @@ class templateClient {
|
|
|
187
184
|
replyTo: std.replyTo,
|
|
188
185
|
headers: std.headers
|
|
189
186
|
};
|
|
190
|
-
// console.log(JSON.stringify(body, undefined, 2));
|
|
191
187
|
if (std.attachments && std.attachments.length > 0) {
|
|
192
188
|
if (std.headers) {
|
|
193
189
|
throw new Error('Headers are not supported with attachment uploads');
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
3
|
+
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
4
|
+
};
|
|
5
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
6
|
+
exports.resolvePackageVersion = resolvePackageVersion;
|
|
7
|
+
const fs_1 = __importDefault(require("fs"));
|
|
8
|
+
const path_1 = __importDefault(require("path"));
|
|
9
|
+
function resolvePackageVersion(options = {}) {
|
|
10
|
+
const argv1 = options.argv1 ?? process.argv[1] ?? '';
|
|
11
|
+
const cwd = options.cwd ?? process.cwd();
|
|
12
|
+
const candidates = [
|
|
13
|
+
argv1 ? path_1.default.resolve(path_1.default.dirname(argv1), '../package.json') : '',
|
|
14
|
+
path_1.default.resolve(cwd, 'package.json'),
|
|
15
|
+
path_1.default.resolve(cwd, 'packages/mail-magic-client/package.json')
|
|
16
|
+
].filter(Boolean);
|
|
17
|
+
for (const candidate of candidates) {
|
|
18
|
+
if (!fs_1.default.existsSync(candidate)) {
|
|
19
|
+
continue;
|
|
20
|
+
}
|
|
21
|
+
try {
|
|
22
|
+
const raw = fs_1.default.readFileSync(candidate, 'utf8');
|
|
23
|
+
const data = JSON.parse(raw);
|
|
24
|
+
if (data.name === '@technomoron/mail-magic-client') {
|
|
25
|
+
return typeof data.version === 'string' && data.version ? data.version : 'unknown';
|
|
26
|
+
}
|
|
27
|
+
}
|
|
28
|
+
catch {
|
|
29
|
+
// Try next candidate.
|
|
30
|
+
}
|
|
31
|
+
}
|
|
32
|
+
return 'unknown';
|
|
33
|
+
}
|
package/dist/cli.js
CHANGED
|
@@ -5,27 +5,17 @@ var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
|
5
5
|
};
|
|
6
6
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
7
7
|
const fs_1 = __importDefault(require("fs"));
|
|
8
|
-
const path_1 = __importDefault(require("path"));
|
|
9
8
|
const readline_1 = __importDefault(require("readline"));
|
|
10
9
|
const commander_1 = require("commander");
|
|
11
10
|
const cli_env_1 = require("./cli-env");
|
|
12
11
|
const cli_helpers_1 = require("./cli-helpers");
|
|
12
|
+
const cli_version_1 = require("./cli-version");
|
|
13
13
|
const mail_magic_client_1 = __importDefault(require("./mail-magic-client"));
|
|
14
14
|
const preprocess_1 = require("./preprocess");
|
|
15
15
|
const program = new commander_1.Command();
|
|
16
16
|
const envDefaults = (0, cli_env_1.loadCliEnv)();
|
|
17
17
|
const defaultToken = (0, cli_env_1.resolveToken)(envDefaults);
|
|
18
18
|
const apiDefault = envDefaults.api || 'http://localhost:3000';
|
|
19
|
-
function resolvePackageVersion() {
|
|
20
|
-
try {
|
|
21
|
-
const raw = fs_1.default.readFileSync(path_1.default.join(__dirname, '../package.json'), 'utf8');
|
|
22
|
-
const data = JSON.parse(raw);
|
|
23
|
-
return typeof data.version === 'string' && data.version ? data.version : 'unknown';
|
|
24
|
-
}
|
|
25
|
-
catch {
|
|
26
|
-
return 'unknown';
|
|
27
|
-
}
|
|
28
|
-
}
|
|
29
19
|
program.option('-a, --api <api>', 'Base API endpoint', apiDefault);
|
|
30
20
|
if (defaultToken) {
|
|
31
21
|
program.option('-t, --token <token>', 'Authentication token in the format "username:token"', defaultToken);
|
|
@@ -155,7 +145,7 @@ program
|
|
|
155
145
|
.command('version')
|
|
156
146
|
.description('Show current client version')
|
|
157
147
|
.action(async () => {
|
|
158
|
-
console.log(resolvePackageVersion());
|
|
148
|
+
console.log((0, cli_version_1.resolvePackageVersion)());
|
|
159
149
|
});
|
|
160
150
|
program
|
|
161
151
|
.command('compile')
|
|
@@ -25,13 +25,11 @@ class templateClient {
|
|
|
25
25
|
headers['Content-Type'] = 'application/json';
|
|
26
26
|
options.body = JSON.stringify(body);
|
|
27
27
|
}
|
|
28
|
-
// console.log(JSON.stringify({ options, url }));
|
|
29
28
|
const response = await fetch(url, options);
|
|
30
29
|
const j = await response.json();
|
|
31
30
|
if (response.ok) {
|
|
32
31
|
return j;
|
|
33
32
|
}
|
|
34
|
-
// console.log(JSON.stringify(j, undefined, 2));
|
|
35
33
|
if (j && j.message) {
|
|
36
34
|
throw new Error(`FETCH FAILED: ${response.status} ${j.message}`);
|
|
37
35
|
}
|
|
@@ -70,10 +68,16 @@ class templateClient {
|
|
|
70
68
|
}
|
|
71
69
|
validateTemplate(template) {
|
|
72
70
|
try {
|
|
73
|
-
const env = new nunjucks.Environment(
|
|
74
|
-
|
|
71
|
+
const env = new nunjucks.Environment(null, { autoescape: true });
|
|
72
|
+
const compiled = nunjucks.compile(template, env);
|
|
73
|
+
compiled.render({});
|
|
75
74
|
}
|
|
76
75
|
catch (error) {
|
|
76
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
77
|
+
// Syntax validation should not require local template loaders.
|
|
78
|
+
if (/template not found|no loader|unable to find template/i.test(message)) {
|
|
79
|
+
return;
|
|
80
|
+
}
|
|
77
81
|
if (error instanceof Error) {
|
|
78
82
|
throw new Error(`Template validation failed: ${error.message}`);
|
|
79
83
|
}
|
|
@@ -139,13 +143,7 @@ class templateClient {
|
|
|
139
143
|
throw new Error(`FETCH FAILED: ${response.status} ${response.statusText}`);
|
|
140
144
|
}
|
|
141
145
|
async storeTemplate(td) {
|
|
142
|
-
|
|
143
|
-
throw new Error('No template data provided');
|
|
144
|
-
}
|
|
145
|
-
this.validateTemplate(td.template);
|
|
146
|
-
if (td.sender) {
|
|
147
|
-
this.validateSender(td.sender);
|
|
148
|
-
}
|
|
146
|
+
// Backward-compatible alias for transactional template storage.
|
|
149
147
|
return this.storeTxTemplate(td);
|
|
150
148
|
}
|
|
151
149
|
async sendTemplate(std) {
|
|
@@ -172,7 +170,6 @@ class templateClient {
|
|
|
172
170
|
if (invalid.length > 0) {
|
|
173
171
|
throw new Error('Invalid email address(es): ' + invalid.join(','));
|
|
174
172
|
}
|
|
175
|
-
// this.validateTemplate(template);
|
|
176
173
|
const body = {
|
|
177
174
|
name: std.name,
|
|
178
175
|
rcpt: std.rcpt,
|
|
@@ -182,7 +179,6 @@ class templateClient {
|
|
|
182
179
|
replyTo: std.replyTo,
|
|
183
180
|
headers: std.headers
|
|
184
181
|
};
|
|
185
|
-
// console.log(JSON.stringify(body, undefined, 2));
|
|
186
182
|
if (std.attachments && std.attachments.length > 0) {
|
|
187
183
|
if (std.headers) {
|
|
188
184
|
throw new Error('Headers are not supported with attachment uploads');
|
|
@@ -30,13 +30,11 @@ class templateClient {
|
|
|
30
30
|
headers['Content-Type'] = 'application/json';
|
|
31
31
|
options.body = JSON.stringify(body);
|
|
32
32
|
}
|
|
33
|
-
// console.log(JSON.stringify({ options, url }));
|
|
34
33
|
const response = await fetch(url, options);
|
|
35
34
|
const j = await response.json();
|
|
36
35
|
if (response.ok) {
|
|
37
36
|
return j;
|
|
38
37
|
}
|
|
39
|
-
// console.log(JSON.stringify(j, undefined, 2));
|
|
40
38
|
if (j && j.message) {
|
|
41
39
|
throw new Error(`FETCH FAILED: ${response.status} ${j.message}`);
|
|
42
40
|
}
|
|
@@ -75,10 +73,16 @@ class templateClient {
|
|
|
75
73
|
}
|
|
76
74
|
validateTemplate(template) {
|
|
77
75
|
try {
|
|
78
|
-
const env = new nunjucks_1.default.Environment(
|
|
79
|
-
|
|
76
|
+
const env = new nunjucks_1.default.Environment(null, { autoescape: true });
|
|
77
|
+
const compiled = nunjucks_1.default.compile(template, env);
|
|
78
|
+
compiled.render({});
|
|
80
79
|
}
|
|
81
80
|
catch (error) {
|
|
81
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
82
|
+
// Syntax validation should not require local template loaders.
|
|
83
|
+
if (/template not found|no loader|unable to find template/i.test(message)) {
|
|
84
|
+
return;
|
|
85
|
+
}
|
|
82
86
|
if (error instanceof Error) {
|
|
83
87
|
throw new Error(`Template validation failed: ${error.message}`);
|
|
84
88
|
}
|
|
@@ -144,13 +148,7 @@ class templateClient {
|
|
|
144
148
|
throw new Error(`FETCH FAILED: ${response.status} ${response.statusText}`);
|
|
145
149
|
}
|
|
146
150
|
async storeTemplate(td) {
|
|
147
|
-
|
|
148
|
-
throw new Error('No template data provided');
|
|
149
|
-
}
|
|
150
|
-
this.validateTemplate(td.template);
|
|
151
|
-
if (td.sender) {
|
|
152
|
-
this.validateSender(td.sender);
|
|
153
|
-
}
|
|
151
|
+
// Backward-compatible alias for transactional template storage.
|
|
154
152
|
return this.storeTxTemplate(td);
|
|
155
153
|
}
|
|
156
154
|
async sendTemplate(std) {
|
|
@@ -177,7 +175,6 @@ class templateClient {
|
|
|
177
175
|
if (invalid.length > 0) {
|
|
178
176
|
throw new Error('Invalid email address(es): ' + invalid.join(','));
|
|
179
177
|
}
|
|
180
|
-
// this.validateTemplate(template);
|
|
181
178
|
const body = {
|
|
182
179
|
name: std.name,
|
|
183
180
|
rcpt: std.rcpt,
|
|
@@ -187,7 +184,6 @@ class templateClient {
|
|
|
187
184
|
replyTo: std.replyTo,
|
|
188
185
|
headers: std.headers
|
|
189
186
|
};
|
|
190
|
-
// console.log(JSON.stringify(body, undefined, 2));
|
|
191
187
|
if (std.attachments && std.attachments.length > 0) {
|
|
192
188
|
if (std.headers) {
|
|
193
189
|
throw new Error('Headers are not supported with attachment uploads');
|
package/dist/preprocess.js
CHANGED
|
@@ -14,14 +14,16 @@ const node_path_1 = __importDefault(require("node:path"));
|
|
|
14
14
|
const cheerio_1 = require("cheerio");
|
|
15
15
|
const juice_1 = __importDefault(require("juice"));
|
|
16
16
|
const nunjucks_1 = __importDefault(require("nunjucks"));
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
17
|
+
function createCompileCfg(options) {
|
|
18
|
+
return {
|
|
19
|
+
env: null,
|
|
20
|
+
src_dir: options.src_dir ?? 'templates',
|
|
21
|
+
dist_dir: options.dist_dir ?? 'templates-dist',
|
|
22
|
+
css_path: options.css_path ?? node_path_1.default.join(process.cwd(), 'templates', 'foundation-emails.css'),
|
|
23
|
+
css_content: null,
|
|
24
|
+
inline_includes: options.inline_includes ?? true
|
|
25
|
+
};
|
|
26
|
+
}
|
|
25
27
|
function resolvePathRoot(dir) {
|
|
26
28
|
return node_path_1.default.isAbsolute(dir) ? dir : node_path_1.default.join(process.cwd(), dir);
|
|
27
29
|
}
|
|
@@ -58,8 +60,9 @@ function inlineIncludes(content, baseDir, srcRoot, normalizedSrcRoot, stack) {
|
|
|
58
60
|
});
|
|
59
61
|
}
|
|
60
62
|
class PreprocessExtension {
|
|
61
|
-
constructor() {
|
|
63
|
+
constructor(cfg) {
|
|
62
64
|
this.tags = ['process_layout'];
|
|
65
|
+
this.cfg = cfg;
|
|
63
66
|
}
|
|
64
67
|
parse(parser, nodes) {
|
|
65
68
|
const token = parser.nextToken();
|
|
@@ -68,13 +71,13 @@ class PreprocessExtension {
|
|
|
68
71
|
return new nodes.CallExtension(this, 'run', args);
|
|
69
72
|
}
|
|
70
73
|
run(_context, tplname) {
|
|
71
|
-
const template = cfg.env.getTemplate(tplname);
|
|
74
|
+
const template = this.cfg.env.getTemplate(tplname);
|
|
72
75
|
const src = template.tmplStr;
|
|
73
76
|
const extmatch = src.match(/\{%\s*extends\s+['"]([^'"]+)['"]\s*%\}/);
|
|
74
77
|
if (!extmatch)
|
|
75
78
|
return src;
|
|
76
79
|
const layoutName = extmatch[1];
|
|
77
|
-
const layoutTemplate = cfg.env.getTemplate(layoutName);
|
|
80
|
+
const layoutTemplate = this.cfg.env.getTemplate(layoutName);
|
|
78
81
|
const layoutSrc = layoutTemplate.tmplStr;
|
|
79
82
|
const blocks = {};
|
|
80
83
|
const blockexp = /\{%\s*block\s+([a-zA-Z0-9_]+)\s*%\}([\s\S]*?)\{%\s*endblock\s*%\}/g;
|
|
@@ -97,7 +100,7 @@ class PreprocessExtension {
|
|
|
97
100
|
return merged;
|
|
98
101
|
}
|
|
99
102
|
}
|
|
100
|
-
function process_template(tplname, writeOutput = true) {
|
|
103
|
+
function process_template(cfg, tplname, writeOutput = true) {
|
|
101
104
|
console.log(`Processing template: ${tplname}`);
|
|
102
105
|
try {
|
|
103
106
|
const srcRoot = resolvePathRoot(cfg.src_dir);
|
|
@@ -224,7 +227,7 @@ function get_all_files(dir, filelist = []) {
|
|
|
224
227
|
});
|
|
225
228
|
return filelist;
|
|
226
229
|
}
|
|
227
|
-
function find_templates() {
|
|
230
|
+
function find_templates(cfg) {
|
|
228
231
|
const srcRoot = resolvePathRoot(cfg.src_dir);
|
|
229
232
|
const all = get_all_files(srcRoot);
|
|
230
233
|
return all
|
|
@@ -242,16 +245,16 @@ function find_templates() {
|
|
|
242
245
|
return name.substring(0, name.length - 4);
|
|
243
246
|
});
|
|
244
247
|
}
|
|
245
|
-
async function process_all_templates() {
|
|
248
|
+
async function process_all_templates(cfg) {
|
|
246
249
|
const distRoot = resolvePathRoot(cfg.dist_dir);
|
|
247
250
|
if (!node_fs_1.default.existsSync(distRoot)) {
|
|
248
251
|
node_fs_1.default.mkdirSync(distRoot, { recursive: true });
|
|
249
252
|
}
|
|
250
|
-
const templates = find_templates();
|
|
253
|
+
const templates = find_templates(cfg);
|
|
251
254
|
console.log(`Found ${templates.length} templates to process: ${templates.join(', ')}`);
|
|
252
255
|
for (const template of templates) {
|
|
253
256
|
try {
|
|
254
|
-
process_template(template);
|
|
257
|
+
process_template(cfg, template);
|
|
255
258
|
}
|
|
256
259
|
catch (error) {
|
|
257
260
|
console.error(`Failed to process ${template}:`, error);
|
|
@@ -259,7 +262,7 @@ async function process_all_templates() {
|
|
|
259
262
|
}
|
|
260
263
|
console.log('All templates processed!');
|
|
261
264
|
}
|
|
262
|
-
function init_env() {
|
|
265
|
+
function init_env(cfg) {
|
|
263
266
|
const loader = new nunjucks_1.default.FileSystemLoader(resolvePathRoot(cfg.src_dir));
|
|
264
267
|
cfg.env = new nunjucks_1.default.Environment(loader, { autoescape: false });
|
|
265
268
|
if (!cfg.env)
|
|
@@ -273,7 +276,7 @@ function init_env() {
|
|
|
273
276
|
cfg.css_content = null;
|
|
274
277
|
}
|
|
275
278
|
// Extension
|
|
276
|
-
cfg.env.addExtension('PreprocessExtension', new PreprocessExtension());
|
|
279
|
+
cfg.env.addExtension('PreprocessExtension', new PreprocessExtension(cfg));
|
|
277
280
|
// Filters
|
|
278
281
|
cfg.env.addFilter('protect_variables', function (content) {
|
|
279
282
|
return content
|
|
@@ -289,29 +292,17 @@ function init_env() {
|
|
|
289
292
|
});
|
|
290
293
|
}
|
|
291
294
|
async function do_the_template_thing(options = {}) {
|
|
292
|
-
|
|
293
|
-
|
|
294
|
-
if (options.dist_dir)
|
|
295
|
-
cfg.dist_dir = options.dist_dir;
|
|
296
|
-
if (options.css_path)
|
|
297
|
-
cfg.css_path = options.css_path;
|
|
298
|
-
if (options.inline_includes !== undefined)
|
|
299
|
-
cfg.inline_includes = options.inline_includes;
|
|
300
|
-
init_env();
|
|
295
|
+
const cfg = createCompileCfg(options);
|
|
296
|
+
init_env(cfg);
|
|
301
297
|
if (options.tplname) {
|
|
302
|
-
process_template(options.tplname);
|
|
298
|
+
process_template(cfg, options.tplname);
|
|
303
299
|
}
|
|
304
300
|
else {
|
|
305
|
-
await process_all_templates();
|
|
301
|
+
await process_all_templates(cfg);
|
|
306
302
|
}
|
|
307
303
|
}
|
|
308
304
|
async function compileTemplate(options) {
|
|
309
|
-
|
|
310
|
-
|
|
311
|
-
|
|
312
|
-
cfg.css_path = options.css_path;
|
|
313
|
-
if (options.inline_includes !== undefined)
|
|
314
|
-
cfg.inline_includes = options.inline_includes;
|
|
315
|
-
init_env();
|
|
316
|
-
return process_template(options.tplname, false);
|
|
305
|
+
const cfg = createCompileCfg(options);
|
|
306
|
+
init_env(cfg);
|
|
307
|
+
return process_template(cfg, options.tplname, false);
|
|
317
308
|
}
|