@technomoron/mail-magic-client 1.0.23

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.
@@ -0,0 +1,281 @@
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
+ const fs_1 = __importDefault(require("fs"));
7
+ const path_1 = __importDefault(require("path"));
8
+ const email_addresses_1 = __importDefault(require("email-addresses"));
9
+ const nunjucks_1 = __importDefault(require("nunjucks"));
10
+ class templateClient {
11
+ constructor(baseURL, apiKey) {
12
+ this.baseURL = baseURL;
13
+ this.apiKey = apiKey;
14
+ if (!apiKey || !baseURL) {
15
+ throw new Error('Apikey/api-url required');
16
+ }
17
+ }
18
+ async request(method, command, body) {
19
+ const url = `${this.baseURL}${command}`;
20
+ const options = {
21
+ method,
22
+ headers: {
23
+ 'Content-Type': 'application/json',
24
+ Authorization: `Bearer apikey-${this.apiKey}`
25
+ },
26
+ body: body ? JSON.stringify(body) : '{}'
27
+ };
28
+ // console.log(JSON.stringify({ options, url }));
29
+ const response = await fetch(url, options);
30
+ const j = await response.json();
31
+ if (response.ok) {
32
+ return j;
33
+ }
34
+ // console.log(JSON.stringify(j, undefined, 2));
35
+ if (j && j.message) {
36
+ throw new Error(`FETCH FAILED: ${response.status} ${j.message}`);
37
+ }
38
+ else {
39
+ throw new Error(`FETCH FAILED: ${response.status} ${response.statusText}`);
40
+ }
41
+ }
42
+ async get(command) {
43
+ return this.request('GET', command);
44
+ }
45
+ async post(command, body) {
46
+ return this.request('POST', command, body);
47
+ }
48
+ async put(command, body) {
49
+ return this.request('PUT', command, body);
50
+ }
51
+ async delete(command, body) {
52
+ return this.request('DELETE', command, body);
53
+ }
54
+ validateEmails(list) {
55
+ const valid = [], invalid = [];
56
+ const emails = list
57
+ .split(',')
58
+ .map((email) => email.trim())
59
+ .filter((email) => email !== '');
60
+ emails.forEach((email) => {
61
+ const parsed = email_addresses_1.default.parseOneAddress(email);
62
+ if (parsed && parsed.address) {
63
+ valid.push(parsed.address);
64
+ }
65
+ else {
66
+ invalid.push(email);
67
+ }
68
+ });
69
+ return { valid, invalid };
70
+ }
71
+ validateTemplate(template) {
72
+ try {
73
+ const env = new nunjucks_1.default.Environment(new nunjucks_1.default.FileSystemLoader(['./templates']));
74
+ const t = env.renderString(template, {});
75
+ }
76
+ catch (error) {
77
+ if (error instanceof Error) {
78
+ throw new Error(`Template validation failed: ${error.message}`);
79
+ }
80
+ else {
81
+ throw new Error('Template validation failed with an unknown error');
82
+ }
83
+ }
84
+ }
85
+ validateSender(sender) {
86
+ const exp = /^[^<>]+<[^<>]+@[^<>]+\.[^<>]+>$/;
87
+ if (!exp.test(sender)) {
88
+ throw new Error('Invalid sender format. Expected "Name <email@example.com>"');
89
+ }
90
+ }
91
+ createAttachmentPayload(attachments) {
92
+ const formData = new FormData();
93
+ const usedFields = [];
94
+ for (const attachment of attachments) {
95
+ if (!attachment?.path) {
96
+ throw new Error('Attachment path is required');
97
+ }
98
+ const raw = fs_1.default.readFileSync(attachment.path);
99
+ const filename = attachment.filename || path_1.default.basename(attachment.path);
100
+ const blob = new Blob([raw], attachment.contentType ? { type: attachment.contentType } : undefined);
101
+ const field = attachment.field || 'attachment';
102
+ formData.append(field, blob, filename);
103
+ usedFields.push(field);
104
+ }
105
+ return { formData, usedFields };
106
+ }
107
+ appendFields(formData, fields) {
108
+ for (const [key, value] of Object.entries(fields)) {
109
+ if (value === undefined || value === null) {
110
+ continue;
111
+ }
112
+ if (typeof value === 'string') {
113
+ formData.append(key, value);
114
+ }
115
+ else if (typeof value === 'number' || typeof value === 'boolean') {
116
+ formData.append(key, String(value));
117
+ }
118
+ else {
119
+ formData.append(key, JSON.stringify(value));
120
+ }
121
+ }
122
+ }
123
+ async postFormData(command, formData) {
124
+ const url = `${this.baseURL}${command}`;
125
+ const response = await fetch(url, {
126
+ method: 'POST',
127
+ headers: {
128
+ Authorization: `Bearer apikey-${this.apiKey}`
129
+ },
130
+ body: formData
131
+ });
132
+ const j = await response.json();
133
+ if (response.ok) {
134
+ return j;
135
+ }
136
+ if (j && j.message) {
137
+ throw new Error(`FETCH FAILED: ${response.status} ${j.message}`);
138
+ }
139
+ throw new Error(`FETCH FAILED: ${response.status} ${response.statusText}`);
140
+ }
141
+ async storeTemplate(td) {
142
+ if (!td.template) {
143
+ throw new Error('No template data provided');
144
+ }
145
+ this.validateTemplate(td.template);
146
+ if (td.sender) {
147
+ this.validateSender(td.sender);
148
+ }
149
+ return this.storeTxTemplate(td);
150
+ }
151
+ async sendTemplate(std) {
152
+ if (!std.name || !std.rcpt) {
153
+ throw new Error('Invalid request body; name/rcpt required');
154
+ }
155
+ return this.sendTxMessage(std);
156
+ }
157
+ async storeTxTemplate(td) {
158
+ if (!td.template) {
159
+ throw new Error('No template data provided');
160
+ }
161
+ this.validateTemplate(td.template);
162
+ if (td.sender) {
163
+ this.validateSender(td.sender);
164
+ }
165
+ return this.post('/api/v1/tx/template', td);
166
+ }
167
+ async sendTxMessage(std) {
168
+ if (!std.name || !std.rcpt) {
169
+ throw new Error('Invalid request body; name/rcpt required');
170
+ }
171
+ const { valid, invalid } = this.validateEmails(std.rcpt);
172
+ if (invalid.length > 0) {
173
+ throw new Error('Invalid email address(es): ' + invalid.join(','));
174
+ }
175
+ // this.validateTemplate(template);
176
+ const body = {
177
+ name: std.name,
178
+ rcpt: std.rcpt,
179
+ domain: std.domain || '',
180
+ locale: std.locale || '',
181
+ vars: std.vars || {},
182
+ replyTo: std.replyTo,
183
+ headers: std.headers
184
+ };
185
+ // console.log(JSON.stringify(body, undefined, 2));
186
+ if (std.attachments && std.attachments.length > 0) {
187
+ if (std.headers) {
188
+ throw new Error('Headers are not supported with attachment uploads');
189
+ }
190
+ const { formData } = this.createAttachmentPayload(std.attachments);
191
+ this.appendFields(formData, {
192
+ name: std.name,
193
+ rcpt: std.rcpt,
194
+ domain: std.domain || '',
195
+ locale: std.locale || '',
196
+ vars: JSON.stringify(std.vars || {}),
197
+ replyTo: std.replyTo
198
+ });
199
+ return this.postFormData('/api/v1/tx/message', formData);
200
+ }
201
+ return this.post('/api/v1/tx/message', body);
202
+ }
203
+ async storeFormTemplate(data) {
204
+ if (!data.template) {
205
+ throw new Error('No template data provided');
206
+ }
207
+ if (!data.idname) {
208
+ throw new Error('Missing form identifier');
209
+ }
210
+ if (!data.sender) {
211
+ throw new Error('Missing sender address');
212
+ }
213
+ if (!data.recipient) {
214
+ throw new Error('Missing recipient address');
215
+ }
216
+ this.validateTemplate(data.template);
217
+ this.validateSender(data.sender);
218
+ return this.post('/api/v1/form/template', data);
219
+ }
220
+ async sendFormMessage(data) {
221
+ if (!data.formid) {
222
+ throw new Error('Invalid request body; formid required');
223
+ }
224
+ const fields = data.fields || {};
225
+ const baseFields = {
226
+ formid: data.formid,
227
+ secret: data.secret,
228
+ recipient: data.recipient,
229
+ domain: data.domain,
230
+ locale: data.locale,
231
+ vars: data.vars || {},
232
+ replyTo: data.replyTo,
233
+ ...fields
234
+ };
235
+ if (data.attachments && data.attachments.length > 0) {
236
+ const { formData } = this.createAttachmentPayload(data.attachments);
237
+ this.appendFields(formData, {
238
+ formid: data.formid,
239
+ secret: data.secret,
240
+ recipient: data.recipient,
241
+ domain: data.domain,
242
+ locale: data.locale,
243
+ vars: JSON.stringify(data.vars || {}),
244
+ replyTo: data.replyTo
245
+ });
246
+ this.appendFields(formData, fields);
247
+ return this.postFormData('/api/v1/form/message', formData);
248
+ }
249
+ return this.post('/api/v1/form/message', baseFields);
250
+ }
251
+ async uploadAssets(data) {
252
+ if (!data.domain) {
253
+ throw new Error('domain is required');
254
+ }
255
+ if (!data.files || data.files.length === 0) {
256
+ throw new Error('At least one asset file is required');
257
+ }
258
+ if (data.templateType && !data.template) {
259
+ throw new Error('template is required when templateType is provided');
260
+ }
261
+ if (data.template && !data.templateType) {
262
+ throw new Error('templateType is required when template is provided');
263
+ }
264
+ const attachments = data.files.map((input) => {
265
+ if (typeof input === 'string') {
266
+ return { path: input, field: 'asset' };
267
+ }
268
+ return { ...input, field: input.field || 'asset' };
269
+ });
270
+ const { formData } = this.createAttachmentPayload(attachments);
271
+ this.appendFields(formData, {
272
+ domain: data.domain,
273
+ templateType: data.templateType,
274
+ template: data.template,
275
+ locale: data.locale,
276
+ path: data.path
277
+ });
278
+ return this.postFormData('/api/v1/assets', formData);
279
+ }
280
+ }
281
+ exports.default = templateClient;
@@ -0,0 +1,310 @@
1
+ "use strict";
2
+ /*
3
+ * Merge templates in source dir using nunjucks blocks and layouts.
4
+ * Preserve flow control and variable expansion for later dynamic data.
5
+ */
6
+ var __importDefault = (this && this.__importDefault) || function (mod) {
7
+ return (mod && mod.__esModule) ? mod : { "default": mod };
8
+ };
9
+ Object.defineProperty(exports, "__esModule", { value: true });
10
+ exports.do_the_template_thing = do_the_template_thing;
11
+ exports.compileTemplate = compileTemplate;
12
+ const node_fs_1 = __importDefault(require("node:fs"));
13
+ const node_path_1 = __importDefault(require("node:path"));
14
+ const cheerio_1 = require("cheerio");
15
+ const juice_1 = __importDefault(require("juice"));
16
+ const nunjucks_1 = __importDefault(require("nunjucks"));
17
+ const cfg = {
18
+ env: null,
19
+ src_dir: 'templates',
20
+ dist_dir: 'templates-dist',
21
+ css_path: node_path_1.default.join(process.cwd(), 'templates', 'foundation-emails.css'),
22
+ css_content: null,
23
+ inline_includes: true
24
+ };
25
+ function resolvePathRoot(dir) {
26
+ return node_path_1.default.isAbsolute(dir) ? dir : node_path_1.default.join(process.cwd(), dir);
27
+ }
28
+ function resolveCssPath(cssPath) {
29
+ if (!cssPath) {
30
+ return '';
31
+ }
32
+ return node_path_1.default.isAbsolute(cssPath) ? cssPath : node_path_1.default.join(process.cwd(), cssPath);
33
+ }
34
+ function inlineIncludes(content, baseDir, srcRoot, stack) {
35
+ const includeExp = /\{%\s*include\s+['"]([^'"]+)['"][^%]*%\}/g;
36
+ return content.replace(includeExp, (_match, includePath) => {
37
+ const cleaned = includePath.replace(/^\/+/, '');
38
+ const candidates = [node_path_1.default.resolve(baseDir, cleaned), node_path_1.default.resolve(srcRoot, cleaned)];
39
+ const found = candidates.find((candidate) => node_fs_1.default.existsSync(candidate));
40
+ if (!found) {
41
+ throw new Error(`Include not found: ${includePath}`);
42
+ }
43
+ const resolved = node_fs_1.default.realpathSync(found);
44
+ if (stack.has(resolved)) {
45
+ throw new Error(`Circular include detected for ${includePath}`);
46
+ }
47
+ stack.add(resolved);
48
+ const raw = node_fs_1.default.readFileSync(resolved, 'utf8');
49
+ const inlined = inlineIncludes(raw, node_path_1.default.dirname(resolved), srcRoot, stack);
50
+ stack.delete(resolved);
51
+ return inlined;
52
+ });
53
+ }
54
+ class PreprocessExtension {
55
+ constructor() {
56
+ this.tags = ['process_layout'];
57
+ }
58
+ // types from nunjucks are not exported for parser/nodes; use any
59
+ parse(parser, nodes) {
60
+ const token = parser.nextToken();
61
+ const args = parser.parseSignature(null, true);
62
+ parser.advanceAfterBlockEnd(token.value);
63
+ return new nodes.CallExtension(this, 'run', args);
64
+ }
65
+ run(_context, tplname) {
66
+ const template = cfg.env.getTemplate(tplname);
67
+ const src = template.tmplStr;
68
+ const extmatch = src.match(/\{%\s*extends\s+['"]([^'"]+)['"]\s*%\}/);
69
+ if (!extmatch)
70
+ return src;
71
+ const layoutName = extmatch[1];
72
+ const layoutTemplate = cfg.env.getTemplate(layoutName);
73
+ const layoutSrc = layoutTemplate.tmplStr;
74
+ const blocks = {};
75
+ const blockexp = /\{%\s*block\s+([a-zA-Z0-9_]+)\s*%\}([\s\S]*?)\{%\s*endblock\s*%\}/g;
76
+ let match;
77
+ while ((match = blockexp.exec(src)) !== null) {
78
+ const bname = match[1];
79
+ const bcontent = match[2];
80
+ blocks[bname] = bcontent.trim();
81
+ }
82
+ let merged = layoutSrc;
83
+ for (const [bname, bcontent] of Object.entries(blocks)) {
84
+ const lbexpt = new RegExp(`\\{%\\s*block\\s+${bname}\\s*%\\}[\\s\\S]*?\\{%\\s*endblock\\s*%\\}`, 'g');
85
+ merged = merged.replace(lbexpt, bcontent);
86
+ }
87
+ merged = merged.replace(/\{%\s*extends\s+['"][^'"]+['"]\s*%\}/, '');
88
+ if (merged.match(/\{%\s*extends\s+['"]([^'"]+)['"]\s*%\}/)) {
89
+ return this.run(_context, layoutName);
90
+ }
91
+ merged = merged.replace(/\{%\s*block\s+([a-zA-Z0-9_]+)\s*%\}\s*\{%\s*endblock\s*%\}/g, '');
92
+ return merged;
93
+ }
94
+ }
95
+ function process_template(tplname, writeOutput = true) {
96
+ console.log(`Processing template: ${tplname}`);
97
+ try {
98
+ const srcRoot = resolvePathRoot(cfg.src_dir);
99
+ const templateFile = node_path_1.default.join(srcRoot, `${tplname}.njk`);
100
+ // 1) Resolve template inheritance
101
+ const mergedTemplate = cfg.env.renderString(`{% process_layout "${tplname}.njk" %}`, {});
102
+ // 1.5) Inline partials/includes so the server doesn't need a loader
103
+ const mergedWithPartials = cfg.inline_includes
104
+ ? inlineIncludes(mergedTemplate, node_path_1.default.dirname(templateFile), srcRoot, new Set())
105
+ : mergedTemplate;
106
+ // 2) Protect variables/flow
107
+ const protectedTemplate = cfg.env.filters.protect_variables(mergedWithPartials);
108
+ // 3) Light HTML transforms for email compatibility
109
+ console.log('Processing HTML for email compatibility');
110
+ let processedHtml = protectedTemplate;
111
+ try {
112
+ const $ = (0, cheerio_1.load)(protectedTemplate, {
113
+ xmlMode: false
114
+ // decodeEntities: false
115
+ });
116
+ // <container> -> <table>
117
+ $('container').each(function () {
118
+ const $container = $(this);
119
+ const $table = $('<table/>').attr({
120
+ align: 'center',
121
+ class: $container.attr('class') || '',
122
+ width: '100%',
123
+ cellpadding: '0',
124
+ cellspacing: '0',
125
+ border: '0'
126
+ });
127
+ const $tbody = $('<tbody/>');
128
+ $table.append($tbody);
129
+ $tbody.append($container.contents());
130
+ $container.replaceWith($table);
131
+ });
132
+ // <row> -> <tr>
133
+ $('row').each(function () {
134
+ const $row = $(this);
135
+ const background = $row.attr('background') || '';
136
+ const $tr = $('<tr/>').attr({ class: $row.attr('class') || '' });
137
+ if (background)
138
+ $tr.css('background', background);
139
+ $tr.append($row.contents());
140
+ $row.replaceWith($tr);
141
+ });
142
+ // <columns> -> <td>
143
+ $('columns').each(function () {
144
+ const $columns = $(this);
145
+ const padding = $columns.attr('padding') || '0';
146
+ const $td = $('<td/>').attr({
147
+ class: $columns.attr('class') || '',
148
+ style: `padding: ${padding};`
149
+ });
150
+ $td.append($columns.contents());
151
+ $columns.replaceWith($td);
152
+ });
153
+ // <button> -> <a>
154
+ $('button').each(function () {
155
+ const $button = $(this);
156
+ const href = $button.attr('href') || '#';
157
+ const buttonClass = $button.attr('class') || '';
158
+ const $a = $('<a/>').attr({
159
+ href,
160
+ class: buttonClass,
161
+ style: $button.attr('style') ||
162
+ 'display: inline-block; padding: 8px 16px; border-radius: 3px; text-decoration: none;'
163
+ });
164
+ $a.append($button.contents());
165
+ $button.replaceWith($a);
166
+ });
167
+ processedHtml = $.html();
168
+ console.log('HTML processing complete');
169
+ }
170
+ catch (htmlError) {
171
+ console.error('HTML processing error:', htmlError);
172
+ processedHtml = protectedTemplate;
173
+ }
174
+ // 4) Inline CSS
175
+ let inlinedHtml;
176
+ try {
177
+ inlinedHtml = (0, juice_1.default)(processedHtml, {
178
+ extraCss: cfg.css_content ?? undefined,
179
+ removeStyleTags: false,
180
+ preserveMediaQueries: true,
181
+ preserveFontFaces: true
182
+ });
183
+ }
184
+ catch (juiceError) {
185
+ console.error('CSS inlining error:', juiceError);
186
+ inlinedHtml = processedHtml;
187
+ }
188
+ // 5) Restore variables/flow
189
+ const finalHtml = cfg.env.filters.restore_variables(inlinedHtml);
190
+ // Write
191
+ if (writeOutput) {
192
+ const distRoot = resolvePathRoot(cfg.dist_dir);
193
+ const outputPath = node_path_1.default.join(distRoot, `${tplname}.njk`);
194
+ node_fs_1.default.mkdirSync(node_path_1.default.dirname(outputPath), { recursive: true });
195
+ node_fs_1.default.writeFileSync(outputPath, finalHtml);
196
+ }
197
+ if (writeOutput) {
198
+ console.log(`Created ${tplname}.njk`);
199
+ }
200
+ return finalHtml;
201
+ }
202
+ catch (error) {
203
+ console.error(`Error processing ${tplname}:`, error);
204
+ throw error;
205
+ }
206
+ }
207
+ function get_all_files(dir, filelist = []) {
208
+ const files = node_fs_1.default.readdirSync(dir);
209
+ files.forEach((file) => {
210
+ const file_path = node_path_1.default.join(dir, file);
211
+ if (node_fs_1.default.statSync(file_path).isDirectory()) {
212
+ get_all_files(file_path, filelist);
213
+ }
214
+ else {
215
+ filelist.push(file_path);
216
+ }
217
+ });
218
+ return filelist;
219
+ }
220
+ function find_templates() {
221
+ const srcRoot = resolvePathRoot(cfg.src_dir);
222
+ const all = get_all_files(srcRoot);
223
+ return all
224
+ .filter((file) => file.endsWith('.njk'))
225
+ .filter((file) => {
226
+ const basename = node_path_1.default.basename(file);
227
+ const content = node_fs_1.default.readFileSync(file, 'utf8');
228
+ return (!basename.startsWith('_') &&
229
+ !basename.includes('layout') &&
230
+ !basename.includes('part') &&
231
+ content.includes('{% extends'));
232
+ })
233
+ .map((file) => {
234
+ const name = node_path_1.default.relative(srcRoot, file);
235
+ return name.substring(0, name.length - 4);
236
+ });
237
+ }
238
+ async function process_all_templates() {
239
+ const distRoot = resolvePathRoot(cfg.dist_dir);
240
+ if (!node_fs_1.default.existsSync(distRoot)) {
241
+ node_fs_1.default.mkdirSync(distRoot, { recursive: true });
242
+ }
243
+ const templates = find_templates();
244
+ console.log(`Found ${templates.length} templates to process: ${templates.join(', ')}`);
245
+ for (const template of templates) {
246
+ try {
247
+ process_template(template);
248
+ }
249
+ catch (error) {
250
+ console.error(`Failed to process ${template}:`, error);
251
+ }
252
+ }
253
+ console.log('All templates processed!');
254
+ }
255
+ function init_env() {
256
+ const loader = new nunjucks_1.default.FileSystemLoader(resolvePathRoot(cfg.src_dir));
257
+ cfg.env = new nunjucks_1.default.Environment(loader, { autoescape: false });
258
+ if (!cfg.env)
259
+ throw Error('Unable to init nunjucks environment');
260
+ // Load CSS if present
261
+ const cssPath = resolveCssPath(cfg.css_path);
262
+ if (cssPath && node_fs_1.default.existsSync(cssPath)) {
263
+ cfg.css_content = node_fs_1.default.readFileSync(cssPath, 'utf8');
264
+ }
265
+ else {
266
+ cfg.css_content = null;
267
+ }
268
+ // Extension
269
+ cfg.env.addExtension('PreprocessExtension', new PreprocessExtension());
270
+ // Filters
271
+ cfg.env.addFilter('protect_variables', function (content) {
272
+ return content
273
+ .replace(/(\{\{[\s\S]*?\}\})/g, (m) => `<!--VAR:${Buffer.from(m).toString('base64')}-->`)
274
+ .replace(/(\{%(?!\s*block|\s*endblock|\s*extends)[\s\S]*?%\})/g, (m) => {
275
+ return `<!--FLOW:${Buffer.from(m).toString('base64')}-->`;
276
+ });
277
+ });
278
+ cfg.env.addFilter('restore_variables', function (content) {
279
+ return content
280
+ .replace(/<!--VAR:(.*?)-->/g, (_m, enc) => Buffer.from(enc, 'base64').toString('utf8'))
281
+ .replace(/<!--FLOW:(.*?)-->/g, (_m, enc) => Buffer.from(enc, 'base64').toString('utf8'));
282
+ });
283
+ }
284
+ async function do_the_template_thing(options = {}) {
285
+ if (options.src_dir)
286
+ cfg.src_dir = options.src_dir;
287
+ if (options.dist_dir)
288
+ cfg.dist_dir = options.dist_dir;
289
+ if (options.css_path)
290
+ cfg.css_path = options.css_path;
291
+ if (options.inline_includes !== undefined)
292
+ cfg.inline_includes = options.inline_includes;
293
+ init_env();
294
+ if (options.tplname) {
295
+ process_template(options.tplname);
296
+ }
297
+ else {
298
+ await process_all_templates();
299
+ }
300
+ }
301
+ async function compileTemplate(options) {
302
+ if (options.src_dir)
303
+ cfg.src_dir = options.src_dir;
304
+ if (options.css_path)
305
+ cfg.css_path = options.css_path;
306
+ if (options.inline_includes !== undefined)
307
+ cfg.inline_includes = options.inline_includes;
308
+ init_env();
309
+ return process_template(options.tplname, false);
310
+ }
package/package.json ADDED
@@ -0,0 +1,69 @@
1
+ {
2
+ "name": "@technomoron/mail-magic-client",
3
+ "version": "1.0.23",
4
+ "description": "Client library for mail-magic",
5
+ "main": "dist/cjs/mail-magic-client.js",
6
+ "types": "dist/cjs/mail-magic-client.d.ts",
7
+ "module": "dist/esm/mail-magic-client.js",
8
+ "bin": {
9
+ "mm-cli": "dist/cli.js"
10
+ },
11
+ "scripts": {
12
+ "prepare": "npm run build",
13
+ "scrub": "rm -rf node_modules pnpm-lock.yaml lib/ dist/",
14
+ "build:cjs": "tsc --project tsconfig/tsconfig.cjs.json",
15
+ "build:esm": "tsc --project tsconfig/tsconfig.esm.json",
16
+ "build:cli": "tsc --project tsconfig/tsconfig.cli.json",
17
+ "build": "npm run build:cjs && npm run build:esm && npm run build:cli",
18
+ "test": "vitest run",
19
+ "test:watch": "vitest",
20
+ "lint": "eslint --ext .js,.ts,.vue ./src",
21
+ "lintfix": "eslint --fix --ext .js,.ts,.vue,.json ./src",
22
+ "format": "npm run lintfix && npm run pretty",
23
+ "pretty": "prettier --write \"**/*.{js,ts,vue,json,css,scss,md}\"",
24
+ "cleanbuild": "rm -rf ./dist/ && npm run lintfix && npm run format && npm run build"
25
+ },
26
+ "repository": {
27
+ "type": "git",
28
+ "url": "git+https://github.com/technomoron/mail-magic.git",
29
+ "directory": "packages/mail-magic-client"
30
+ },
31
+ "author": "Bjørn Erik Jacobsen",
32
+ "license": "MIT",
33
+ "files": [
34
+ "dist/**/*.js",
35
+ "dist/**/*.d.ts",
36
+ "package.json",
37
+ "CHANGES"
38
+ ],
39
+ "dependencies": {
40
+ "cheerio": "^1.1.2",
41
+ "commander": "^10.0.1",
42
+ "email-addresses": "^5.0.0",
43
+ "foundation-emails": "^2.4.0",
44
+ "inky": "^0.1.0",
45
+ "juice": "^11.0.1",
46
+ "node-fetch": "^3.3.2",
47
+ "nunjucks": "^3.2.4"
48
+ },
49
+ "devDependencies": {
50
+ "@types/cheerio": "^1.0.0",
51
+ "@types/node": "^20.19.11",
52
+ "@types/nunjucks": "^3.2.6",
53
+ "@typescript-eslint/eslint-plugin": "^8.30.1",
54
+ "@typescript-eslint/parser": "^8.30.1",
55
+ "@vue/eslint-config-prettier": "^10.2.0",
56
+ "@vue/eslint-config-typescript": "^14.5.0",
57
+ "eslint": "^9.34.0",
58
+ "eslint-config-prettier": "^10.1.5",
59
+ "eslint-import-resolver-alias": "^1.1.2",
60
+ "eslint-plugin-import": "^2.31.0",
61
+ "eslint-plugin-nuxt": "^4.0.0",
62
+ "eslint-plugin-prettier": "^5.4.1",
63
+ "eslint-plugin-vue": "^10.0.0",
64
+ "prettier": "^3.5.3",
65
+ "typescript": "^5.9.2",
66
+ "vitest": "^4.0.16",
67
+ "vue-eslint-parser": "^10.1.3"
68
+ }
69
+ }