@technomoron/mail-magic-client 1.0.23 → 1.0.28
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 +33 -0
- package/README.md +73 -7
- package/dist/cjs/mail-magic-client.d.ts +36 -19
- package/dist/cjs/mail-magic-client.js +75 -24
- package/dist/cli.js +34 -6
- package/dist/esm/mail-magic-client.js +75 -24
- package/dist/mail-magic-client.js +75 -24
- package/dist/preprocess.js +19 -12
- package/package.json +6 -23
package/CHANGES
CHANGED
|
@@ -1,3 +1,36 @@
|
|
|
1
|
+
Version 1.0.29 (2026-02-11)
|
|
2
|
+
|
|
3
|
+
- Expand client coverage for mail-magic-owned endpoints:
|
|
4
|
+
- Add `storeFormRecipient()` for `POST /api/v1/form/recipient`.
|
|
5
|
+
- Add `getSwaggerSpec()` for `GET /api/swagger`.
|
|
6
|
+
- Add `fetchPublicAsset()` for public asset routes (`/asset/...` and `/api/asset/...`).
|
|
7
|
+
- Extend `storeFormTemplate()` input support for newer form fields:
|
|
8
|
+
`replyto_email`, `replyto_from_fields`, `allowed_fields`, and `captcha_required`.
|
|
9
|
+
- Add tests for the new client methods and route mappings.
|
|
10
|
+
- Refresh README usage docs for recipient upserts, swagger retrieval, and public asset fetch helpers.
|
|
11
|
+
|
|
12
|
+
Version 1.0.28 (2026-02-08)
|
|
13
|
+
|
|
14
|
+
- Refresh README/examples to match current server auth and public form submission semantics (`_mm_form_key`,
|
|
15
|
+
`_mm_locale`, `_mm_recipients`).
|
|
16
|
+
- Update `sendFormMessage()` to require `_mm_form_key` and default attachment multipart fields to `_mm_fileN`.
|
|
17
|
+
|
|
18
|
+
Version 1.0.27 (2026-02-07)
|
|
19
|
+
|
|
20
|
+
- Harden the template compiler to prevent `{% include %}` paths from escaping the
|
|
21
|
+
configured template root directory.
|
|
22
|
+
|
|
23
|
+
Version 1.0.26 (2026-02-07)
|
|
24
|
+
|
|
25
|
+
- Fix `GET` requests to omit request bodies and only set `Content-Type` when
|
|
26
|
+
sending JSON payloads.
|
|
27
|
+
- `mm-cli version` now reports the package version instead of a hardcoded string.
|
|
28
|
+
|
|
29
|
+
Version 1.0.25 (2026-02-07)
|
|
30
|
+
|
|
31
|
+
- `sendFormMessage()` now requires `domain` when sending by `formid`, and also
|
|
32
|
+
supports sending by `form_key`.
|
|
33
|
+
|
|
1
34
|
Version 1.0.23 (2026-01-31)
|
|
2
35
|
|
|
3
36
|
- Fixed the per-package release script name and tag format used to trigger
|
package/README.md
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
# mail-magic-client
|
|
1
|
+
# @technomoron/mail-magic-client
|
|
2
2
|
|
|
3
3
|
Client library and CLI for the mail-magic server.
|
|
4
4
|
|
|
@@ -13,7 +13,13 @@ npm install @technomoron/mail-magic-client
|
|
|
13
13
|
```ts
|
|
14
14
|
import TemplateClient from '@technomoron/mail-magic-client';
|
|
15
15
|
|
|
16
|
-
|
|
16
|
+
// Use the server origin (no /api).
|
|
17
|
+
const baseUrl = 'http://127.0.0.1:3776';
|
|
18
|
+
|
|
19
|
+
// This is the user token from init-data.json / the admin API.
|
|
20
|
+
const token = 'example-token';
|
|
21
|
+
|
|
22
|
+
const client = new TemplateClient(baseUrl, token);
|
|
17
23
|
|
|
18
24
|
await client.storeTxTemplate({
|
|
19
25
|
domain: 'example.test',
|
|
@@ -31,6 +37,62 @@ await client.sendTxMessage({
|
|
|
31
37
|
});
|
|
32
38
|
```
|
|
33
39
|
|
|
40
|
+
## Forms
|
|
41
|
+
|
|
42
|
+
Store/update a form template (authenticated). The response includes `data.form_key`, a stable random identifier (nanoid)
|
|
43
|
+
that is preferred for public form submissions:
|
|
44
|
+
|
|
45
|
+
```ts
|
|
46
|
+
const res = await client.storeFormTemplate({
|
|
47
|
+
domain: 'example.test',
|
|
48
|
+
idname: 'contact',
|
|
49
|
+
sender: 'Example Forms <forms@example.test>',
|
|
50
|
+
recipient: 'owner@example.test',
|
|
51
|
+
subject: 'New contact form submission',
|
|
52
|
+
secret: 's3cret',
|
|
53
|
+
template: '<p>Hello {{ _fields_.name }}</p>'
|
|
54
|
+
});
|
|
55
|
+
|
|
56
|
+
const form_key = res.data.form_key;
|
|
57
|
+
```
|
|
58
|
+
|
|
59
|
+
Store/update form recipient mappings (authenticated):
|
|
60
|
+
|
|
61
|
+
```ts
|
|
62
|
+
await client.storeFormRecipient({
|
|
63
|
+
domain: 'example.test',
|
|
64
|
+
idname: 'support',
|
|
65
|
+
email: 'Support <support@example.test>',
|
|
66
|
+
name: 'Support Team',
|
|
67
|
+
formid: 'contact',
|
|
68
|
+
locale: 'en'
|
|
69
|
+
});
|
|
70
|
+
```
|
|
71
|
+
|
|
72
|
+
Submit a form publicly (no auth required):
|
|
73
|
+
|
|
74
|
+
```ts
|
|
75
|
+
await fetch(`${baseUrl}/api/v1/form/message`, {
|
|
76
|
+
method: 'POST',
|
|
77
|
+
headers: { 'Content-Type': 'application/json' },
|
|
78
|
+
body: JSON.stringify({
|
|
79
|
+
_mm_form_key: form_key,
|
|
80
|
+
name: 'Sam',
|
|
81
|
+
email: 'sam@example.test',
|
|
82
|
+
message: 'Hello from the website'
|
|
83
|
+
})
|
|
84
|
+
});
|
|
85
|
+
```
|
|
86
|
+
|
|
87
|
+
If you want to use the client helper (`sendFormMessage()`), pass `_mm_form_key` (public form key):
|
|
88
|
+
|
|
89
|
+
```ts
|
|
90
|
+
await client.sendFormMessage({
|
|
91
|
+
_mm_form_key: form_key,
|
|
92
|
+
fields: { name: 'Sam', email: 'sam@example.test', message: 'Hello' }
|
|
93
|
+
});
|
|
94
|
+
```
|
|
95
|
+
|
|
34
96
|
## CLI
|
|
35
97
|
|
|
36
98
|
The package ships `mm-cli`.
|
|
@@ -40,14 +102,14 @@ The package ships `mm-cli`.
|
|
|
40
102
|
Create `.mmcli-env` in your working directory to set defaults:
|
|
41
103
|
|
|
42
104
|
```ini
|
|
43
|
-
MMCLI_API=http://
|
|
44
|
-
MMCLI_TOKEN=
|
|
45
|
-
# or, split token:
|
|
46
|
-
MMCLI_USERNAME=username
|
|
47
|
-
MMCLI_PASSWORD=token
|
|
105
|
+
MMCLI_API=http://127.0.0.1:3776
|
|
106
|
+
MMCLI_TOKEN=example-token
|
|
48
107
|
MMCLI_DOMAIN=example.test
|
|
49
108
|
```
|
|
50
109
|
|
|
110
|
+
`MMCLI_TOKEN` is treated as the server token string. As a convenience, `MMCLI_USERNAME` + `MMCLI_PASSWORD` can be used
|
|
111
|
+
to build a combined token string (for legacy setups).
|
|
112
|
+
|
|
51
113
|
### Template Commands
|
|
52
114
|
|
|
53
115
|
Compile a template locally:
|
|
@@ -100,3 +162,7 @@ mm-cli assets --file ./hero.png --domain example.test --template-type tx --templ
|
|
|
100
162
|
|
|
101
163
|
- `push-dir` expects a `init-data.json` and domain folders that match the server config layout.
|
|
102
164
|
- Asset uploads use the server endpoint `POST /api/v1/assets`.
|
|
165
|
+
- OpenAPI spec (when enabled): `await client.getSwaggerSpec()`
|
|
166
|
+
- Public asset fetch helpers:
|
|
167
|
+
- `await client.fetchPublicAsset('example.test', 'images/logo.png')` -> `/asset/{domain}/{path}`
|
|
168
|
+
- `await client.fetchPublicAsset('example.test', 'images/logo.png', true)` -> `/api/asset/{domain}/{path}`
|
|
@@ -1,3 +1,8 @@
|
|
|
1
|
+
type JsonPrimitive = string | number | boolean | null;
|
|
2
|
+
type JsonValue = JsonPrimitive | JsonValue[] | {
|
|
3
|
+
[key: string]: JsonValue;
|
|
4
|
+
};
|
|
5
|
+
type RequestBody = JsonValue | object;
|
|
1
6
|
interface templateData {
|
|
2
7
|
template: string;
|
|
3
8
|
domain: string;
|
|
@@ -16,25 +21,34 @@ interface formTemplateData {
|
|
|
16
21
|
subject?: string;
|
|
17
22
|
locale?: string;
|
|
18
23
|
secret?: string;
|
|
24
|
+
replyto_email?: string;
|
|
25
|
+
replyto_from_fields?: boolean;
|
|
26
|
+
allowed_fields?: string[] | string;
|
|
27
|
+
captcha_required?: boolean;
|
|
28
|
+
}
|
|
29
|
+
interface formRecipientData {
|
|
30
|
+
domain: string;
|
|
31
|
+
idname: string;
|
|
32
|
+
email: string;
|
|
33
|
+
name?: string;
|
|
34
|
+
form_key?: string;
|
|
35
|
+
formid?: string;
|
|
36
|
+
locale?: string;
|
|
19
37
|
}
|
|
20
38
|
interface sendTemplateData {
|
|
21
39
|
name: string;
|
|
22
40
|
rcpt: string;
|
|
23
41
|
domain: string;
|
|
24
42
|
locale?: string;
|
|
25
|
-
vars?:
|
|
43
|
+
vars?: Record<string, unknown>;
|
|
26
44
|
replyTo?: string;
|
|
27
45
|
headers?: Record<string, string>;
|
|
28
46
|
attachments?: AttachmentInput[];
|
|
29
47
|
}
|
|
30
48
|
interface sendFormData {
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
domain?: string;
|
|
35
|
-
locale?: string;
|
|
36
|
-
vars?: object;
|
|
37
|
-
replyTo?: string;
|
|
49
|
+
_mm_form_key: string;
|
|
50
|
+
_mm_locale?: string;
|
|
51
|
+
_mm_recipients?: string[] | string;
|
|
38
52
|
fields?: Record<string, unknown>;
|
|
39
53
|
attachments?: AttachmentInput[];
|
|
40
54
|
}
|
|
@@ -57,11 +71,11 @@ declare class templateClient {
|
|
|
57
71
|
private baseURL;
|
|
58
72
|
private apiKey;
|
|
59
73
|
constructor(baseURL: string, apiKey: string);
|
|
60
|
-
request<T>(method: 'GET' | 'POST' | 'PUT' | 'DELETE', command: string, body?:
|
|
74
|
+
request<T>(method: 'GET' | 'POST' | 'PUT' | 'DELETE', command: string, body?: RequestBody): Promise<T>;
|
|
61
75
|
get<T>(command: string): Promise<T>;
|
|
62
|
-
post<T>(command: string, body:
|
|
63
|
-
put<T>(command: string, body:
|
|
64
|
-
delete<T>(command: string, body?:
|
|
76
|
+
post<T>(command: string, body: RequestBody): Promise<T>;
|
|
77
|
+
put<T>(command: string, body: RequestBody): Promise<T>;
|
|
78
|
+
delete<T>(command: string, body?: RequestBody): Promise<T>;
|
|
65
79
|
validateEmails(list: string): {
|
|
66
80
|
valid: string[];
|
|
67
81
|
invalid: string[];
|
|
@@ -71,12 +85,15 @@ declare class templateClient {
|
|
|
71
85
|
private createAttachmentPayload;
|
|
72
86
|
private appendFields;
|
|
73
87
|
private postFormData;
|
|
74
|
-
storeTemplate(td: templateData): Promise<
|
|
75
|
-
sendTemplate(std: sendTemplateData): Promise<
|
|
76
|
-
storeTxTemplate(td: templateData): Promise<
|
|
77
|
-
sendTxMessage(std: sendTemplateData): Promise<
|
|
78
|
-
storeFormTemplate(data: formTemplateData): Promise<
|
|
79
|
-
|
|
80
|
-
|
|
88
|
+
storeTemplate(td: templateData): Promise<unknown>;
|
|
89
|
+
sendTemplate(std: sendTemplateData): Promise<unknown>;
|
|
90
|
+
storeTxTemplate(td: templateData): Promise<unknown>;
|
|
91
|
+
sendTxMessage(std: sendTemplateData): Promise<unknown>;
|
|
92
|
+
storeFormTemplate(data: formTemplateData): Promise<unknown>;
|
|
93
|
+
storeFormRecipient(data: formRecipientData): Promise<unknown>;
|
|
94
|
+
sendFormMessage(data: sendFormData): Promise<unknown>;
|
|
95
|
+
uploadAssets(data: uploadAssetsData): Promise<unknown>;
|
|
96
|
+
getSwaggerSpec(): Promise<unknown>;
|
|
97
|
+
fetchPublicAsset(domain: string, assetPath: string, viaApiBase?: boolean): Promise<ArrayBuffer>;
|
|
81
98
|
}
|
|
82
99
|
export default templateClient;
|
|
@@ -17,14 +17,19 @@ class templateClient {
|
|
|
17
17
|
}
|
|
18
18
|
async request(method, command, body) {
|
|
19
19
|
const url = `${this.baseURL}${command}`;
|
|
20
|
+
const headers = {
|
|
21
|
+
Accept: 'application/json',
|
|
22
|
+
Authorization: `Bearer apikey-${this.apiKey}`
|
|
23
|
+
};
|
|
20
24
|
const options = {
|
|
21
25
|
method,
|
|
22
|
-
headers
|
|
23
|
-
'Content-Type': 'application/json',
|
|
24
|
-
Authorization: `Bearer apikey-${this.apiKey}`
|
|
25
|
-
},
|
|
26
|
-
body: body ? JSON.stringify(body) : '{}'
|
|
26
|
+
headers
|
|
27
27
|
};
|
|
28
|
+
// Avoid GET bodies (they're non-standard and can break under some proxies).
|
|
29
|
+
if (method !== 'GET' && body !== undefined) {
|
|
30
|
+
headers['Content-Type'] = 'application/json';
|
|
31
|
+
options.body = JSON.stringify(body);
|
|
32
|
+
}
|
|
28
33
|
// console.log(JSON.stringify({ options, url }));
|
|
29
34
|
const response = await fetch(url, options);
|
|
30
35
|
const j = await response.json();
|
|
@@ -71,7 +76,7 @@ class templateClient {
|
|
|
71
76
|
validateTemplate(template) {
|
|
72
77
|
try {
|
|
73
78
|
const env = new nunjucks_1.default.Environment(new nunjucks_1.default.FileSystemLoader(['./templates']));
|
|
74
|
-
|
|
79
|
+
env.renderString(template, {});
|
|
75
80
|
}
|
|
76
81
|
catch (error) {
|
|
77
82
|
if (error instanceof Error) {
|
|
@@ -168,7 +173,7 @@ class templateClient {
|
|
|
168
173
|
if (!std.name || !std.rcpt) {
|
|
169
174
|
throw new Error('Invalid request body; name/rcpt required');
|
|
170
175
|
}
|
|
171
|
-
const {
|
|
176
|
+
const { invalid } = this.validateEmails(std.rcpt);
|
|
172
177
|
if (invalid.length > 0) {
|
|
173
178
|
throw new Error('Invalid email address(es): ' + invalid.join(','));
|
|
174
179
|
}
|
|
@@ -217,31 +222,46 @@ class templateClient {
|
|
|
217
222
|
this.validateSender(data.sender);
|
|
218
223
|
return this.post('/api/v1/form/template', data);
|
|
219
224
|
}
|
|
225
|
+
async storeFormRecipient(data) {
|
|
226
|
+
if (!data.domain) {
|
|
227
|
+
throw new Error('Missing domain');
|
|
228
|
+
}
|
|
229
|
+
if (!data.idname) {
|
|
230
|
+
throw new Error('Missing recipient identifier');
|
|
231
|
+
}
|
|
232
|
+
if (!data.email) {
|
|
233
|
+
throw new Error('Missing recipient email');
|
|
234
|
+
}
|
|
235
|
+
const parsed = email_addresses_1.default.parseOneAddress(data.email);
|
|
236
|
+
if (!parsed || !parsed.address) {
|
|
237
|
+
throw new Error('Invalid recipient email address');
|
|
238
|
+
}
|
|
239
|
+
return this.post('/api/v1/form/recipient', data);
|
|
240
|
+
}
|
|
220
241
|
async sendFormMessage(data) {
|
|
221
|
-
if (!data.
|
|
222
|
-
throw new Error('Invalid request body;
|
|
242
|
+
if (!data._mm_form_key) {
|
|
243
|
+
throw new Error('Invalid request body; _mm_form_key required');
|
|
223
244
|
}
|
|
224
245
|
const fields = data.fields || {};
|
|
225
246
|
const baseFields = {
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
domain: data.domain,
|
|
230
|
-
locale: data.locale,
|
|
231
|
-
vars: data.vars || {},
|
|
232
|
-
replyTo: data.replyTo,
|
|
247
|
+
_mm_form_key: data._mm_form_key,
|
|
248
|
+
_mm_locale: data._mm_locale,
|
|
249
|
+
_mm_recipients: data._mm_recipients,
|
|
233
250
|
...fields
|
|
234
251
|
};
|
|
235
252
|
if (data.attachments && data.attachments.length > 0) {
|
|
236
|
-
const
|
|
253
|
+
const normalized = data.attachments.map((attachment, idx) => {
|
|
254
|
+
const field = attachment.field || `_mm_file${idx + 1}`;
|
|
255
|
+
if (!field.startsWith('_mm_file')) {
|
|
256
|
+
throw new Error('Form attachments must use multipart field names starting with _mm_file');
|
|
257
|
+
}
|
|
258
|
+
return { ...attachment, field };
|
|
259
|
+
});
|
|
260
|
+
const { formData } = this.createAttachmentPayload(normalized);
|
|
237
261
|
this.appendFields(formData, {
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
domain: data.domain,
|
|
242
|
-
locale: data.locale,
|
|
243
|
-
vars: JSON.stringify(data.vars || {}),
|
|
244
|
-
replyTo: data.replyTo
|
|
262
|
+
_mm_form_key: data._mm_form_key,
|
|
263
|
+
_mm_locale: data._mm_locale,
|
|
264
|
+
_mm_recipients: data._mm_recipients
|
|
245
265
|
});
|
|
246
266
|
this.appendFields(formData, fields);
|
|
247
267
|
return this.postFormData('/api/v1/form/message', formData);
|
|
@@ -277,5 +297,36 @@ class templateClient {
|
|
|
277
297
|
});
|
|
278
298
|
return this.postFormData('/api/v1/assets', formData);
|
|
279
299
|
}
|
|
300
|
+
async getSwaggerSpec() {
|
|
301
|
+
return this.get('/api/swagger');
|
|
302
|
+
}
|
|
303
|
+
async fetchPublicAsset(domain, assetPath, viaApiBase = false) {
|
|
304
|
+
if (!domain) {
|
|
305
|
+
throw new Error('domain is required');
|
|
306
|
+
}
|
|
307
|
+
if (!assetPath) {
|
|
308
|
+
throw new Error('assetPath is required');
|
|
309
|
+
}
|
|
310
|
+
const cleanedPath = assetPath
|
|
311
|
+
.split('/')
|
|
312
|
+
.filter(Boolean)
|
|
313
|
+
.map((segment) => encodeURIComponent(segment))
|
|
314
|
+
.join('/');
|
|
315
|
+
if (!cleanedPath) {
|
|
316
|
+
throw new Error('assetPath is required');
|
|
317
|
+
}
|
|
318
|
+
const prefix = viaApiBase ? '/api/asset' : '/asset';
|
|
319
|
+
const url = `${this.baseURL}${prefix}/${encodeURIComponent(domain)}/${cleanedPath}`;
|
|
320
|
+
const response = await fetch(url, {
|
|
321
|
+
method: 'GET',
|
|
322
|
+
headers: {
|
|
323
|
+
Accept: '*/*'
|
|
324
|
+
}
|
|
325
|
+
});
|
|
326
|
+
if (!response.ok) {
|
|
327
|
+
throw new Error(`FETCH FAILED: ${response.status} ${response.statusText}`);
|
|
328
|
+
}
|
|
329
|
+
return response.arrayBuffer();
|
|
330
|
+
}
|
|
280
331
|
}
|
|
281
332
|
exports.default = templateClient;
|
package/dist/cli.js
CHANGED
|
@@ -5,6 +5,7 @@ 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"));
|
|
8
9
|
const readline_1 = __importDefault(require("readline"));
|
|
9
10
|
const commander_1 = require("commander");
|
|
10
11
|
const cli_env_1 = require("./cli-env");
|
|
@@ -15,6 +16,16 @@ const program = new commander_1.Command();
|
|
|
15
16
|
const envDefaults = (0, cli_env_1.loadCliEnv)();
|
|
16
17
|
const defaultToken = (0, cli_env_1.resolveToken)(envDefaults);
|
|
17
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
|
+
}
|
|
18
29
|
program.option('-a, --api <api>', 'Base API endpoint', apiDefault);
|
|
19
30
|
if (defaultToken) {
|
|
20
31
|
program.option('-t, --token <token>', 'Authentication token in the format "username:token"', defaultToken);
|
|
@@ -66,6 +77,24 @@ const getTemplateData = async () => {
|
|
|
66
77
|
return await readStdin();
|
|
67
78
|
}
|
|
68
79
|
};
|
|
80
|
+
const isPlainObject = (value) => typeof value === 'object' && value !== null && !Array.isArray(value);
|
|
81
|
+
const parseVarsOption = (input) => {
|
|
82
|
+
if (!input) {
|
|
83
|
+
return {};
|
|
84
|
+
}
|
|
85
|
+
let parsed;
|
|
86
|
+
try {
|
|
87
|
+
parsed = JSON.parse(input);
|
|
88
|
+
}
|
|
89
|
+
catch (error) {
|
|
90
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
91
|
+
throw new Error(`Invalid JSON for --vars: ${message}`);
|
|
92
|
+
}
|
|
93
|
+
if (!isPlainObject(parsed)) {
|
|
94
|
+
throw new Error('--vars must be a JSON object');
|
|
95
|
+
}
|
|
96
|
+
return parsed;
|
|
97
|
+
};
|
|
69
98
|
program
|
|
70
99
|
.command('template')
|
|
71
100
|
.description('Store a template on the server')
|
|
@@ -82,7 +111,7 @@ program
|
|
|
82
111
|
domain: program.opts().domain,
|
|
83
112
|
part: !!program.opts().part
|
|
84
113
|
};
|
|
85
|
-
|
|
114
|
+
await client.storeTemplate(templateData);
|
|
86
115
|
console.log('Template updated');
|
|
87
116
|
}
|
|
88
117
|
catch (error) {
|
|
@@ -101,8 +130,7 @@ program
|
|
|
101
130
|
.action(async () => {
|
|
102
131
|
const client = new mail_magic_client_1.default(program.opts().api, program.opts().token);
|
|
103
132
|
try {
|
|
104
|
-
const
|
|
105
|
-
const vars = program.opts().vars ? JSON.parse(program.opts().vars) : '{}';
|
|
133
|
+
const vars = parseVarsOption(program.opts().vars);
|
|
106
134
|
const templateData = {
|
|
107
135
|
name: program.opts().name,
|
|
108
136
|
rcpt: program.opts().rcpt,
|
|
@@ -110,7 +138,7 @@ program
|
|
|
110
138
|
locale: program.opts().locale,
|
|
111
139
|
vars
|
|
112
140
|
};
|
|
113
|
-
|
|
141
|
+
await client.sendTemplate(templateData);
|
|
114
142
|
console.log('Template sent');
|
|
115
143
|
}
|
|
116
144
|
catch (error) {
|
|
@@ -126,8 +154,8 @@ program
|
|
|
126
154
|
program
|
|
127
155
|
.command('version')
|
|
128
156
|
.description('Show current client version')
|
|
129
|
-
.action(async (
|
|
130
|
-
console.log(
|
|
157
|
+
.action(async () => {
|
|
158
|
+
console.log(resolvePackageVersion());
|
|
131
159
|
});
|
|
132
160
|
program
|
|
133
161
|
.command('compile')
|
|
@@ -12,14 +12,19 @@ class templateClient {
|
|
|
12
12
|
}
|
|
13
13
|
async request(method, command, body) {
|
|
14
14
|
const url = `${this.baseURL}${command}`;
|
|
15
|
+
const headers = {
|
|
16
|
+
Accept: 'application/json',
|
|
17
|
+
Authorization: `Bearer apikey-${this.apiKey}`
|
|
18
|
+
};
|
|
15
19
|
const options = {
|
|
16
20
|
method,
|
|
17
|
-
headers
|
|
18
|
-
'Content-Type': 'application/json',
|
|
19
|
-
Authorization: `Bearer apikey-${this.apiKey}`
|
|
20
|
-
},
|
|
21
|
-
body: body ? JSON.stringify(body) : '{}'
|
|
21
|
+
headers
|
|
22
22
|
};
|
|
23
|
+
// Avoid GET bodies (they're non-standard and can break under some proxies).
|
|
24
|
+
if (method !== 'GET' && body !== undefined) {
|
|
25
|
+
headers['Content-Type'] = 'application/json';
|
|
26
|
+
options.body = JSON.stringify(body);
|
|
27
|
+
}
|
|
23
28
|
// console.log(JSON.stringify({ options, url }));
|
|
24
29
|
const response = await fetch(url, options);
|
|
25
30
|
const j = await response.json();
|
|
@@ -66,7 +71,7 @@ class templateClient {
|
|
|
66
71
|
validateTemplate(template) {
|
|
67
72
|
try {
|
|
68
73
|
const env = new nunjucks.Environment(new nunjucks.FileSystemLoader(['./templates']));
|
|
69
|
-
|
|
74
|
+
env.renderString(template, {});
|
|
70
75
|
}
|
|
71
76
|
catch (error) {
|
|
72
77
|
if (error instanceof Error) {
|
|
@@ -163,7 +168,7 @@ class templateClient {
|
|
|
163
168
|
if (!std.name || !std.rcpt) {
|
|
164
169
|
throw new Error('Invalid request body; name/rcpt required');
|
|
165
170
|
}
|
|
166
|
-
const {
|
|
171
|
+
const { invalid } = this.validateEmails(std.rcpt);
|
|
167
172
|
if (invalid.length > 0) {
|
|
168
173
|
throw new Error('Invalid email address(es): ' + invalid.join(','));
|
|
169
174
|
}
|
|
@@ -212,31 +217,46 @@ class templateClient {
|
|
|
212
217
|
this.validateSender(data.sender);
|
|
213
218
|
return this.post('/api/v1/form/template', data);
|
|
214
219
|
}
|
|
220
|
+
async storeFormRecipient(data) {
|
|
221
|
+
if (!data.domain) {
|
|
222
|
+
throw new Error('Missing domain');
|
|
223
|
+
}
|
|
224
|
+
if (!data.idname) {
|
|
225
|
+
throw new Error('Missing recipient identifier');
|
|
226
|
+
}
|
|
227
|
+
if (!data.email) {
|
|
228
|
+
throw new Error('Missing recipient email');
|
|
229
|
+
}
|
|
230
|
+
const parsed = emailAddresses.parseOneAddress(data.email);
|
|
231
|
+
if (!parsed || !parsed.address) {
|
|
232
|
+
throw new Error('Invalid recipient email address');
|
|
233
|
+
}
|
|
234
|
+
return this.post('/api/v1/form/recipient', data);
|
|
235
|
+
}
|
|
215
236
|
async sendFormMessage(data) {
|
|
216
|
-
if (!data.
|
|
217
|
-
throw new Error('Invalid request body;
|
|
237
|
+
if (!data._mm_form_key) {
|
|
238
|
+
throw new Error('Invalid request body; _mm_form_key required');
|
|
218
239
|
}
|
|
219
240
|
const fields = data.fields || {};
|
|
220
241
|
const baseFields = {
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
domain: data.domain,
|
|
225
|
-
locale: data.locale,
|
|
226
|
-
vars: data.vars || {},
|
|
227
|
-
replyTo: data.replyTo,
|
|
242
|
+
_mm_form_key: data._mm_form_key,
|
|
243
|
+
_mm_locale: data._mm_locale,
|
|
244
|
+
_mm_recipients: data._mm_recipients,
|
|
228
245
|
...fields
|
|
229
246
|
};
|
|
230
247
|
if (data.attachments && data.attachments.length > 0) {
|
|
231
|
-
const
|
|
248
|
+
const normalized = data.attachments.map((attachment, idx) => {
|
|
249
|
+
const field = attachment.field || `_mm_file${idx + 1}`;
|
|
250
|
+
if (!field.startsWith('_mm_file')) {
|
|
251
|
+
throw new Error('Form attachments must use multipart field names starting with _mm_file');
|
|
252
|
+
}
|
|
253
|
+
return { ...attachment, field };
|
|
254
|
+
});
|
|
255
|
+
const { formData } = this.createAttachmentPayload(normalized);
|
|
232
256
|
this.appendFields(formData, {
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
domain: data.domain,
|
|
237
|
-
locale: data.locale,
|
|
238
|
-
vars: JSON.stringify(data.vars || {}),
|
|
239
|
-
replyTo: data.replyTo
|
|
257
|
+
_mm_form_key: data._mm_form_key,
|
|
258
|
+
_mm_locale: data._mm_locale,
|
|
259
|
+
_mm_recipients: data._mm_recipients
|
|
240
260
|
});
|
|
241
261
|
this.appendFields(formData, fields);
|
|
242
262
|
return this.postFormData('/api/v1/form/message', formData);
|
|
@@ -272,5 +292,36 @@ class templateClient {
|
|
|
272
292
|
});
|
|
273
293
|
return this.postFormData('/api/v1/assets', formData);
|
|
274
294
|
}
|
|
295
|
+
async getSwaggerSpec() {
|
|
296
|
+
return this.get('/api/swagger');
|
|
297
|
+
}
|
|
298
|
+
async fetchPublicAsset(domain, assetPath, viaApiBase = false) {
|
|
299
|
+
if (!domain) {
|
|
300
|
+
throw new Error('domain is required');
|
|
301
|
+
}
|
|
302
|
+
if (!assetPath) {
|
|
303
|
+
throw new Error('assetPath is required');
|
|
304
|
+
}
|
|
305
|
+
const cleanedPath = assetPath
|
|
306
|
+
.split('/')
|
|
307
|
+
.filter(Boolean)
|
|
308
|
+
.map((segment) => encodeURIComponent(segment))
|
|
309
|
+
.join('/');
|
|
310
|
+
if (!cleanedPath) {
|
|
311
|
+
throw new Error('assetPath is required');
|
|
312
|
+
}
|
|
313
|
+
const prefix = viaApiBase ? '/api/asset' : '/asset';
|
|
314
|
+
const url = `${this.baseURL}${prefix}/${encodeURIComponent(domain)}/${cleanedPath}`;
|
|
315
|
+
const response = await fetch(url, {
|
|
316
|
+
method: 'GET',
|
|
317
|
+
headers: {
|
|
318
|
+
Accept: '*/*'
|
|
319
|
+
}
|
|
320
|
+
});
|
|
321
|
+
if (!response.ok) {
|
|
322
|
+
throw new Error(`FETCH FAILED: ${response.status} ${response.statusText}`);
|
|
323
|
+
}
|
|
324
|
+
return response.arrayBuffer();
|
|
325
|
+
}
|
|
275
326
|
}
|
|
276
327
|
export default templateClient;
|
|
@@ -17,14 +17,19 @@ class templateClient {
|
|
|
17
17
|
}
|
|
18
18
|
async request(method, command, body) {
|
|
19
19
|
const url = `${this.baseURL}${command}`;
|
|
20
|
+
const headers = {
|
|
21
|
+
Accept: 'application/json',
|
|
22
|
+
Authorization: `Bearer apikey-${this.apiKey}`
|
|
23
|
+
};
|
|
20
24
|
const options = {
|
|
21
25
|
method,
|
|
22
|
-
headers
|
|
23
|
-
'Content-Type': 'application/json',
|
|
24
|
-
Authorization: `Bearer apikey-${this.apiKey}`
|
|
25
|
-
},
|
|
26
|
-
body: body ? JSON.stringify(body) : '{}'
|
|
26
|
+
headers
|
|
27
27
|
};
|
|
28
|
+
// Avoid GET bodies (they're non-standard and can break under some proxies).
|
|
29
|
+
if (method !== 'GET' && body !== undefined) {
|
|
30
|
+
headers['Content-Type'] = 'application/json';
|
|
31
|
+
options.body = JSON.stringify(body);
|
|
32
|
+
}
|
|
28
33
|
// console.log(JSON.stringify({ options, url }));
|
|
29
34
|
const response = await fetch(url, options);
|
|
30
35
|
const j = await response.json();
|
|
@@ -71,7 +76,7 @@ class templateClient {
|
|
|
71
76
|
validateTemplate(template) {
|
|
72
77
|
try {
|
|
73
78
|
const env = new nunjucks_1.default.Environment(new nunjucks_1.default.FileSystemLoader(['./templates']));
|
|
74
|
-
|
|
79
|
+
env.renderString(template, {});
|
|
75
80
|
}
|
|
76
81
|
catch (error) {
|
|
77
82
|
if (error instanceof Error) {
|
|
@@ -168,7 +173,7 @@ class templateClient {
|
|
|
168
173
|
if (!std.name || !std.rcpt) {
|
|
169
174
|
throw new Error('Invalid request body; name/rcpt required');
|
|
170
175
|
}
|
|
171
|
-
const {
|
|
176
|
+
const { invalid } = this.validateEmails(std.rcpt);
|
|
172
177
|
if (invalid.length > 0) {
|
|
173
178
|
throw new Error('Invalid email address(es): ' + invalid.join(','));
|
|
174
179
|
}
|
|
@@ -217,31 +222,46 @@ class templateClient {
|
|
|
217
222
|
this.validateSender(data.sender);
|
|
218
223
|
return this.post('/api/v1/form/template', data);
|
|
219
224
|
}
|
|
225
|
+
async storeFormRecipient(data) {
|
|
226
|
+
if (!data.domain) {
|
|
227
|
+
throw new Error('Missing domain');
|
|
228
|
+
}
|
|
229
|
+
if (!data.idname) {
|
|
230
|
+
throw new Error('Missing recipient identifier');
|
|
231
|
+
}
|
|
232
|
+
if (!data.email) {
|
|
233
|
+
throw new Error('Missing recipient email');
|
|
234
|
+
}
|
|
235
|
+
const parsed = email_addresses_1.default.parseOneAddress(data.email);
|
|
236
|
+
if (!parsed || !parsed.address) {
|
|
237
|
+
throw new Error('Invalid recipient email address');
|
|
238
|
+
}
|
|
239
|
+
return this.post('/api/v1/form/recipient', data);
|
|
240
|
+
}
|
|
220
241
|
async sendFormMessage(data) {
|
|
221
|
-
if (!data.
|
|
222
|
-
throw new Error('Invalid request body;
|
|
242
|
+
if (!data._mm_form_key) {
|
|
243
|
+
throw new Error('Invalid request body; _mm_form_key required');
|
|
223
244
|
}
|
|
224
245
|
const fields = data.fields || {};
|
|
225
246
|
const baseFields = {
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
domain: data.domain,
|
|
230
|
-
locale: data.locale,
|
|
231
|
-
vars: data.vars || {},
|
|
232
|
-
replyTo: data.replyTo,
|
|
247
|
+
_mm_form_key: data._mm_form_key,
|
|
248
|
+
_mm_locale: data._mm_locale,
|
|
249
|
+
_mm_recipients: data._mm_recipients,
|
|
233
250
|
...fields
|
|
234
251
|
};
|
|
235
252
|
if (data.attachments && data.attachments.length > 0) {
|
|
236
|
-
const
|
|
253
|
+
const normalized = data.attachments.map((attachment, idx) => {
|
|
254
|
+
const field = attachment.field || `_mm_file${idx + 1}`;
|
|
255
|
+
if (!field.startsWith('_mm_file')) {
|
|
256
|
+
throw new Error('Form attachments must use multipart field names starting with _mm_file');
|
|
257
|
+
}
|
|
258
|
+
return { ...attachment, field };
|
|
259
|
+
});
|
|
260
|
+
const { formData } = this.createAttachmentPayload(normalized);
|
|
237
261
|
this.appendFields(formData, {
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
domain: data.domain,
|
|
242
|
-
locale: data.locale,
|
|
243
|
-
vars: JSON.stringify(data.vars || {}),
|
|
244
|
-
replyTo: data.replyTo
|
|
262
|
+
_mm_form_key: data._mm_form_key,
|
|
263
|
+
_mm_locale: data._mm_locale,
|
|
264
|
+
_mm_recipients: data._mm_recipients
|
|
245
265
|
});
|
|
246
266
|
this.appendFields(formData, fields);
|
|
247
267
|
return this.postFormData('/api/v1/form/message', formData);
|
|
@@ -277,5 +297,36 @@ class templateClient {
|
|
|
277
297
|
});
|
|
278
298
|
return this.postFormData('/api/v1/assets', formData);
|
|
279
299
|
}
|
|
300
|
+
async getSwaggerSpec() {
|
|
301
|
+
return this.get('/api/swagger');
|
|
302
|
+
}
|
|
303
|
+
async fetchPublicAsset(domain, assetPath, viaApiBase = false) {
|
|
304
|
+
if (!domain) {
|
|
305
|
+
throw new Error('domain is required');
|
|
306
|
+
}
|
|
307
|
+
if (!assetPath) {
|
|
308
|
+
throw new Error('assetPath is required');
|
|
309
|
+
}
|
|
310
|
+
const cleanedPath = assetPath
|
|
311
|
+
.split('/')
|
|
312
|
+
.filter(Boolean)
|
|
313
|
+
.map((segment) => encodeURIComponent(segment))
|
|
314
|
+
.join('/');
|
|
315
|
+
if (!cleanedPath) {
|
|
316
|
+
throw new Error('assetPath is required');
|
|
317
|
+
}
|
|
318
|
+
const prefix = viaApiBase ? '/api/asset' : '/asset';
|
|
319
|
+
const url = `${this.baseURL}${prefix}/${encodeURIComponent(domain)}/${cleanedPath}`;
|
|
320
|
+
const response = await fetch(url, {
|
|
321
|
+
method: 'GET',
|
|
322
|
+
headers: {
|
|
323
|
+
Accept: '*/*'
|
|
324
|
+
}
|
|
325
|
+
});
|
|
326
|
+
if (!response.ok) {
|
|
327
|
+
throw new Error(`FETCH FAILED: ${response.status} ${response.statusText}`);
|
|
328
|
+
}
|
|
329
|
+
return response.arrayBuffer();
|
|
330
|
+
}
|
|
280
331
|
}
|
|
281
332
|
exports.default = templateClient;
|
package/dist/preprocess.js
CHANGED
|
@@ -31,7 +31,7 @@ function resolveCssPath(cssPath) {
|
|
|
31
31
|
}
|
|
32
32
|
return node_path_1.default.isAbsolute(cssPath) ? cssPath : node_path_1.default.join(process.cwd(), cssPath);
|
|
33
33
|
}
|
|
34
|
-
function inlineIncludes(content, baseDir, srcRoot, stack) {
|
|
34
|
+
function inlineIncludes(content, baseDir, srcRoot, normalizedSrcRoot, stack) {
|
|
35
35
|
const includeExp = /\{%\s*include\s+['"]([^'"]+)['"][^%]*%\}/g;
|
|
36
36
|
return content.replace(includeExp, (_match, includePath) => {
|
|
37
37
|
const cleaned = includePath.replace(/^\/+/, '');
|
|
@@ -41,12 +41,18 @@ function inlineIncludes(content, baseDir, srcRoot, stack) {
|
|
|
41
41
|
throw new Error(`Include not found: ${includePath}`);
|
|
42
42
|
}
|
|
43
43
|
const resolved = node_fs_1.default.realpathSync(found);
|
|
44
|
+
if (!resolved.startsWith(normalizedSrcRoot)) {
|
|
45
|
+
throw new Error(`Include path escapes template root: ${includePath}`);
|
|
46
|
+
}
|
|
47
|
+
if (!node_fs_1.default.statSync(resolved).isFile()) {
|
|
48
|
+
throw new Error(`Include is not a file: ${includePath}`);
|
|
49
|
+
}
|
|
44
50
|
if (stack.has(resolved)) {
|
|
45
51
|
throw new Error(`Circular include detected for ${includePath}`);
|
|
46
52
|
}
|
|
47
53
|
stack.add(resolved);
|
|
48
54
|
const raw = node_fs_1.default.readFileSync(resolved, 'utf8');
|
|
49
|
-
const inlined = inlineIncludes(raw, node_path_1.default.dirname(resolved), srcRoot, stack);
|
|
55
|
+
const inlined = inlineIncludes(raw, node_path_1.default.dirname(resolved), srcRoot, normalizedSrcRoot, stack);
|
|
50
56
|
stack.delete(resolved);
|
|
51
57
|
return inlined;
|
|
52
58
|
});
|
|
@@ -55,7 +61,6 @@ class PreprocessExtension {
|
|
|
55
61
|
constructor() {
|
|
56
62
|
this.tags = ['process_layout'];
|
|
57
63
|
}
|
|
58
|
-
// types from nunjucks are not exported for parser/nodes; use any
|
|
59
64
|
parse(parser, nodes) {
|
|
60
65
|
const token = parser.nextToken();
|
|
61
66
|
const args = parser.parseSignature(null, true);
|
|
@@ -96,12 +101,14 @@ function process_template(tplname, writeOutput = true) {
|
|
|
96
101
|
console.log(`Processing template: ${tplname}`);
|
|
97
102
|
try {
|
|
98
103
|
const srcRoot = resolvePathRoot(cfg.src_dir);
|
|
104
|
+
const resolvedSrcRoot = node_fs_1.default.realpathSync(srcRoot);
|
|
105
|
+
const normalizedSrcRoot = resolvedSrcRoot.endsWith(node_path_1.default.sep) ? resolvedSrcRoot : resolvedSrcRoot + node_path_1.default.sep;
|
|
99
106
|
const templateFile = node_path_1.default.join(srcRoot, `${tplname}.njk`);
|
|
100
107
|
// 1) Resolve template inheritance
|
|
101
108
|
const mergedTemplate = cfg.env.renderString(`{% process_layout "${tplname}.njk" %}`, {});
|
|
102
109
|
// 1.5) Inline partials/includes so the server doesn't need a loader
|
|
103
110
|
const mergedWithPartials = cfg.inline_includes
|
|
104
|
-
? inlineIncludes(mergedTemplate, node_path_1.default.dirname(templateFile), srcRoot, new Set())
|
|
111
|
+
? inlineIncludes(mergedTemplate, node_path_1.default.dirname(templateFile), srcRoot, normalizedSrcRoot, new Set())
|
|
105
112
|
: mergedTemplate;
|
|
106
113
|
// 2) Protect variables/flow
|
|
107
114
|
const protectedTemplate = cfg.env.filters.protect_variables(mergedWithPartials);
|
|
@@ -114,8 +121,8 @@ function process_template(tplname, writeOutput = true) {
|
|
|
114
121
|
// decodeEntities: false
|
|
115
122
|
});
|
|
116
123
|
// <container> -> <table>
|
|
117
|
-
$('container').each(
|
|
118
|
-
const $container = $(
|
|
124
|
+
$('container').each((_index, element) => {
|
|
125
|
+
const $container = $(element);
|
|
119
126
|
const $table = $('<table/>').attr({
|
|
120
127
|
align: 'center',
|
|
121
128
|
class: $container.attr('class') || '',
|
|
@@ -130,8 +137,8 @@ function process_template(tplname, writeOutput = true) {
|
|
|
130
137
|
$container.replaceWith($table);
|
|
131
138
|
});
|
|
132
139
|
// <row> -> <tr>
|
|
133
|
-
$('row').each(
|
|
134
|
-
const $row = $(
|
|
140
|
+
$('row').each((_index, element) => {
|
|
141
|
+
const $row = $(element);
|
|
135
142
|
const background = $row.attr('background') || '';
|
|
136
143
|
const $tr = $('<tr/>').attr({ class: $row.attr('class') || '' });
|
|
137
144
|
if (background)
|
|
@@ -140,8 +147,8 @@ function process_template(tplname, writeOutput = true) {
|
|
|
140
147
|
$row.replaceWith($tr);
|
|
141
148
|
});
|
|
142
149
|
// <columns> -> <td>
|
|
143
|
-
$('columns').each(
|
|
144
|
-
const $columns = $(
|
|
150
|
+
$('columns').each((_index, element) => {
|
|
151
|
+
const $columns = $(element);
|
|
145
152
|
const padding = $columns.attr('padding') || '0';
|
|
146
153
|
const $td = $('<td/>').attr({
|
|
147
154
|
class: $columns.attr('class') || '',
|
|
@@ -151,8 +158,8 @@ function process_template(tplname, writeOutput = true) {
|
|
|
151
158
|
$columns.replaceWith($td);
|
|
152
159
|
});
|
|
153
160
|
// <button> -> <a>
|
|
154
|
-
$('button').each(
|
|
155
|
-
const $button = $(
|
|
161
|
+
$('button').each((_index, element) => {
|
|
162
|
+
const $button = $(element);
|
|
156
163
|
const href = $button.attr('href') || '#';
|
|
157
164
|
const buttonClass = $button.attr('class') || '';
|
|
158
165
|
const $a = $('<a/>').attr({
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@technomoron/mail-magic-client",
|
|
3
|
-
"version": "1.0.
|
|
3
|
+
"version": "1.0.28",
|
|
4
4
|
"description": "Client library for mail-magic",
|
|
5
5
|
"main": "dist/cjs/mail-magic-client.js",
|
|
6
6
|
"types": "dist/cjs/mail-magic-client.d.ts",
|
|
@@ -17,11 +17,11 @@
|
|
|
17
17
|
"build": "npm run build:cjs && npm run build:esm && npm run build:cli",
|
|
18
18
|
"test": "vitest run",
|
|
19
19
|
"test:watch": "vitest",
|
|
20
|
-
"lint": "eslint --ext .js,.ts,.vue ./src",
|
|
21
|
-
"lintfix": "eslint --fix --ext .js,.ts,.vue,.json ./src",
|
|
20
|
+
"lint": "node ../../node_modules/eslint/bin/eslint.js --config ../../eslint.config.mjs --no-error-on-unmatched-pattern --ext .js,.cjs,.mjs,.ts,.mts,.tsx,.vue,.json ./src",
|
|
21
|
+
"lintfix": "node ../../node_modules/eslint/bin/eslint.js --config ../../eslint.config.mjs --fix --no-error-on-unmatched-pattern --ext .js,.cjs,.mjs,.ts,.mts,.tsx,.vue,.json ./src",
|
|
22
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
|
|
23
|
+
"pretty": "node ../../node_modules/prettier/bin/prettier.cjs --config ../../.prettierrc --write \"**/*.{js,ts,vue,json,css,scss,md}\"",
|
|
24
|
+
"cleanbuild": "rm -rf ./dist/ && npm run format && npm run build"
|
|
25
25
|
},
|
|
26
26
|
"repository": {
|
|
27
27
|
"type": "git",
|
|
@@ -40,30 +40,13 @@
|
|
|
40
40
|
"cheerio": "^1.1.2",
|
|
41
41
|
"commander": "^10.0.1",
|
|
42
42
|
"email-addresses": "^5.0.0",
|
|
43
|
-
"foundation-emails": "^2.4.0",
|
|
44
|
-
"inky": "^0.1.0",
|
|
45
43
|
"juice": "^11.0.1",
|
|
46
|
-
"node-fetch": "^3.3.2",
|
|
47
44
|
"nunjucks": "^3.2.4"
|
|
48
45
|
},
|
|
49
46
|
"devDependencies": {
|
|
50
|
-
"@types/cheerio": "^1.0.0",
|
|
51
47
|
"@types/node": "^20.19.11",
|
|
52
48
|
"@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
49
|
"typescript": "^5.9.2",
|
|
66
|
-
"vitest": "^4.0.16"
|
|
67
|
-
"vue-eslint-parser": "^10.1.3"
|
|
50
|
+
"vitest": "^4.0.16"
|
|
68
51
|
}
|
|
69
52
|
}
|