mirror of https://github.com/eleith/emailjs.git
smtp: complete typescript conversion
This commit is contained in:
parent
50b9ec18e8
commit
3bb741b3a8
12
email.ts
12
email.ts
|
@ -1,7 +1,5 @@
|
|||
import * as server from './smtp/client.js';
|
||||
import * as message from './smtp/message.js';
|
||||
import * as date from './smtp/date.js';
|
||||
import * as SMTP from './smtp/smtp.js';
|
||||
import * as error from './smtp/error.js';
|
||||
|
||||
export { server, message, date, SMTP, error };
|
||||
export * as client from './smtp/client';
|
||||
export * as message from './smtp/message';
|
||||
export * as date from './smtp/date';
|
||||
export * as SMTP from './smtp/smtp';
|
||||
export * as error from './smtp/error';
|
||||
|
|
116
package.json
116
package.json
|
@ -1,56 +1,72 @@
|
|||
{
|
||||
"name": "emailjs",
|
||||
"description": "send text/html emails and attachments (files, streams and strings) from node.js to any smtp server",
|
||||
"version": "2.2.0",
|
||||
"author": "eleith",
|
||||
"contributors": [
|
||||
"izuzak",
|
||||
"Hiverness",
|
||||
"mscdex",
|
||||
"jimmybergman",
|
||||
"zackschuster"
|
||||
],
|
||||
"repository": {
|
||||
"type": "git",
|
||||
"url": "http://github.com/eleith/emailjs.git"
|
||||
},
|
||||
"dependencies": {
|
||||
"addressparser": "^0.3.2",
|
||||
"emailjs-mime-codec": "^2.0.7"
|
||||
},
|
||||
"devDependencies": {
|
||||
"chai": "^4.1.2",
|
||||
"eslint": "^5.1.0",
|
||||
"eslint-config-prettier": "^2.9.0",
|
||||
"eslint-plugin-mocha": "^5.1.0",
|
||||
"eslint-plugin-prettier": "^2.6.2",
|
||||
"mailparser": "^2.2.0",
|
||||
"mocha": "^5.2.0",
|
||||
"prettier": "^1.13.7",
|
||||
"rollup": "^0.62.0",
|
||||
"rollup-plugin-commonjs": "^9.1.3",
|
||||
"rollup-plugin-node-resolve": "^3.3.0",
|
||||
"smtp-server": "^3.4.6"
|
||||
},
|
||||
"engine": [
|
||||
"node >= 6"
|
||||
],
|
||||
"name": "emailjs",
|
||||
"description": "send text/html emails and attachments (files, streams and strings) from node.js to any smtp server",
|
||||
"version": "2.2.0",
|
||||
"author": "eleith",
|
||||
"contributors": [
|
||||
"izuzak",
|
||||
"Hiverness",
|
||||
"mscdex",
|
||||
"jimmybergman",
|
||||
"zackschuster"
|
||||
],
|
||||
"repository": {
|
||||
"type": "git",
|
||||
"url": "http://github.com/eleith/emailjs.git"
|
||||
},
|
||||
"dependencies": {
|
||||
"addressparser": "^0.3.2",
|
||||
"emailjs-mime-codec": "^2.0.7"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@ledge/configs": "22.0.2",
|
||||
"@ledge/types": "6.1.0",
|
||||
"@types/mailparser": "2.7.2",
|
||||
"@types/smtp-server": "3.5.4",
|
||||
"ava": "3.7.1",
|
||||
"eslint": "^5.1.0",
|
||||
"eslint-config-prettier": "^2.9.0",
|
||||
"eslint-plugin-mocha": "^5.1.0",
|
||||
"eslint-plugin-prettier": "^2.6.2",
|
||||
"mailparser": "^2.2.0",
|
||||
"prettier": "^1.13.7",
|
||||
"rollup": "^0.62.0",
|
||||
"rollup-plugin-commonjs": "^9.1.3",
|
||||
"rollup-plugin-node-resolve": "^3.3.0",
|
||||
"smtp-server": "^3.4.6",
|
||||
"typescript": "3.8.3",
|
||||
"ts-node": "8.9.0"
|
||||
},
|
||||
"engine": [
|
||||
"node >= 10"
|
||||
],
|
||||
"main": "email.js",
|
||||
"type": "module",
|
||||
"scripts": {
|
||||
"scripts": {
|
||||
"rollup": "rollup -c rollup.config.js && npm run rollup:test",
|
||||
"rollup:test": "npm run test -- --file rollup/email.bundle.test.js",
|
||||
"test": "mocha"
|
||||
},
|
||||
"license": "MIT",
|
||||
"eslintIgnore": [
|
||||
"rollup.config.js",
|
||||
"rollup/email.bundle.js",
|
||||
"email.esm.js"
|
||||
],
|
||||
"prettier": {
|
||||
"singleQuote": true,
|
||||
"trailingComma": "es5",
|
||||
"useTabs": true
|
||||
}
|
||||
"test": "ava"
|
||||
},
|
||||
"license": "MIT",
|
||||
"ava": {
|
||||
"files": [
|
||||
"test/*.ts"
|
||||
],
|
||||
"extensions": [
|
||||
"ts"
|
||||
],
|
||||
"require": [
|
||||
"ts-node/register/transpile-only"
|
||||
]
|
||||
},
|
||||
"eslintIgnore": [
|
||||
"rollup.config.js",
|
||||
"rollup/email.bundle.js",
|
||||
"email.esm.js"
|
||||
],
|
||||
"prettier": {
|
||||
"singleQuote": true,
|
||||
"trailingComma": "es5",
|
||||
"useTabs": true
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,22 +1,23 @@
|
|||
// @ts-ignore
|
||||
import addressparser from 'addressparser';
|
||||
import { Message, create, MessageAttachment } from './message.js';
|
||||
import { SMTP, SMTPState } from './smtp.js';
|
||||
import { Message, MessageHeaders, MessageAttachment } from './message';
|
||||
import { SMTP, SMTPState, SMTPOptions } from './smtp';
|
||||
|
||||
export interface MessageStack {
|
||||
callback: (error: Error, message: Message) => void;
|
||||
callback: (error: Error | null, message: Message) => void;
|
||||
message: Message;
|
||||
attachment: MessageAttachment;
|
||||
text: string;
|
||||
returnPath: string;
|
||||
from: string;
|
||||
to: string | string[];
|
||||
to: string | { address: string }[];
|
||||
cc: string[];
|
||||
bcc: string[];
|
||||
}
|
||||
|
||||
class Client {
|
||||
export class Client {
|
||||
public smtp: SMTP;
|
||||
public queue: any[];
|
||||
public queue: MessageStack[] = []
|
||||
public timer: any;
|
||||
public sending: boolean;
|
||||
public ready: boolean;
|
||||
|
@ -44,15 +45,10 @@ class Client {
|
|||
* @constructor
|
||||
* @param {SMTPOptions} server smtp options
|
||||
*/
|
||||
constructor(server) {
|
||||
constructor(server: Partial<SMTPOptions>) {
|
||||
this.smtp = new SMTP(server);
|
||||
//this.smtp.debug(1);
|
||||
|
||||
/**
|
||||
* @type {MessageStack[]}
|
||||
*/
|
||||
this.queue = [];
|
||||
|
||||
/**
|
||||
* @type {NodeJS.Timer}
|
||||
*/
|
||||
|
@ -69,27 +65,16 @@ class Client {
|
|||
this.ready = false;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param {Message|MessageStack} msg msg
|
||||
* @param {function(Error, MessageStack): void} callback callback
|
||||
* @returns {void}
|
||||
*/
|
||||
send(msg, callback) {
|
||||
/**
|
||||
* @type {Message}
|
||||
*/
|
||||
const message =
|
||||
send(msg: Message, callback: (err: Error, msg: Message) => void): void {
|
||||
const message: Message | null =
|
||||
msg instanceof Message
|
||||
? msg
|
||||
: this._canMakeMessage(msg)
|
||||
? create(msg)
|
||||
? new Message(msg)
|
||||
: null;
|
||||
|
||||
if (message == null) {
|
||||
callback(
|
||||
new Error('message is not a valid Message instance'),
|
||||
/** @type {MessageStack} */ (msg)
|
||||
);
|
||||
callback(new Error('message is not a valid Message instance'), msg);
|
||||
return;
|
||||
}
|
||||
|
||||
|
@ -100,7 +85,7 @@ class Client {
|
|||
to: addressparser(message.header.to),
|
||||
from: addressparser(message.header.from)[0].address,
|
||||
callback: (callback || function() {}).bind(this),
|
||||
};
|
||||
} as MessageStack;
|
||||
|
||||
if (message.header.cc) {
|
||||
stack.to = stack.to.concat(addressparser(message.header.cc));
|
||||
|
@ -131,23 +116,23 @@ class Client {
|
|||
* @private
|
||||
* @returns {void}
|
||||
*/
|
||||
_poll() {
|
||||
_poll(): void {
|
||||
clearTimeout(this.timer);
|
||||
|
||||
if (this.queue.length) {
|
||||
if (this.smtp.state() == SMTPState.NOTCONNECTED) {
|
||||
if (this.queue.length > 0) {
|
||||
if (this.smtp.state == SMTPState.NOTCONNECTED) {
|
||||
this._connect(this.queue[0]);
|
||||
} else if (
|
||||
this.smtp.state() == SMTPState.CONNECTED &&
|
||||
this.smtp.state == SMTPState.CONNECTED &&
|
||||
!this.sending &&
|
||||
this.ready
|
||||
) {
|
||||
this._sendmail(this.queue.shift());
|
||||
this._sendmail(this.queue.shift() as MessageStack);
|
||||
}
|
||||
}
|
||||
// wait around 1 seconds in case something does come in,
|
||||
// otherwise close out SMTP connection if still open
|
||||
else if (this.smtp.state() == SMTPState.CONNECTED) {
|
||||
else if (this.smtp.state == SMTPState.CONNECTED) {
|
||||
this.timer = setTimeout(() => this.smtp.quit(), 1000);
|
||||
}
|
||||
}
|
||||
|
@ -157,14 +142,14 @@ class Client {
|
|||
* @param {MessageStack} stack stack
|
||||
* @returns {void}
|
||||
*/
|
||||
_connect(stack) {
|
||||
_connect(stack: MessageStack): void {
|
||||
/**
|
||||
* @param {Error} err callback error
|
||||
* @returns {void}
|
||||
*/
|
||||
const connect = err => {
|
||||
const connect = (err: Error): void => {
|
||||
if (!err) {
|
||||
const begin = err => {
|
||||
const begin = (err: Error) => {
|
||||
if (!err) {
|
||||
this.ready = true;
|
||||
this._poll();
|
||||
|
@ -177,7 +162,7 @@ class Client {
|
|||
}
|
||||
};
|
||||
|
||||
if (!this.smtp.authorized()) {
|
||||
if (!this.smtp.isAuthorized) {
|
||||
this.smtp.login(begin);
|
||||
} else {
|
||||
this.smtp.ehlo_or_helo_if_needed(begin);
|
||||
|
@ -200,11 +185,11 @@ class Client {
|
|||
* @param {MessageStack} msg message stack
|
||||
* @returns {boolean} can make message
|
||||
*/
|
||||
_canMakeMessage(msg) {
|
||||
return (
|
||||
_canMakeMessage(msg: MessageHeaders): boolean {
|
||||
return !!(
|
||||
msg.from &&
|
||||
(msg.to || msg.cc || msg.bcc) &&
|
||||
(msg.text !== undefined || this._containsInlinedHtml(msg.attachment))
|
||||
(msg.text != null || this._containsInlinedHtml(msg.attachment))
|
||||
);
|
||||
}
|
||||
|
||||
|
@ -213,7 +198,7 @@ class Client {
|
|||
* @param {*} attachment attachment
|
||||
* @returns {boolean} does contain
|
||||
*/
|
||||
_containsInlinedHtml(attachment) {
|
||||
_containsInlinedHtml(attachment: any): boolean {
|
||||
if (Array.isArray(attachment)) {
|
||||
return attachment.some(att => {
|
||||
return this._isAttachmentInlinedHtml(att);
|
||||
|
@ -228,7 +213,7 @@ class Client {
|
|||
* @param {*} attachment attachment
|
||||
* @returns {boolean} is inlined
|
||||
*/
|
||||
_isAttachmentInlinedHtml(attachment) {
|
||||
_isAttachmentInlinedHtml(attachment: any): boolean {
|
||||
return (
|
||||
attachment &&
|
||||
(attachment.data || attachment.path) &&
|
||||
|
@ -242,7 +227,7 @@ class Client {
|
|||
* @param {function(MessageStack): void} next next
|
||||
* @returns {function(Error): void} callback
|
||||
*/
|
||||
_sendsmtp(stack, next) {
|
||||
_sendsmtp(stack: MessageStack, next: (msg: MessageStack) => void): (err: Error) => void {
|
||||
/**
|
||||
* @param {Error} [err] error
|
||||
* @returns {void}
|
||||
|
@ -263,7 +248,7 @@ class Client {
|
|||
* @param {MessageStack} stack stack
|
||||
* @returns {void}
|
||||
*/
|
||||
_sendmail(stack) {
|
||||
_sendmail(stack: MessageStack): void {
|
||||
const from = stack.returnPath || stack.from;
|
||||
this.sending = true;
|
||||
this.smtp.mail(this._sendsmtp(stack, this._sendrcpt), '<' + from + '>');
|
||||
|
@ -274,12 +259,12 @@ class Client {
|
|||
* @param {MessageStack} stack stack
|
||||
* @returns {void}
|
||||
*/
|
||||
_sendrcpt(stack) {
|
||||
_sendrcpt(stack: MessageStack): void {
|
||||
if (stack.to == null || typeof stack.to === 'string') {
|
||||
throw new TypeError('stack.to must be array');
|
||||
}
|
||||
|
||||
const to = stack.to.shift().address;
|
||||
const { address: to } = stack.to.shift() ?? {};
|
||||
this.smtp.rcpt(
|
||||
this._sendsmtp(stack, stack.to.length ? this._sendrcpt : this._senddata),
|
||||
`<${to}>`
|
||||
|
@ -291,7 +276,7 @@ class Client {
|
|||
* @param {MessageStack} stack stack
|
||||
* @returns {void}
|
||||
*/
|
||||
_senddata(stack) {
|
||||
_senddata(stack: MessageStack): void {
|
||||
this.smtp.data(this._sendsmtp(stack, this._sendmessage));
|
||||
}
|
||||
|
||||
|
@ -300,7 +285,7 @@ class Client {
|
|||
* @param {MessageStack} stack stack
|
||||
* @returns {void}
|
||||
*/
|
||||
_sendmessage(stack) {
|
||||
_sendmessage(stack: MessageStack): void {
|
||||
const stream = stack.message.stream();
|
||||
|
||||
stream.on('data', data => this.smtp.message(data));
|
||||
|
@ -324,15 +309,9 @@ class Client {
|
|||
* @param {MessageStack} stack stack
|
||||
* @returns {void}
|
||||
*/
|
||||
_senddone(err, stack) {
|
||||
_senddone(err: Error | null, stack: MessageStack): void {
|
||||
this.sending = false;
|
||||
stack.callback(err, stack.message);
|
||||
this._poll();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @param {SMTPOptions} server smtp options
|
||||
* @returns {Client} the client
|
||||
*/
|
||||
export const connect = server => new Client(server);
|
||||
|
|
10
smtp/date.ts
10
smtp/date.ts
|
@ -1,7 +1,7 @@
|
|||
/**
|
||||
* @param {Date} [date] an optional date to convert to RFC2822 format
|
||||
* @param {boolean} [useUtc=false] whether to parse the date as UTC (default: false)
|
||||
* @returns {string} the converted date
|
||||
* @param [date] an optional date to convert to RFC2822 format
|
||||
* @param [useUtc] whether to parse the date as UTC (default: false)
|
||||
* @returns the converted date
|
||||
*/
|
||||
export function getRFC2822Date(date = new Date(), useUtc = false) {
|
||||
if (useUtc) {
|
||||
|
@ -24,8 +24,8 @@ export function getRFC2822Date(date = new Date(), useUtc = false) {
|
|||
}
|
||||
|
||||
/**
|
||||
* @param {Date} [date] an optional date to convert to RFC2822 format (UTC)
|
||||
* @returns {string} the converted date
|
||||
* @param [date] an optional date to convert to RFC2822 format (UTC)
|
||||
* @returns the converted date
|
||||
*/
|
||||
export function getRFC2822DateUTC(date = new Date()) {
|
||||
const dates = date.toUTCString().split(' ');
|
||||
|
|
228
smtp/message.ts
228
smtp/message.ts
|
@ -2,10 +2,13 @@ import fs from 'fs';
|
|||
import { hostname } from 'os';
|
||||
import { Stream, Duplex } from 'stream';
|
||||
|
||||
// @ts-ignore
|
||||
import addressparser from 'addressparser';
|
||||
import emailjsMimeCodec from 'emailjs-mime-codec';
|
||||
// @ts-ignore
|
||||
import { mimeWordEncode } from 'emailjs-mime-codec';
|
||||
import type { Indexed } from '@ledge/types';
|
||||
|
||||
import { getRFC2822Date } from './date.js';
|
||||
import { getRFC2822Date } from './date';
|
||||
|
||||
const CRLF = '\r\n';
|
||||
|
||||
|
@ -25,22 +28,22 @@ export const MIME64CHUNK: 456 = (MIMECHUNK * 6) as 456;
|
|||
export const BUFFERSIZE: 12768 = (MIMECHUNK * 24 * 7) as 12768;
|
||||
|
||||
|
||||
export interface MessageAttachmentHeaders {
|
||||
'content-type': string;
|
||||
'content-transfer-encoding': string;
|
||||
'content-disposition': string;
|
||||
export interface MessageAttachmentHeaders extends Indexed {
|
||||
'content-type'?: string;
|
||||
'content-transfer-encoding'?: string;
|
||||
'content-disposition'?: string;
|
||||
}
|
||||
|
||||
export interface AlternateMessageAttachment {
|
||||
export interface AlternateMessageAttachment extends Indexed {
|
||||
headers: MessageAttachmentHeaders;
|
||||
inline: boolean;
|
||||
alternative: MessageAttachment;
|
||||
related: MessageAttachment[];
|
||||
alternative?: MessageAttachment;
|
||||
related?: MessageAttachment[];
|
||||
data: any;
|
||||
encoded: any;
|
||||
encoded?: any;
|
||||
}
|
||||
|
||||
export interface MessageAttachment extends Partial<AlternateMessageAttachment> {
|
||||
export interface MessageAttachment extends AlternateMessageAttachment {
|
||||
name: string;
|
||||
type: string;
|
||||
charset: string;
|
||||
|
@ -50,11 +53,17 @@ export interface MessageAttachment extends Partial<AlternateMessageAttachment> {
|
|||
}
|
||||
|
||||
|
||||
export interface MessageHeaders {
|
||||
export interface MessageHeaders extends Indexed {
|
||||
'content-type': string;
|
||||
'message-id': string;
|
||||
date: string;
|
||||
from: string;
|
||||
to: string;
|
||||
cc: string;
|
||||
bcc: string;
|
||||
subject: string;
|
||||
text: string;
|
||||
attachment: MessageAttachment;
|
||||
text: string | null;
|
||||
attachment: MessageAttachment | MessageAttachment[];
|
||||
}
|
||||
|
||||
let counter: number = 0;
|
||||
|
@ -73,9 +82,9 @@ function generate_boundary() {
|
|||
|
||||
function convertPersonToAddress(person: string) {
|
||||
return addressparser(person)
|
||||
.map(({ name, address }) => {
|
||||
.map(({ name, address }: { name: string, address: string }) => {
|
||||
return name
|
||||
? `${emailjsMimeCodec.mimeWordEncode(name).replace(/,/g, '=2C')} <${address}>`
|
||||
? `${mimeWordEncode(name).replace(/,/g, '=2C')} <${address}>`
|
||||
: address;
|
||||
})
|
||||
.join(', ');
|
||||
|
@ -89,20 +98,12 @@ function convertDashDelimitedTextToSnakeCase(text: string) {
|
|||
|
||||
export class Message {
|
||||
attachments: any[] = [];
|
||||
alternative: Partial<MessageAttachment> | null = null;
|
||||
header: {
|
||||
'message-id': string;
|
||||
date: string;
|
||||
from?: string;
|
||||
subject?: string;
|
||||
to?: string;
|
||||
cc?: string;
|
||||
bcc?: string;
|
||||
};
|
||||
alternative: AlternateMessageAttachment | null = null;
|
||||
header: Partial<MessageHeaders>;
|
||||
content: string;
|
||||
text: any;
|
||||
|
||||
constructor(headers: MessageHeaders) {
|
||||
constructor(headers: Partial<MessageHeaders>) {
|
||||
|
||||
this.header = {
|
||||
'message-id': `<${new Date().getTime()}.${counter++}.${
|
||||
|
@ -118,20 +119,19 @@ export class Message {
|
|||
this.content = headers[header];
|
||||
} else if (header === 'text') {
|
||||
this.text = headers[header];
|
||||
} else if (
|
||||
header === 'attachment' &&
|
||||
typeof headers[header] === 'object'
|
||||
) {
|
||||
} else if (header === 'attachment') {
|
||||
const attachment = headers[header];
|
||||
if (Array.isArray(attachment)) {
|
||||
for (let i = 0; i < attachment.length; i++) {
|
||||
this.attach(attachment[i]);
|
||||
if (attachment != null) {
|
||||
if (Array.isArray(attachment)) {
|
||||
for (let i = 0; i < attachment.length; i++) {
|
||||
this.attach(attachment[i]);
|
||||
}
|
||||
} else {
|
||||
this.attach(attachment);
|
||||
}
|
||||
} else {
|
||||
this.attach(attachment);
|
||||
}
|
||||
} else if (header === 'subject') {
|
||||
this.header.subject = emailjsMimeCodec.mimeWordEncode(headers.subject);
|
||||
this.header.subject = mimeWordEncode(headers.subject);
|
||||
} else if (/^(cc|bcc|to|from)/i.test(header)) {
|
||||
this.header[header.toLowerCase()] = convertPersonToAddress(headers[header]);
|
||||
} else {
|
||||
|
@ -169,6 +169,9 @@ export class Message {
|
|||
*/
|
||||
attach_alternative(html: string, charset = 'utf-8'): Message {
|
||||
this.alternative = {
|
||||
headers: {
|
||||
|
||||
},
|
||||
data: html,
|
||||
charset,
|
||||
type: 'text/html',
|
||||
|
@ -182,7 +185,7 @@ export class Message {
|
|||
* @param {function(boolean, string): void} callback This callback is displayed as part of the Requester class.
|
||||
* @returns {void}
|
||||
*/
|
||||
valid(callback: (arg0: boolean, arg1: string) => void): void {
|
||||
valid(callback: (arg0: boolean, arg1?: string) => void): void {
|
||||
if (!this.header.from) {
|
||||
callback(false, 'message does not have a valid sender');
|
||||
}
|
||||
|
@ -192,7 +195,7 @@ export class Message {
|
|||
} else if (this.attachments.length === 0) {
|
||||
callback(true, undefined);
|
||||
} else {
|
||||
const failed = [];
|
||||
const failed: string[] = [];
|
||||
|
||||
this.attachments.forEach(attachment => {
|
||||
if (attachment.path) {
|
||||
|
@ -237,7 +240,7 @@ class MessageStream extends Stream {
|
|||
message: Message;
|
||||
readable: boolean;
|
||||
paused: boolean;
|
||||
buffer: Buffer;
|
||||
buffer: Buffer | null;
|
||||
bufferIndex: number;
|
||||
/**
|
||||
* @param {Message} message the message to stream
|
||||
|
@ -283,9 +286,11 @@ class MessageStream extends Stream {
|
|||
output_text(this.message);
|
||||
output_message(boundary, this.message.attachments, 0, close);
|
||||
} else {
|
||||
const cb = () =>
|
||||
output_message(boundary, this.message.attachments, 0, close);
|
||||
output_alternative(this.message, cb);
|
||||
output_alternative(
|
||||
// typescript bug; should narrow to { alternative: AlternateMessageAttachment }
|
||||
this.message as Parameters<typeof output_alternative>[0],
|
||||
() => output_message(boundary, this.message.attachments, 0, close)
|
||||
);
|
||||
}
|
||||
};
|
||||
|
||||
|
@ -315,12 +320,11 @@ class MessageStream extends Stream {
|
|||
};
|
||||
|
||||
/**
|
||||
* @param {MessageAttachment} attachment the metadata to use as headers
|
||||
* @returns {void}
|
||||
*/
|
||||
const output_attachment_headers = (attachment: Partial<MessageAttachment>): void => {
|
||||
let data = [];
|
||||
const headers = {
|
||||
const output_attachment_headers = (attachment: MessageAttachment | AlternateMessageAttachment): void => {
|
||||
let data: string[] = [];
|
||||
const headers: Partial<MessageHeaders> = {
|
||||
'content-type':
|
||||
attachment.type +
|
||||
(attachment.charset ? `; charset=${attachment.charset}` : '') +
|
||||
|
@ -328,7 +332,7 @@ class MessageStream extends Stream {
|
|||
'content-transfer-encoding': 'base64',
|
||||
'content-disposition': attachment.inline
|
||||
? 'inline'
|
||||
: `attachment; filename="${emailjsMimeCodec.mimeWordEncode(attachment.name)}"`,
|
||||
: `attachment; filename="${mimeWordEncode(attachment.name)}"`,
|
||||
};
|
||||
|
||||
// allow sender to override default headers
|
||||
|
@ -348,12 +352,7 @@ class MessageStream extends Stream {
|
|||
output(data.concat([CRLF]).join(''));
|
||||
};
|
||||
|
||||
/**
|
||||
* @param {MessageAttachment} attachment the metadata to use as headers
|
||||
* @param {function(): void} callback the function to call after output is finished
|
||||
* @returns {void}
|
||||
*/
|
||||
const output_attachment = (attachment: Partial<MessageAttachment>, callback: () => void): void => {
|
||||
const output_attachment = (attachment: MessageAttachment | AlternateMessageAttachment, callback: () => void): void => {
|
||||
const build = attachment.path
|
||||
? output_file
|
||||
: attachment.stream
|
||||
|
@ -368,7 +367,7 @@ class MessageStream extends Stream {
|
|||
* @param {function(): void} callback the function to call after output is finished
|
||||
* @returns {void}
|
||||
*/
|
||||
const output_data = (attachment: Partial<MessageAttachment>, callback: () => void): void => {
|
||||
const output_data = (attachment: MessageAttachment | AlternateMessageAttachment, callback: () => void): void => {
|
||||
output_base64(
|
||||
attachment.encoded
|
||||
? attachment.data
|
||||
|
@ -377,15 +376,10 @@ class MessageStream extends Stream {
|
|||
);
|
||||
};
|
||||
|
||||
/**
|
||||
* @param {MessageAttachment} attachment the metadata to use as headers
|
||||
* @param {function(NodeJS.ErrnoException): void} next the function to call when the file is closed
|
||||
* @returns {void}
|
||||
*/
|
||||
const output_file = (attachment: Partial<MessageAttachment>, next: (arg0: NodeJS.ErrnoException) => void): void => {
|
||||
const output_file = (attachment: MessageAttachment | AlternateMessageAttachment, next: (err: NodeJS.ErrnoException) => void): void => {
|
||||
const chunk = MIME64CHUNK * 16;
|
||||
const buffer = Buffer.alloc(chunk);
|
||||
const closed = fd => fs.closeSync(fd);
|
||||
const closed = (fd: number) => fs.closeSync(fd);
|
||||
|
||||
/**
|
||||
* @param {Error} err the error to emit
|
||||
|
@ -394,7 +388,7 @@ class MessageStream extends Stream {
|
|||
*/
|
||||
const opened = (err: Error, fd: number): void => {
|
||||
if (!err) {
|
||||
const read = (err, bytes) => {
|
||||
const read = (err: Error, bytes: number) => {
|
||||
if (!err && this.readable) {
|
||||
let encoding =
|
||||
attachment && attachment.headers
|
||||
|
@ -441,20 +435,20 @@ class MessageStream extends Stream {
|
|||
* @param {function(): void} callback the function to call after output is finished
|
||||
* @returns {void}
|
||||
*/
|
||||
const output_stream = (attachment: Partial<MessageAttachment>, callback: () => void): void => {
|
||||
const output_stream = (attachment: MessageAttachment | AlternateMessageAttachment, callback: () => void): void => {
|
||||
if (attachment.stream.readable) {
|
||||
let previous = Buffer.alloc(0);
|
||||
|
||||
attachment.stream.resume();
|
||||
|
||||
attachment.stream.on('end', () => {
|
||||
(attachment as MessageAttachment).on('end', () => {
|
||||
output_base64(previous.toString('base64'), callback);
|
||||
this.removeListener('pause', attachment.stream.pause);
|
||||
this.removeListener('resume', attachment.stream.resume);
|
||||
this.removeListener('error', attachment.stream.resume);
|
||||
});
|
||||
|
||||
attachment.stream.on('data', buff => {
|
||||
(attachment as MessageAttachment).stream.on('data', buff => {
|
||||
// do we have bytes from a previous stream data event?
|
||||
let buffer = Buffer.isBuffer(buff) ? buff : Buffer.from(buff);
|
||||
|
||||
|
@ -503,7 +497,7 @@ class MessageStream extends Stream {
|
|||
* @returns {void}
|
||||
*/
|
||||
const output_text = (message: Message): void => {
|
||||
let data = [];
|
||||
let data: string[] = [];
|
||||
|
||||
data = data.concat([
|
||||
'Content-Type:',
|
||||
|
@ -523,7 +517,7 @@ class MessageStream extends Stream {
|
|||
* @param {function(): void} callback the function to call after output is finished
|
||||
* @returns {void}
|
||||
*/
|
||||
const output_alternative = (message: Message, callback: () => void): void => {
|
||||
const output_alternative = (message: Message & { alternative: AlternateMessageAttachment }, callback: () => void): void => {
|
||||
const boundary = generate_boundary();
|
||||
output(
|
||||
`Content-Type: multipart/alternative; boundary="${boundary}"${CRLF}${CRLF}--${boundary}${CRLF}`
|
||||
|
@ -551,13 +545,13 @@ class MessageStream extends Stream {
|
|||
* @param {function(): void} callback the function to call after output is finished
|
||||
* @returns {void}
|
||||
*/
|
||||
const output_related = (message: Partial<MessageAttachment>, callback: () => void): void => {
|
||||
const output_related = (message: AlternateMessageAttachment, callback: () => void): void => {
|
||||
const boundary = generate_boundary();
|
||||
output(
|
||||
`Content-Type: multipart/related; boundary="${boundary}"${CRLF}${CRLF}--${boundary}${CRLF}`
|
||||
);
|
||||
output_attachment(message, () => {
|
||||
output_message(boundary, message.related, 0, () => {
|
||||
output_message(boundary, message.related ?? [], 0, () => {
|
||||
output(`${CRLF}--${boundary}--${CRLF}${CRLF}`);
|
||||
callback();
|
||||
});
|
||||
|
@ -582,7 +576,7 @@ class MessageStream extends Stream {
|
|||
* @returns {void}
|
||||
*/
|
||||
const output_header = (): void => {
|
||||
let data = [];
|
||||
let data: string[] = [];
|
||||
|
||||
for (const header in this.message.header) {
|
||||
// do not output BCC in the headers (regex) nor custom Object.prototype functions...
|
||||
|
@ -608,51 +602,56 @@ class MessageStream extends Stream {
|
|||
* @param [callback] the function
|
||||
* @param [args] array of arguments to pass to the callback
|
||||
*/
|
||||
const output = (data: string, callback?: (...args: any[]) => void, args?: any[]) => {
|
||||
const bytes = Buffer.byteLength(data);
|
||||
|
||||
const output = (data: string, callback?: (...args: any[]) => void, args: any[] = []) => {
|
||||
// can we buffer the data?
|
||||
if (bytes + this.bufferIndex < this.buffer.length) {
|
||||
this.buffer.write(data, this.bufferIndex);
|
||||
this.bufferIndex += bytes;
|
||||
if (callback) {
|
||||
callback.apply(null, args);
|
||||
}
|
||||
}
|
||||
// we can't buffer the data, so ship it out!
|
||||
else if (bytes > this.buffer.length) {
|
||||
if (this.bufferIndex) {
|
||||
this.emit('data', this.buffer.toString('utf-8', 0, this.bufferIndex));
|
||||
this.bufferIndex = 0;
|
||||
}
|
||||
if (this.buffer != null) {
|
||||
const bytes = Buffer.byteLength(data);
|
||||
|
||||
const loops = Math.ceil(data.length / this.buffer.length);
|
||||
let loop = 0;
|
||||
while (loop < loops) {
|
||||
this.emit(
|
||||
'data',
|
||||
data.substring(
|
||||
this.buffer.length * loop,
|
||||
this.buffer.length * (loop + 1)
|
||||
)
|
||||
);
|
||||
loop++;
|
||||
}
|
||||
} // we need to clean out the buffer, it is getting full
|
||||
else {
|
||||
if (!this.paused) {
|
||||
this.emit('data', this.buffer.toString('utf-8', 0, this.bufferIndex));
|
||||
this.buffer.write(data, 0);
|
||||
this.bufferIndex = bytes;
|
||||
// we could get paused after emitting data...
|
||||
if (this.paused) {
|
||||
this.once('resume', () => callback.apply(null, args));
|
||||
} else if (callback) {
|
||||
if ((bytes + this.bufferIndex) < this.buffer.length) {
|
||||
this.buffer.write(data, this.bufferIndex);
|
||||
this.bufferIndex += bytes;
|
||||
if (callback) {
|
||||
callback.apply(null, args);
|
||||
}
|
||||
} // we can't empty out the buffer, so let's wait till we resume before adding to it
|
||||
}
|
||||
// we can't buffer the data, so ship it out!
|
||||
else if (bytes > this.buffer.length) {
|
||||
if (this.bufferIndex) {
|
||||
this.emit('data', this.buffer.toString('utf-8', 0, this.bufferIndex));
|
||||
this.bufferIndex = 0;
|
||||
}
|
||||
|
||||
const loops = Math.ceil(data.length / this.buffer.length);
|
||||
let loop = 0;
|
||||
while (loop < loops) {
|
||||
this.emit(
|
||||
'data',
|
||||
data.substring(
|
||||
this.buffer.length * loop,
|
||||
this.buffer.length * (loop + 1)
|
||||
)
|
||||
);
|
||||
loop++;
|
||||
}
|
||||
} // we need to clean out the buffer, it is getting full
|
||||
else {
|
||||
this.once('resume', () => output(data, callback, args));
|
||||
if (!this.paused) {
|
||||
this.emit('data', this.buffer.toString('utf-8', 0, this.bufferIndex));
|
||||
this.buffer.write(data, 0);
|
||||
this.bufferIndex = bytes;
|
||||
// we could get paused after emitting data...
|
||||
|
||||
if (typeof callback === 'function') {
|
||||
if (this.paused) {
|
||||
this.once('resume', () => callback.apply(null, args));
|
||||
} else {
|
||||
callback.apply(null, args);
|
||||
}
|
||||
}
|
||||
} // we can't empty out the buffer, so let's wait till we resume before adding to it
|
||||
else {
|
||||
this.once('resume', () => output(data, callback, args));
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
|
@ -661,7 +660,7 @@ class MessageStream extends Stream {
|
|||
if (err) {
|
||||
this.emit('error', err);
|
||||
} else {
|
||||
this.emit('data', this.buffer.toString('utf-8', 0, this.bufferIndex));
|
||||
this.emit('data', this.buffer?.toString('utf-8', 0, this.bufferIndex) ?? '');
|
||||
this.emit('end');
|
||||
}
|
||||
this.buffer = null;
|
||||
|
@ -715,8 +714,3 @@ class MessageStream extends Stream {
|
|||
this.emit('destroy');
|
||||
}
|
||||
}
|
||||
|
||||
export /**
|
||||
* @param {{ content: string; subject?: string; text?: string; attachment?: MessageAttachment; }} headers
|
||||
*/
|
||||
const create = headers => new Message(headers);
|
||||
|
|
|
@ -1,7 +1,7 @@
|
|||
import { Socket } from 'net';
|
||||
import { TLSSocket } from 'tls';
|
||||
|
||||
import { makeSMTPError, SMTPErrorStates } from './error.js';
|
||||
import { makeSMTPError, SMTPErrorStates } from './error';
|
||||
|
||||
export class SMTPResponse {
|
||||
private buffer = '';
|
||||
|
@ -47,7 +47,7 @@ export class SMTPResponse {
|
|||
.trim()
|
||||
.split(/\n/)
|
||||
.pop()
|
||||
.match(/^(\d{3})\s/)
|
||||
?.match(/^(\d{3})\s/) ?? false
|
||||
) {
|
||||
return;
|
||||
}
|
||||
|
|
85
smtp/smtp.ts
85
smtp/smtp.ts
|
@ -4,8 +4,9 @@ import { hostname } from 'os';
|
|||
import { connect, createSecureContext, TLSSocket } from 'tls';
|
||||
import { EventEmitter } from 'events';
|
||||
|
||||
import { SMTPResponse, monitor } from './response.js';
|
||||
import { makeSMTPError, SMTPErrorStates } from './error.js';
|
||||
import { SMTPResponse, monitor } from './response';
|
||||
import { makeSMTPError, SMTPErrorStates } from './error';
|
||||
import { Indexed } from '@ledge/types';
|
||||
|
||||
/**
|
||||
* @readonly
|
||||
|
@ -38,15 +39,11 @@ const SMTP_TLS_PORT: 587 = 587;
|
|||
*/
|
||||
const CRLF: '\r\n' = '\r\n';
|
||||
|
||||
/**
|
||||
* @readonly
|
||||
* @enum
|
||||
*/
|
||||
export const AUTH_METHODS = {
|
||||
PLAIN: /** @type {'PLAIN'} */ ('PLAIN'),
|
||||
CRAM_MD5: /** @type {'CRAM-MD5'} */ ('CRAM-MD5'),
|
||||
LOGIN: /** @type {'LOGIN'} */ ('LOGIN'),
|
||||
XOAUTH2: /** @type {'XOAUTH2'} */ ('XOAUTH2'),
|
||||
export enum AUTH_METHODS {
|
||||
PLAIN = 'PLAIN',
|
||||
CRAM_MD5 = 'CRAM-MD5',
|
||||
LOGIN = 'LOGIN',
|
||||
XOAUTH2 = 'XOAUTH2',
|
||||
};
|
||||
|
||||
/**
|
||||
|
@ -87,7 +84,7 @@ const log = (...args: any[]): void => {
|
|||
* @param {...*} args the arguments to apply to the function
|
||||
* @returns {void}
|
||||
*/
|
||||
const caller = (callback: (...rest: any[]) => void, ...args: any[]): void => {
|
||||
const caller = (callback?: (...rest: any[]) => void, ...args: any[]): void => {
|
||||
if (typeof callback === 'function') {
|
||||
callback.apply(null, args);
|
||||
}
|
||||
|
@ -100,7 +97,7 @@ export interface SMTPSocketOptions {
|
|||
}
|
||||
|
||||
export interface SMTPOptions {
|
||||
timeout: number;
|
||||
timeout: number | null;
|
||||
user: string;
|
||||
password: string;
|
||||
domain: string;
|
||||
|
@ -120,8 +117,9 @@ export class SMTP extends EventEmitter {
|
|||
private _state: 0 | 1 | 2 = SMTPState.NOTCONNECTED;
|
||||
private _isAuthorized = false;
|
||||
private _isSecure = false;
|
||||
private _user = '';
|
||||
private _password = '';
|
||||
private _user?: string = '';
|
||||
private _password?: string = '';
|
||||
private _timeout: number = TIMEOUT;
|
||||
|
||||
public set debug(level: 0 | 1) {
|
||||
DEBUG = level;
|
||||
|
@ -131,6 +129,10 @@ export class SMTP extends EventEmitter {
|
|||
return this._state;
|
||||
}
|
||||
|
||||
public get timeout() {
|
||||
return this._timeout;
|
||||
}
|
||||
|
||||
public get user() {
|
||||
return this._user;
|
||||
}
|
||||
|
@ -144,10 +146,9 @@ export class SMTP extends EventEmitter {
|
|||
}
|
||||
|
||||
protected sock: Socket | TLSSocket | null = null;
|
||||
protected features: { [i: string]: string | boolean } | null = null;
|
||||
protected features: Indexed<string | boolean> = {};
|
||||
protected monitor: SMTPResponse | null = null;
|
||||
protected authentication: any[];
|
||||
protected timeout: number = TIMEOUT;
|
||||
protected domain = hostname();
|
||||
protected host = 'localhost';
|
||||
protected ssl: boolean | SMTPSocketOptions = false;
|
||||
|
@ -185,7 +186,7 @@ export class SMTP extends EventEmitter {
|
|||
];
|
||||
|
||||
if (typeof timeout === 'number') {
|
||||
this.timeout = timeout;
|
||||
this._timeout = timeout;
|
||||
}
|
||||
|
||||
if (typeof domain === 'string') {
|
||||
|
@ -285,7 +286,7 @@ export class SMTP extends EventEmitter {
|
|||
}
|
||||
};
|
||||
|
||||
const response = (err, msg) => {
|
||||
const response = (err: Error, msg: { code: string | number, data: string }) => {
|
||||
if (err) {
|
||||
if (this._state === SMTPState.NOTCONNECTED && !this.sock) {
|
||||
return;
|
||||
|
@ -333,7 +334,7 @@ export class SMTP extends EventEmitter {
|
|||
);
|
||||
}
|
||||
|
||||
this.monitor = monitor(this.sock, this.timeout, () =>
|
||||
this.monitor = monitor(this.sock, this._timeout, () =>
|
||||
this.close(true)
|
||||
);
|
||||
this.sock.once('response', response);
|
||||
|
@ -380,7 +381,7 @@ export class SMTP extends EventEmitter {
|
|||
? [codes]
|
||||
: [250];
|
||||
|
||||
const response = (err, msg) => {
|
||||
const response = (err: Error, msg: { code: string | number, data: string, message: string }) => {
|
||||
if (err) {
|
||||
caller(callback, err);
|
||||
} else {
|
||||
|
@ -393,7 +394,7 @@ export class SMTP extends EventEmitter {
|
|||
}'${suffix}`;
|
||||
caller(
|
||||
callback,
|
||||
makeSMTPError(errorMessage, SMTPErrorStates.BADRESPONSE, null, msg.data)
|
||||
makeSMTPError(errorMessage, SMTPErrorStates.BADRESPONSE, undefined, msg.data)
|
||||
);
|
||||
}
|
||||
}
|
||||
|
@ -412,7 +413,7 @@ export class SMTP extends EventEmitter {
|
|||
* @param {string} domain the domain to associate with the 'helo' request
|
||||
* @returns {void}
|
||||
*/
|
||||
helo(callback: (...rest: any[]) => void, domain: string): void {
|
||||
helo(callback: (...rest: any[]) => void, domain?: string): void {
|
||||
this.command(`helo ${domain || this.domain}`, (err, data) => {
|
||||
if (err) {
|
||||
caller(callback, err);
|
||||
|
@ -428,7 +429,11 @@ export class SMTP extends EventEmitter {
|
|||
* @returns {void}
|
||||
*/
|
||||
starttls(callback: (...rest: any[]) => void): void {
|
||||
const response = (err, msg) => {
|
||||
const response = (err: Error, msg: { data: any }) => {
|
||||
if (this.sock == null) {
|
||||
throw new Error('null socket');
|
||||
}
|
||||
|
||||
if (err) {
|
||||
err.message += ' while establishing a starttls session';
|
||||
caller(callback, err);
|
||||
|
@ -438,7 +443,7 @@ export class SMTP extends EventEmitter {
|
|||
);
|
||||
const secureSocket = new TLSSocket(this.sock, { secureContext });
|
||||
|
||||
secureSocket.on('error', err => {
|
||||
secureSocket.on('error', (err: Error) => {
|
||||
this.close(true);
|
||||
caller(callback, err);
|
||||
});
|
||||
|
@ -446,7 +451,7 @@ export class SMTP extends EventEmitter {
|
|||
this._isSecure = true;
|
||||
this.sock = secureSocket;
|
||||
|
||||
monitor(this.sock, this.timeout, () => this.close(true));
|
||||
monitor(this.sock, this._timeout, () => this.close(true));
|
||||
caller(callback, msg.data);
|
||||
}
|
||||
};
|
||||
|
@ -488,7 +493,7 @@ export class SMTP extends EventEmitter {
|
|||
* @param {string} domain the domain to associate with the 'ehlo' request
|
||||
* @returns {void}
|
||||
*/
|
||||
ehlo(callback: (...rest: any[]) => void, domain: string): void {
|
||||
ehlo(callback: (...rest: any[]) => void, domain?: string): void {
|
||||
this.features = {};
|
||||
this.command(`ehlo ${domain || this.domain}`, (err, data) => {
|
||||
if (err) {
|
||||
|
@ -579,7 +584,7 @@ export class SMTP extends EventEmitter {
|
|||
*/
|
||||
message(data: string): void {
|
||||
this.log(data);
|
||||
this.sock.write(data);
|
||||
this.sock?.write(data) ?? this.log('no socket to write to');
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -614,10 +619,10 @@ export class SMTP extends EventEmitter {
|
|||
* @param {string} [domain] the domain to associate with the command
|
||||
* @returns {void}
|
||||
*/
|
||||
ehlo_or_helo_if_needed(callback: (...rest: any[]) => void, domain: string): void {
|
||||
ehlo_or_helo_if_needed(callback: (...rest: any[]) => void, domain?: string): void {
|
||||
// is this code callable...?
|
||||
if (!this.features) {
|
||||
const response = (err, data) => caller(callback, err, data);
|
||||
if (Object.keys(this.features).length === 0) {
|
||||
const response = (err: Error, data: any) => caller(callback, err, data);
|
||||
this.ehlo((err, data) => {
|
||||
if (err) {
|
||||
this.helo(response, domain);
|
||||
|
@ -642,22 +647,22 @@ export class SMTP extends EventEmitter {
|
|||
* @param {{ method: string, domain: string }} [options] login options
|
||||
* @returns {void}
|
||||
*/
|
||||
login(callback: (...rest: any[]) => void, user: string, password: string, options: { method: string; domain: string; }): void {
|
||||
login(callback: (...rest: any[]) => void, user = '', password = '', options: { method?: string; domain?: string; } = {}): void {
|
||||
const login = {
|
||||
user: () => user || this.user,
|
||||
password: () => password || this.password,
|
||||
user: () => user || this.user || '',
|
||||
password: () => password || this.password || '',
|
||||
method: options && options.method ? options.method.toUpperCase() : '',
|
||||
};
|
||||
|
||||
const domain = options && options.domain ? options.domain : this.domain;
|
||||
|
||||
const initiate = (err, data) => {
|
||||
const initiate = (err: Error, data: any) => {
|
||||
if (err) {
|
||||
caller(callback, err);
|
||||
return;
|
||||
}
|
||||
|
||||
let method = null;
|
||||
let method: AUTH_METHODS | null = null;
|
||||
|
||||
/**
|
||||
* @param {string} challenge challenge
|
||||
|
@ -765,7 +770,7 @@ export class SMTP extends EventEmitter {
|
|||
* @param {string} msg msg
|
||||
* @returns {void}
|
||||
*/
|
||||
const attempt_user = (err: Error, data: any, msg: string): void => {
|
||||
const attempt_user = (err: Error, data: any): void => {
|
||||
if (err) {
|
||||
failed(err, data);
|
||||
} else {
|
||||
|
@ -802,7 +807,7 @@ export class SMTP extends EventEmitter {
|
|||
break;
|
||||
default:
|
||||
const msg = 'no form of authorization supported';
|
||||
const err = makeSMTPError(msg, SMTPErrorStates.AUTHNOTSUPPORTED, null, data);
|
||||
const err = makeSMTPError(msg, SMTPErrorStates.AUTHNOTSUPPORTED, undefined, data);
|
||||
caller(callback, err);
|
||||
break;
|
||||
}
|
||||
|
@ -834,7 +839,7 @@ export class SMTP extends EventEmitter {
|
|||
this._state = SMTPState.NOTCONNECTED;
|
||||
this._isSecure = false;
|
||||
this.sock = null;
|
||||
this.features = null;
|
||||
this.features = {};
|
||||
this._isAuthorized = !(this._user && this._password);
|
||||
}
|
||||
|
||||
|
@ -842,7 +847,7 @@ export class SMTP extends EventEmitter {
|
|||
* @param {function(...*): void} [callback] function to call after response
|
||||
* @returns {void}
|
||||
*/
|
||||
quit(callback: (...rest: any[]) => void): void {
|
||||
quit(callback?: (...rest: any[]) => void): void {
|
||||
this.command(
|
||||
'quit',
|
||||
(err, data) => {
|
||||
|
|
|
@ -0,0 +1,14 @@
|
|||
{
|
||||
"extends": "@ledge/configs/tsconfig.json",
|
||||
"include": [
|
||||
"email.ts",
|
||||
"smtp/**/*.ts",
|
||||
"test/**/*.ts",
|
||||
"test/-register.js"
|
||||
],
|
||||
"ts-node": {
|
||||
"compilerOptions": {
|
||||
"module": "commonjs"
|
||||
}
|
||||
}
|
||||
}
|
Loading…
Reference in New Issue