@technomoron/mail-magic 1.0.37 → 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 CHANGED
@@ -1,3 +1,39 @@
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
+
28
+ Version 1.0.38 (2026-02-17)
29
+
30
+ - Prefer `fs.watch` for init-data auto-reload with automatic fallback to
31
+ `fs.watchFile` when native file watching is unavailable.
32
+ - Add regression tests for auto-reload watcher behavior (`watch`, fallback,
33
+ disabled mode).
34
+ - Add schema-drift regression tests that assert Zod schema keys match Sequelize
35
+ model attributes for core API models.
36
+
1
37
  Version 1.0.37 (2026-02-17)
2
38
 
3
39
  - Remove stale commented-out debug code from server mailer/store paths.
package/README.md CHANGED
@@ -308,5 +308,5 @@ pnpm -w --filter @technomoron/mail-magic cleanbuild
308
308
 
309
309
  Documentation:
310
310
 
311
- - `packages/mail-magic/docs/tutorial.md` is a hands-on config walkthrough.
312
- - `packages/mail-magic/docs/form-security.md` covers the public form endpoint contract and recommended mitigations.
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.
@@ -1,3 +1,3 @@
1
1
  {
2
- "type": "commonjs"
2
+ "type": "commonjs"
3
3
  }
@@ -11,12 +11,13 @@ export class MailerAPI extends ApiModule {
11
11
  // and valid email addresses.
12
12
  //
13
13
  validateEmails(list) {
14
- const valid = [], invalid = [];
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
- emails.forEach((email) => {
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
@@ -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
- console.log('DB INIT');
100
+ store.print_debug('DB INIT');
101
101
  const env = store.vars;
102
102
  const dbparams = {
103
- logging: false, // env.DB_LOG ? console.log : false,
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) {
@@ -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) {
@@ -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 processor = new Unyuck({
56
- basePath: domainRoot,
55
+ const { html, assets } = flattenTemplateWithAssets({
56
+ domainRoot,
57
+ templateKey,
57
58
  baseUrl: assetBaseUrl,
58
- collectAssets: true,
59
- assetFormatter: (ctx) => buildAssetUrl(assetBaseUrl, assetRoute, domain.name, ctx.urlPath)
59
+ assetFormatter: (urlPath) => buildAssetUrl(assetBaseUrl, assetRoute, domain.name, urlPath),
60
+ normalizeInlineCid: buildInlineAssetCid
60
61
  });
61
- const { html: mergedHtml, assets } = processor.flattenWithAssets(templateKey);
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) {
@@ -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
  },
@@ -22,10 +22,36 @@ function create_mail_transport(vars) {
22
22
  if (user && pass) {
23
23
  args.auth = { user, pass };
24
24
  }
25
- const mailer = createTransport({
26
- ...args
27
- });
28
- return mailer;
25
+ return createTransport(args);
26
+ }
27
+ export function enableInitDataAutoReload(ctx, reload) {
28
+ if (!ctx.vars.DB_AUTO_RELOAD) {
29
+ return null;
30
+ }
31
+ const initDataPath = ctx.config_filename('init-data.json');
32
+ ctx.print_debug('Enabling auto reload of init-data.json');
33
+ const onChange = () => {
34
+ ctx.print_debug('Config file changed, reloading...');
35
+ try {
36
+ reload();
37
+ }
38
+ catch (err) {
39
+ ctx.print_debug(`Failed to reload config: ${err}`);
40
+ }
41
+ };
42
+ try {
43
+ const watcher = fs.watch(initDataPath, { persistent: false }, onChange);
44
+ return {
45
+ close: () => watcher.close()
46
+ };
47
+ }
48
+ catch (err) {
49
+ ctx.print_debug(`fs.watch unavailable; falling back to fs.watchFile: ${err}`);
50
+ fs.watchFile(initDataPath, { interval: 2000 }, onChange);
51
+ return {
52
+ close: () => fs.unwatchFile(initDataPath, onChange)
53
+ };
54
+ }
29
55
  }
30
56
  export class mailStore {
31
57
  env;
@@ -35,6 +61,7 @@ export class mailStore {
35
61
  configpath = '';
36
62
  uploadTemplate;
37
63
  uploadStagingPath;
64
+ autoReloadHandle = null;
38
65
  print_debug(msg) {
39
66
  if (this.vars.DEBUG) {
40
67
  console.log(msg);
@@ -137,10 +164,12 @@ export class mailStore {
137
164
  if (this.vars.FORM_CAPTCHA_REQUIRED && !String(this.vars.FORM_CAPTCHA_SECRET ?? '').trim()) {
138
165
  throw new Error('FORM_CAPTCHA_SECRET must be set when FORM_CAPTCHA_REQUIRED=true');
139
166
  }
140
- EnvLoader.genTemplate(envOptions, '.env-dist');
167
+ if (this.vars.GEN_ENV_TEMPLATE) {
168
+ EnvLoader.genTemplate(envOptions, '.env-dist');
169
+ }
141
170
  const p = this.vars.CONFIG_PATH;
142
171
  this.configpath = path.isAbsolute(p) ? p : path.resolve(process.cwd(), p);
143
- console.log(`Config path is ${this.configpath}`);
172
+ this.print_debug(`Config path is ${this.configpath}`);
144
173
  if (this.vars.UPLOAD_PATH && this.vars.UPLOAD_PATH.includes('{domain}')) {
145
174
  this.uploadTemplate = this.vars.UPLOAD_PATH;
146
175
  this.uploadStagingPath = path.resolve(this.configpath, '_uploads');
@@ -153,18 +182,8 @@ export class mailStore {
153
182
  }
154
183
  this.transport = await create_mail_transport(this.vars);
155
184
  this.api_db = await connect_api_db(this);
156
- if (this.vars.DB_AUTO_RELOAD) {
157
- this.print_debug('Enabling auto reload of init-data.json');
158
- fs.watchFile(this.config_filename('init-data.json'), { interval: 2000 }, () => {
159
- this.print_debug('Config file changed, reloading...');
160
- try {
161
- importData(this);
162
- }
163
- catch (err) {
164
- this.print_debug(`Failed to reload config: ${err}`);
165
- }
166
- });
167
- }
185
+ this.autoReloadHandle?.close();
186
+ this.autoReloadHandle = enableInitDataAutoReload(this, () => importData(this));
168
187
  return this;
169
188
  }
170
189
  }
@@ -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 forcedValue || undefined;
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
- "name": "@technomoron/mail-magic",
3
- "version": "1.0.37",
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/mail-magic"
26
- },
27
- "scripts": {
28
- "start": "node dist/esm/index.js",
29
- "dev": "NODE_ENV=development nodemon --watch 'src/**/*.ts' --watch 'config/**/*.*' --watch '.env' --exec 'tsx' src/index.ts",
30
- "run": "NODE_ENV=production npm run start",
31
- "build:esm": "tsc --project tsconfig/tsconfig.esm.json",
32
- "build:cjs": "node scripts/add-shebang.cjs --cjs-only",
33
- "build": "npm run build:esm && npm run build:cjs",
34
- "postbuild": "node scripts/add-shebang.cjs",
35
- "prepack": "npm run build",
36
- "test": "vitest run",
37
- "test:watch": "vitest",
38
- "scrub": "rm -rf ./node_modules/ ./dist/ pnpm-lock.yaml",
39
- "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 ./",
40
- "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 ./",
41
- "pretty": "node ../../node_modules/prettier/bin/prettier.cjs --config ../../.prettierrc --write \"**/*.{js,jsx,cjs,mjs,ts,tsx,mts,vue,json,md}\"",
42
- "format": "npm run lintfix && npm run pretty",
43
- "cleanbuild": "rm -rf ./dist/ && npm run format && npm run build",
44
- "lintconfig": "node ../../lintconfig.cjs"
45
- },
46
- "keywords": [],
47
- "author": "Bjørn Erik Jacobsen",
48
- "license": "MIT",
49
- "copyright": "Copyright (c) 2025 Bjørn Erik Jacobsen",
50
- "bugs": {
51
- "url": "https://github.com/technomoron/mail-magic/issues"
52
- },
53
- "dependencies": {
54
- "@technomoron/api-server-base": "2.0.0-beta.18",
55
- "@technomoron/env-loader": "^1.0.8",
56
- "@technomoron/unyuck": "^1.0.4",
57
- "bcryptjs": "^3.0.2",
58
- "dotenv": "^16.4.5",
59
- "email-addresses": "^5.0.0",
60
- "html-to-text": "^9.0.5",
61
- "nanoid": "^5.1.6",
62
- "nodemailer": "^6.10.1",
63
- "nunjucks": "^3.2.4",
64
- "sequelize": "^6.37.7",
65
- "sqlite3": "^5.1.7",
66
- "swagger-jsdoc": "^6.2.8",
67
- "swagger-ui-express": "^5.0.1",
68
- "zod": "^4.1.5"
69
- },
70
- "devDependencies": {
71
- "@types/express": "^5.0.6",
72
- "@types/html-to-text": "^9.0.4",
73
- "@types/nodemailer": "^6.4.19",
74
- "@types/nunjucks": "^3.2.6",
75
- "@types/supertest": "^6.0.3",
76
- "mailparser": "^3.9.1",
77
- "nodemon": "^3.1.10",
78
- "smtp-server": "^3.18.0",
79
- "supertest": "^7.1.4",
80
- "tsx": "^4.20.5",
81
- "typescript": "^5.9.3",
82
- "vitest": "^4.0.16"
83
- },
84
- "homepage": "https://github.com/technomoron/mail-magic#readme",
85
- "packageManager": "pnpm@10.26.1+sha512.664074abc367d2c9324fdc18037097ce0a8f126034160f709928e9e9f95d98714347044e5c3164d65bd5da6c59c6be362b107546292a8eecb7999196e5ce58fa"
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
+ }