@technomoron/mail-magic 1.0.38 → 1.0.40
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 +27 -0
- package/README.md +2 -2
- package/dist/cjs/package.json +1 -1
- package/dist/esm/api/mailer.js +4 -3
- package/dist/esm/bin/mail-magic.js +0 -0
- package/dist/esm/models/db.js +2 -2
- package/dist/esm/models/domain.js +0 -2
- package/dist/esm/models/form.js +0 -2
- package/dist/esm/models/init.js +7 -28
- package/dist/esm/models/recipient.js +0 -2
- package/dist/esm/models/txmail.js +0 -2
- package/dist/esm/models/user.js +0 -2
- package/dist/esm/store/envloader.js +5 -0
- package/dist/esm/store/store.js +5 -6
- package/dist/esm/util/forms.js +1 -3
- package/dist/esm/util/shared-template-flatten.js +41 -0
- package/docs/tutorial.md +2 -2
- package/package.json +88 -85
package/CHANGES
CHANGED
|
@@ -1,3 +1,30 @@
|
|
|
1
|
+
CHANGES
|
|
2
|
+
=======
|
|
3
|
+
|
|
4
|
+
Unreleased (2026-02-22)
|
|
5
|
+
|
|
6
|
+
- chore(release): add package-level `release:check` script and wire `release` to shared publish script.
|
|
7
|
+
- chore(scripts): replace `rm -rf` cleanup scripts with `rimraf`.
|
|
8
|
+
- test(logging): suppress noisy SQL/startup stdout in automated tests by default (`DB_LOG=false` in test setup; startup logs debug-only).
|
|
9
|
+
- test(logging): quiet package test output by default (silent Vitest + silent sync step) and remove Node `DEP0170` warning in schema-drift test DB setup.
|
|
10
|
+
- (Changes generated/assisted by Codex (profile: chatgpt-5.3-codex/medium).)
|
|
11
|
+
|
|
12
|
+
Version 1.0.40 (2026-02-22)
|
|
13
|
+
|
|
14
|
+
- chore(changes): normalize this package changelog to required CHANGES format.
|
|
15
|
+
- (Changes generated/assisted by Codex (profile: chatgpt-5.3-codex/medium).)
|
|
16
|
+
|
|
17
|
+
Version 1.0.39 (2026-02-19)
|
|
18
|
+
|
|
19
|
+
- Code clarity pass: restore `DB_LOG` env-driven logging config (was commented
|
|
20
|
+
out), normalize import path in `db.ts`.
|
|
21
|
+
- Simplify `create_mail_transport` by removing unnecessary spread and
|
|
22
|
+
intermediate variable.
|
|
23
|
+
- Simplify `buildReplyToValue` by removing redundant variable and duplicate
|
|
24
|
+
return path in `forms.ts`.
|
|
25
|
+
- Replace `forEach` with `for...of` and use separate `const` declarations in
|
|
26
|
+
`validateEmails` (`mailer.ts`).
|
|
27
|
+
|
|
1
28
|
Version 1.0.38 (2026-02-17)
|
|
2
29
|
|
|
3
30
|
- Prefer `fs.watch` for init-data auto-reload with automatic fallback to
|
package/README.md
CHANGED
|
@@ -308,5 +308,5 @@ pnpm -w --filter @technomoron/mail-magic cleanbuild
|
|
|
308
308
|
|
|
309
309
|
Documentation:
|
|
310
310
|
|
|
311
|
-
- `packages/
|
|
312
|
-
- `packages/
|
|
311
|
+
- `packages/server/docs/tutorial.md` is a hands-on config walkthrough.
|
|
312
|
+
- `packages/server/docs/form-security.md` covers the public form endpoint contract and recommended mitigations.
|
package/dist/cjs/package.json
CHANGED
package/dist/esm/api/mailer.js
CHANGED
|
@@ -11,12 +11,13 @@ export class MailerAPI extends ApiModule {
|
|
|
11
11
|
// and valid email addresses.
|
|
12
12
|
//
|
|
13
13
|
validateEmails(list) {
|
|
14
|
-
const valid = []
|
|
14
|
+
const valid = [];
|
|
15
|
+
const invalid = [];
|
|
15
16
|
const emails = list
|
|
16
17
|
.split(',')
|
|
17
18
|
.map((email) => email.trim())
|
|
18
19
|
.filter((email) => email !== '');
|
|
19
|
-
|
|
20
|
+
for (const email of emails) {
|
|
20
21
|
const addr = validateEmail(email);
|
|
21
22
|
if (addr) {
|
|
22
23
|
valid.push(addr);
|
|
@@ -24,7 +25,7 @@ export class MailerAPI extends ApiModule {
|
|
|
24
25
|
else {
|
|
25
26
|
invalid.push(email);
|
|
26
27
|
}
|
|
27
|
-
}
|
|
28
|
+
}
|
|
28
29
|
return { valid, invalid };
|
|
29
30
|
}
|
|
30
31
|
// Store a template in the database
|
|
File without changes
|
package/dist/esm/models/db.js
CHANGED
|
@@ -97,10 +97,10 @@ export async function init_api_db(db, store) {
|
|
|
97
97
|
store.print_debug('API Database Initialized...');
|
|
98
98
|
}
|
|
99
99
|
export async function connect_api_db(store) {
|
|
100
|
-
|
|
100
|
+
store.print_debug('DB INIT');
|
|
101
101
|
const env = store.vars;
|
|
102
102
|
const dbparams = {
|
|
103
|
-
logging:
|
|
103
|
+
logging: env.DB_LOG ? console.log : false,
|
|
104
104
|
dialect: env.DB_TYPE,
|
|
105
105
|
dialectOptions: {
|
|
106
106
|
charset: 'utf8mb4'
|
|
@@ -15,8 +15,6 @@ export const api_domain_schema = z
|
|
|
15
15
|
is_default: z.boolean().default(false).describe('If true, this is the default domain for the user.')
|
|
16
16
|
})
|
|
17
17
|
.describe('Domain configuration record.');
|
|
18
|
-
// Sequelize typing pattern: merge the Zod-inferred attribute type onto the model instance type.
|
|
19
|
-
// eslint-disable-next-line @typescript-eslint/no-unsafe-declaration-merging
|
|
20
18
|
export class api_domain extends Model {
|
|
21
19
|
}
|
|
22
20
|
export async function init_api_domain(api_db) {
|
package/dist/esm/models/form.js
CHANGED
|
@@ -68,8 +68,6 @@ export const api_form_schema = z
|
|
|
68
68
|
.describe('Derived list of template-referenced assets (inline cids and external links) resolved during preprocessing/import.')
|
|
69
69
|
})
|
|
70
70
|
.describe('Form configuration and template used by the public form submission endpoint.');
|
|
71
|
-
// Sequelize typing pattern: merge the Zod-inferred attribute type onto the model instance type.
|
|
72
|
-
// eslint-disable-next-line @typescript-eslint/no-unsafe-declaration-merging
|
|
73
71
|
export class api_form extends Model {
|
|
74
72
|
}
|
|
75
73
|
export async function init_api_form(api_db) {
|
package/dist/esm/models/init.js
CHANGED
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
import fs from 'fs';
|
|
2
2
|
import path from 'path';
|
|
3
|
-
import { Unyuck } from '@technomoron/unyuck';
|
|
4
3
|
import { z } from 'zod';
|
|
5
4
|
import { buildAssetUrl } from '../util/paths.js';
|
|
5
|
+
import { flattenTemplateWithAssets } from '../util/shared-template-flatten.js';
|
|
6
6
|
import { user_and_domain } from '../util.js';
|
|
7
7
|
import { api_domain, api_domain_schema } from './domain.js';
|
|
8
8
|
import { api_form_schema, upsert_form } from './form.js';
|
|
@@ -52,35 +52,14 @@ async function _load_template(store, filename, pathname, user, domain, locale, t
|
|
|
52
52
|
}
|
|
53
53
|
const assetBaseUrl = store.vars.ASSET_PUBLIC_BASE?.trim() ? store.vars.ASSET_PUBLIC_BASE : store.vars.API_URL;
|
|
54
54
|
const assetRoute = store.vars.ASSET_ROUTE;
|
|
55
|
-
const
|
|
56
|
-
|
|
55
|
+
const { html, assets } = flattenTemplateWithAssets({
|
|
56
|
+
domainRoot,
|
|
57
|
+
templateKey,
|
|
57
58
|
baseUrl: assetBaseUrl,
|
|
58
|
-
|
|
59
|
-
|
|
59
|
+
assetFormatter: (urlPath) => buildAssetUrl(assetBaseUrl, assetRoute, domain.name, urlPath),
|
|
60
|
+
normalizeInlineCid: buildInlineAssetCid
|
|
60
61
|
});
|
|
61
|
-
|
|
62
|
-
let html = mergedHtml;
|
|
63
|
-
const mappedAssets = assets.map((asset) => {
|
|
64
|
-
const rel = asset.filename.replace(/\\/g, '/');
|
|
65
|
-
const urlPath = rel.startsWith('assets/') ? rel.slice('assets/'.length) : rel;
|
|
66
|
-
return {
|
|
67
|
-
filename: urlPath,
|
|
68
|
-
path: asset.path,
|
|
69
|
-
cid: asset.cid ? buildInlineAssetCid(urlPath) : undefined
|
|
70
|
-
};
|
|
71
|
-
});
|
|
72
|
-
for (const asset of assets) {
|
|
73
|
-
if (!asset.cid) {
|
|
74
|
-
continue;
|
|
75
|
-
}
|
|
76
|
-
const rel = asset.filename.replace(/\\/g, '/');
|
|
77
|
-
const urlPath = rel.startsWith('assets/') ? rel.slice('assets/'.length) : rel;
|
|
78
|
-
const desiredCid = buildInlineAssetCid(urlPath);
|
|
79
|
-
if (asset.cid !== desiredCid) {
|
|
80
|
-
html = html.replaceAll(`cid:${asset.cid}`, `cid:${desiredCid}`);
|
|
81
|
-
}
|
|
82
|
-
}
|
|
83
|
-
return { html, assets: mappedAssets };
|
|
62
|
+
return { html, assets: assets };
|
|
84
63
|
}
|
|
85
64
|
catch (err) {
|
|
86
65
|
throw new Error(`Template "${absPath}" failed to preprocess: ${err.message}`);
|
|
@@ -11,8 +11,6 @@ export const api_recipient_schema = z
|
|
|
11
11
|
name: z.string().default('').describe('Optional recipient display name.')
|
|
12
12
|
})
|
|
13
13
|
.describe('Recipient routing record for form submissions.');
|
|
14
|
-
// Sequelize typing pattern: merge the Zod-inferred attribute type onto the model instance type.
|
|
15
|
-
// eslint-disable-next-line @typescript-eslint/no-unsafe-declaration-merging
|
|
16
14
|
export class api_recipient extends Model {
|
|
17
15
|
}
|
|
18
16
|
export async function init_api_recipient(api_db) {
|
|
@@ -26,8 +26,6 @@ export const api_txmail_schema = z
|
|
|
26
26
|
.describe('Derived list of template-referenced assets resolved during preprocessing/import.')
|
|
27
27
|
})
|
|
28
28
|
.describe('Transactional email template configuration.');
|
|
29
|
-
// Sequelize typing pattern: merge the Zod-inferred attribute type onto the model instance type.
|
|
30
|
-
// eslint-disable-next-line @typescript-eslint/no-unsafe-declaration-merging
|
|
31
29
|
export class api_txmail extends Model {
|
|
32
30
|
}
|
|
33
31
|
export async function upsert_txmail(record) {
|
package/dist/esm/models/user.js
CHANGED
|
@@ -13,8 +13,6 @@ export const api_user_schema = z
|
|
|
13
13
|
locale: z.string().default('').describe('Default locale for the user.')
|
|
14
14
|
})
|
|
15
15
|
.describe('User account record and API credentials.');
|
|
16
|
-
// Sequelize typing pattern: merge the Zod-inferred attribute type onto the model instance type.
|
|
17
|
-
// eslint-disable-next-line @typescript-eslint/no-unsafe-declaration-merging
|
|
18
16
|
export class api_user extends Model {
|
|
19
17
|
}
|
|
20
18
|
export function apiTokenToHmac(token, pepper) {
|
|
@@ -67,6 +67,11 @@ export const envOptions = defineEnvOptions({
|
|
|
67
67
|
description: 'Path to directory where config files are located',
|
|
68
68
|
default: './data/'
|
|
69
69
|
},
|
|
70
|
+
GEN_ENV_TEMPLATE: {
|
|
71
|
+
description: 'Write .env-dist to current working directory on startup',
|
|
72
|
+
default: false,
|
|
73
|
+
type: 'boolean'
|
|
74
|
+
},
|
|
70
75
|
DB_USER: {
|
|
71
76
|
description: 'Database username for API database'
|
|
72
77
|
},
|
package/dist/esm/store/store.js
CHANGED
|
@@ -22,10 +22,7 @@ function create_mail_transport(vars) {
|
|
|
22
22
|
if (user && pass) {
|
|
23
23
|
args.auth = { user, pass };
|
|
24
24
|
}
|
|
25
|
-
|
|
26
|
-
...args
|
|
27
|
-
});
|
|
28
|
-
return mailer;
|
|
25
|
+
return createTransport(args);
|
|
29
26
|
}
|
|
30
27
|
export function enableInitDataAutoReload(ctx, reload) {
|
|
31
28
|
if (!ctx.vars.DB_AUTO_RELOAD) {
|
|
@@ -167,10 +164,12 @@ export class mailStore {
|
|
|
167
164
|
if (this.vars.FORM_CAPTCHA_REQUIRED && !String(this.vars.FORM_CAPTCHA_SECRET ?? '').trim()) {
|
|
168
165
|
throw new Error('FORM_CAPTCHA_SECRET must be set when FORM_CAPTCHA_REQUIRED=true');
|
|
169
166
|
}
|
|
170
|
-
|
|
167
|
+
if (this.vars.GEN_ENV_TEMPLATE) {
|
|
168
|
+
EnvLoader.genTemplate(envOptions, '.env-dist');
|
|
169
|
+
}
|
|
171
170
|
const p = this.vars.CONFIG_PATH;
|
|
172
171
|
this.configpath = path.isAbsolute(p) ? p : path.resolve(process.cwd(), p);
|
|
173
|
-
|
|
172
|
+
this.print_debug(`Config path is ${this.configpath}`);
|
|
174
173
|
if (this.vars.UPLOAD_PATH && this.vars.UPLOAD_PATH.includes('{domain}')) {
|
|
175
174
|
this.uploadTemplate = this.vars.UPLOAD_PATH;
|
|
176
175
|
this.uploadStagingPath = path.resolve(this.configpath, '_uploads');
|
package/dist/esm/util/forms.js
CHANGED
|
@@ -90,15 +90,13 @@ export async function enforceCaptchaPolicy(params) {
|
|
|
90
90
|
}
|
|
91
91
|
export function buildReplyToValue(form, fields) {
|
|
92
92
|
const forced = typeof form.replyto_email === 'string' ? form.replyto_email.trim() : '';
|
|
93
|
-
const forcedValue = forced ? forced : '';
|
|
94
93
|
if (form.replyto_from_fields) {
|
|
95
94
|
const extracted = extractReplyToFromSubmission(fields);
|
|
96
95
|
if (extracted) {
|
|
97
96
|
return extracted;
|
|
98
97
|
}
|
|
99
|
-
return forcedValue || undefined;
|
|
100
98
|
}
|
|
101
|
-
return
|
|
99
|
+
return forced || undefined;
|
|
102
100
|
}
|
|
103
101
|
export function parseIdnameList(value, field) {
|
|
104
102
|
if (value === undefined || value === null || value === '') {
|
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
// AUTO-GENERATED by scripts/sync-shared-code.cjs. Do not edit directly.
|
|
2
|
+
import { Unyuck } from '@technomoron/unyuck';
|
|
3
|
+
function defaultInlineAssetCid(urlPath) {
|
|
4
|
+
const normalized = String(urlPath || '')
|
|
5
|
+
.trim()
|
|
6
|
+
.replace(/\\/g, '/');
|
|
7
|
+
const safe = normalized.replace(/[^A-Za-z0-9._-]/g, '_').replace(/_+/g, '_');
|
|
8
|
+
return (safe || 'asset').slice(0, 200);
|
|
9
|
+
}
|
|
10
|
+
export function flattenTemplateWithAssets(options) {
|
|
11
|
+
const processor = new Unyuck({
|
|
12
|
+
basePath: options.domainRoot,
|
|
13
|
+
baseUrl: options.baseUrl,
|
|
14
|
+
collectAssets: true,
|
|
15
|
+
assetFormatter: (ctx) => options.assetFormatter(ctx.urlPath)
|
|
16
|
+
});
|
|
17
|
+
const { html: mergedHtml, assets } = processor.flattenWithAssets(options.templateKey);
|
|
18
|
+
let html = mergedHtml;
|
|
19
|
+
const normalizeCid = options.normalizeInlineCid ?? defaultInlineAssetCid;
|
|
20
|
+
const mappedAssets = assets.map((asset) => {
|
|
21
|
+
const rel = asset.filename.replace(/\\/g, '/');
|
|
22
|
+
const urlPath = rel.startsWith('assets/') ? rel.slice('assets/'.length) : rel;
|
|
23
|
+
return {
|
|
24
|
+
filename: urlPath,
|
|
25
|
+
path: asset.path,
|
|
26
|
+
cid: asset.cid ? normalizeCid(urlPath) : undefined
|
|
27
|
+
};
|
|
28
|
+
});
|
|
29
|
+
for (const asset of assets) {
|
|
30
|
+
if (!asset.cid) {
|
|
31
|
+
continue;
|
|
32
|
+
}
|
|
33
|
+
const rel = asset.filename.replace(/\\/g, '/');
|
|
34
|
+
const urlPath = rel.startsWith('assets/') ? rel.slice('assets/'.length) : rel;
|
|
35
|
+
const desiredCid = normalizeCid(urlPath);
|
|
36
|
+
if (asset.cid !== desiredCid) {
|
|
37
|
+
html = html.split(`cid:${asset.cid}`).join(`cid:${desiredCid}`);
|
|
38
|
+
}
|
|
39
|
+
}
|
|
40
|
+
return { html, assets: mappedAssets };
|
|
41
|
+
}
|
package/docs/tutorial.md
CHANGED
|
@@ -19,7 +19,7 @@ export CONFIG_ROOT=$(realpath ../myorg-config)
|
|
|
19
19
|
|
|
20
20
|
Update your `.env` (or runtime environment) to point at the new workspace:
|
|
21
21
|
|
|
22
|
-
```
|
|
22
|
+
```dotenv
|
|
23
23
|
API_TOKEN_PEPPER=<generate-a-long-random-string>
|
|
24
24
|
CONFIG_PATH=${CONFIG_ROOT}
|
|
25
25
|
DB_AUTO_RELOAD=1 # optional: hot-reload init-data and templates
|
|
@@ -41,7 +41,7 @@ mkdir -p \
|
|
|
41
41
|
|
|
42
42
|
The resulting tree should look like this (logo placement shown for clarity — add the file in step 4):
|
|
43
43
|
|
|
44
|
-
```
|
|
44
|
+
```text
|
|
45
45
|
myorg-config/
|
|
46
46
|
├── init-data.json
|
|
47
47
|
└── myorg.com/
|
package/package.json
CHANGED
|
@@ -1,86 +1,89 @@
|
|
|
1
1
|
{
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
2
|
+
"name": "@technomoron/mail-magic",
|
|
3
|
+
"version": "1.0.40",
|
|
4
|
+
"main": "dist/cjs/index.js",
|
|
5
|
+
"module": "dist/esm/index.js",
|
|
6
|
+
"type": "module",
|
|
7
|
+
"bin": {
|
|
8
|
+
"mail-magic": "dist/esm/bin/mail-magic.js"
|
|
9
|
+
},
|
|
10
|
+
"exports": {
|
|
11
|
+
".": {
|
|
12
|
+
"import": "./dist/esm/index.js",
|
|
13
|
+
"require": "./dist/cjs/index.js"
|
|
14
|
+
}
|
|
15
|
+
},
|
|
16
|
+
"files": [
|
|
17
|
+
"dist",
|
|
18
|
+
"docs",
|
|
19
|
+
"README.md",
|
|
20
|
+
"CHANGES"
|
|
21
|
+
],
|
|
22
|
+
"repository": {
|
|
23
|
+
"type": "git",
|
|
24
|
+
"url": "git+https://github.com/technomoron/mail-magic.git",
|
|
25
|
+
"directory": "packages/server"
|
|
26
|
+
},
|
|
27
|
+
"keywords": [],
|
|
28
|
+
"author": "Bjørn Erik Jacobsen",
|
|
29
|
+
"license": "MIT",
|
|
30
|
+
"copyright": "Copyright (c) 2025 Bjørn Erik Jacobsen",
|
|
31
|
+
"bugs": {
|
|
32
|
+
"url": "https://github.com/technomoron/mail-magic/issues"
|
|
33
|
+
},
|
|
34
|
+
"dependencies": {
|
|
35
|
+
"@technomoron/api-server-base": "2.0.0-beta.20",
|
|
36
|
+
"@technomoron/env-loader": "^1.0.8",
|
|
37
|
+
"@technomoron/unyuck": "^1.0.4",
|
|
38
|
+
"bcryptjs": "^3.0.2",
|
|
39
|
+
"dotenv": "^16.4.5",
|
|
40
|
+
"email-addresses": "^5.0.0",
|
|
41
|
+
"html-to-text": "^9.0.5",
|
|
42
|
+
"nanoid": "^5.1.6",
|
|
43
|
+
"nodemailer": "^6.10.1",
|
|
44
|
+
"nunjucks": "^3.2.4",
|
|
45
|
+
"sequelize": "^6.37.7",
|
|
46
|
+
"sqlite3": "^5.1.7",
|
|
47
|
+
"swagger-jsdoc": "^6.2.8",
|
|
48
|
+
"swagger-ui-express": "^5.0.1",
|
|
49
|
+
"zod": "^4.1.5"
|
|
50
|
+
},
|
|
51
|
+
"devDependencies": {
|
|
52
|
+
"@types/express": "^5.0.6",
|
|
53
|
+
"@types/html-to-text": "^9.0.4",
|
|
54
|
+
"@types/nodemailer": "^6.4.19",
|
|
55
|
+
"@types/nunjucks": "^3.2.6",
|
|
56
|
+
"@types/supertest": "^6.0.3",
|
|
57
|
+
"mailparser": "^3.9.1",
|
|
58
|
+
"nodemon": "^3.1.10",
|
|
59
|
+
"smtp-server": "^3.18.0",
|
|
60
|
+
"supertest": "^7.1.4",
|
|
61
|
+
"tsx": "^4.20.5",
|
|
62
|
+
"typescript": "^5.9.3",
|
|
63
|
+
"vitest": "^4.0.16"
|
|
64
|
+
},
|
|
65
|
+
"homepage": "https://github.com/technomoron/mail-magic#readme",
|
|
66
|
+
"scripts": {
|
|
67
|
+
"start": "node dist/esm/index.js",
|
|
68
|
+
"dev": "NODE_ENV=development nodemon --watch 'src/**/*.ts' --watch 'config/**/*.*' --watch '.env' --exec 'tsx' src/index.ts",
|
|
69
|
+
"run": "NODE_ENV=production npm run start",
|
|
70
|
+
"sync:shared": "node ../../scripts/sync-shared-code.cjs >/dev/null",
|
|
71
|
+
"build:esm": "tsc --project tsconfig/tsconfig.esm.json",
|
|
72
|
+
"build:cjs": "node scripts/add-shebang.cjs --cjs-only",
|
|
73
|
+
"build": "run-s sync:shared build:esm build:cjs",
|
|
74
|
+
"postbuild": "node scripts/add-shebang.cjs",
|
|
75
|
+
"test:unit": "vitest run --silent --reporter=dot",
|
|
76
|
+
"test": "run-s --silent sync:shared test:unit",
|
|
77
|
+
"test:watch": "vitest",
|
|
78
|
+
"scrub": "rimraf ./node_modules/ ./dist/ pnpm-lock.yaml package-lock.json yarn.lock",
|
|
79
|
+
"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 ./",
|
|
80
|
+
"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 ./",
|
|
81
|
+
"pretty": "node ../../node_modules/prettier/bin/prettier.cjs --config ../../.prettierrc --write \"**/*.{js,jsx,cjs,mjs,ts,tsx,mts,vue,json,md}\"",
|
|
82
|
+
"format": "run-s lintfix pretty",
|
|
83
|
+
"cleanbuild": "run-s clean:dist format build",
|
|
84
|
+
"lintconfig": "node ../../lintconfig.cjs",
|
|
85
|
+
"clean:dist": "rimraf ./dist/",
|
|
86
|
+
"release": "bash ../../scripts/release-package.sh .",
|
|
87
|
+
"release:check": "bash ../../scripts/release-package-check.sh ."
|
|
88
|
+
}
|
|
89
|
+
}
|