mirror of
https://github.com/dawidd6/action-send-mail.git
synced 2026-10-31 01:38:34 +07:00
node_modules: update (#355)
Co-authored-by: dawidd6 <9713907+dawidd6@users.noreply.github.com>
This commit is contained in:
1 parent
b6d53adeaf
commit
f8b8145a29
34 files changed
+375
-36
No files matched your search
+3
-3
@@ -94,9 +94,9 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/nodemailer": {
|
"node_modules/nodemailer": {
|
||||||
"version": "10.2.0",
|
"version": "10.3.0",
|
||||||
"resolved": "https://registry.npmjs.org/nodemailer/-/nodemailer-10.2.0.tgz",
|
"resolved": "https://registry.npmjs.org/nodemailer/-/nodemailer-10.3.0.tgz",
|
||||||
"integrity": "sha512-/XyGw6kX2stMdKGUJdAQmYAeY1FscWfdRydfd2NtK3A3KDfUUXCqZzExSNAt2xhh90X2qLCR+o2Gd48YPJcJwA==",
|
"integrity": "sha512-LsqM+Sxjap8BMgxQuVvABoEcm9ed2f3h2e0ohB7kV3sl9+4IaXlxC0DT8CQelg/S0he38gT4yT0RH1pvZxHf+A==",
|
||||||
"license": "MIT-0",
|
"license": "MIT-0",
|
||||||
"engines": {
|
"engines": {
|
||||||
"node": ">=20.0.0"
|
"node": ">=20.0.0"
|
||||||
|
|||||||
+19
@@ -1,5 +1,24 @@
|
|||||||
# CHANGELOG
|
# CHANGELOG
|
||||||
|
|
||||||
|
## [10.3.0](https://github.com/nodemailer/nodemailer/compare/v10.2.1...v10.3.0) (2026-10-29)
|
||||||
|
|
||||||
|
|
||||||
|
### Features
|
||||||
|
|
||||||
|
* add the otp option for the OTP-Token header ([7789e15](https://github.com/nodemailer/nodemailer/commit/7789e1501e4f555e9754a15664aa30f1415902b9))
|
||||||
|
|
||||||
|
|
||||||
|
### Bug Fixes
|
||||||
|
|
||||||
|
* encode a Content-ID header as a message id ([#1904](https://github.com/nodemailer/nodemailer/issues/1904)) ([47f2b25](https://github.com/nodemailer/nodemailer/commit/47f2b252fccc8ec6a1a5700e12c1087ae3633e17))
|
||||||
|
|
||||||
|
## [10.2.1](https://github.com/nodemailer/nodemailer/compare/v10.2.0...v10.2.1) (2026-10-22)
|
||||||
|
|
||||||
|
|
||||||
|
### Bug Fixes
|
||||||
|
|
||||||
|
* drop the comments of a Content-Type or Content-Disposition value with the stripComments option ([b3052b7](https://github.com/nodemailer/nodemailer/commit/b3052b7fe2e49b51dd66d6c7814a072e885f1ac8))
|
||||||
|
|
||||||
## [10.2.0](https://github.com/nodemailer/nodemailer/compare/v10.1.7...v10.2.0) (2026-10-21)
|
## [10.2.0](https://github.com/nodemailer/nodemailer/compare/v10.1.7...v10.2.0) (2026-10-21)
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
+1
-1
@@ -68,7 +68,7 @@ function sign(headers, hashAlgo, bodyHash, options) {
|
|||||||
'Content-Description:Resent-Date:Resent-From:Resent-Sender:' +
|
'Content-Description:Resent-Date:Resent-From:Resent-Sender:' +
|
||||||
'Resent-To:Resent-Cc:Resent-Message-ID:In-Reply-To:References:' +
|
'Resent-To:Resent-Cc:Resent-Message-ID:In-Reply-To:References:' +
|
||||||
'List-Id:List-Help:List-Unsubscribe:List-Subscribe:List-Post:' +
|
'List-Id:List-Help:List-Unsubscribe:List-Subscribe:List-Post:' +
|
||||||
'List-Owner:List-Archive';
|
'List-Owner:List-Archive:OTP-Token';
|
||||||
const fieldNames = options.headerFieldNames || defaultFieldNames;
|
const fieldNames = options.headerFieldNames || defaultFieldNames;
|
||||||
const canonicalizedHeaderData = relaxedHeaders(headers, fieldNames, options.skipFields);
|
const canonicalizedHeaderData = relaxedHeaders(headers, fieldNames, options.skipFields);
|
||||||
const dkimHeader = generateDKIMHeader(options.domainName, options.keySelector, canonicalizedHeaderData.fieldNames, hashAlgo, bodyHash);
|
const dkimHeader = generateDKIMHeader(options.domainName, options.keySelector, canonicalizedHeaderData.fieldNames, hashAlgo, bodyHash);
|
||||||
|
|||||||
+2
@@ -21,6 +21,7 @@ export declare const ERROR_CODES: {
|
|||||||
readonly EPROTOCOL: "Invalid SMTP server response";
|
readonly EPROTOCOL: "Invalid SMTP server response";
|
||||||
readonly EENVELOPE: "Invalid mail envelope (sender or recipients)";
|
readonly EENVELOPE: "Invalid mail envelope (sender or recipients)";
|
||||||
readonly EMESSAGE: "Message delivery error";
|
readonly EMESSAGE: "Message delivery error";
|
||||||
|
readonly EOTP: "Invalid one-time passcode data (otp option)";
|
||||||
readonly ESTREAM: "Stream processing error";
|
readonly ESTREAM: "Stream processing error";
|
||||||
readonly EAUTH: "Authentication failed";
|
readonly EAUTH: "Authentication failed";
|
||||||
readonly ENOAUTH: "Authentication credentials not provided";
|
readonly ENOAUTH: "Authentication credentials not provided";
|
||||||
@@ -48,6 +49,7 @@ export declare const EREQUIRETLS = "EREQUIRETLS";
|
|||||||
export declare const EPROTOCOL = "EPROTOCOL";
|
export declare const EPROTOCOL = "EPROTOCOL";
|
||||||
export declare const EENVELOPE = "EENVELOPE";
|
export declare const EENVELOPE = "EENVELOPE";
|
||||||
export declare const EMESSAGE = "EMESSAGE";
|
export declare const EMESSAGE = "EMESSAGE";
|
||||||
|
export declare const EOTP = "EOTP";
|
||||||
export declare const ESTREAM = "ESTREAM";
|
export declare const ESTREAM = "ESTREAM";
|
||||||
export declare const EAUTH = "EAUTH";
|
export declare const EAUTH = "EAUTH";
|
||||||
export declare const ENOAUTH = "ENOAUTH";
|
export declare const ENOAUTH = "ENOAUTH";
|
||||||
|
|||||||
+3
-1
@@ -10,7 +10,7 @@
|
|||||||
* err.code = errors.ECONNECTION;
|
* err.code = errors.ECONNECTION;
|
||||||
*/
|
*/
|
||||||
Object.defineProperty(exports, "__esModule", { value: true });
|
Object.defineProperty(exports, "__esModule", { value: true });
|
||||||
exports.ERR_ACCESS_DENIED = exports.isPermanentResponseCode = exports.isTransientError = exports.EFETCH = exports.EURLACCESS = exports.EFILEACCESS = exports.EPROXY = exports.ECONFIG = exports.ESES = exports.ESENDMAIL = exports.EMAXRECIPIENTS = exports.EMAXLIMIT = exports.EOAUTH2 = exports.ENOAUTH = exports.EAUTH = exports.ESTREAM = exports.EMESSAGE = exports.EENVELOPE = exports.EPROTOCOL = exports.EREQUIRETLS = exports.ETLS = exports.EDNS = exports.ESOCKET = exports.ETIMEDOUT = exports.ECONNECTION = exports.ERROR_CODES = void 0;
|
exports.ERR_ACCESS_DENIED = exports.isPermanentResponseCode = exports.isTransientError = exports.EFETCH = exports.EURLACCESS = exports.EFILEACCESS = exports.EPROXY = exports.ECONFIG = exports.ESES = exports.ESENDMAIL = exports.EMAXRECIPIENTS = exports.EMAXLIMIT = exports.EOAUTH2 = exports.ENOAUTH = exports.EAUTH = exports.ESTREAM = exports.EOTP = exports.EMESSAGE = exports.EENVELOPE = exports.EPROTOCOL = exports.EREQUIRETLS = exports.ETLS = exports.EDNS = exports.ESOCKET = exports.ETIMEDOUT = exports.ECONNECTION = exports.ERROR_CODES = void 0;
|
||||||
/**
|
/**
|
||||||
* Error code descriptions for documentation and debugging
|
* Error code descriptions for documentation and debugging
|
||||||
*/
|
*/
|
||||||
@@ -27,6 +27,7 @@ exports.ERROR_CODES = {
|
|||||||
EPROTOCOL: 'Invalid SMTP server response',
|
EPROTOCOL: 'Invalid SMTP server response',
|
||||||
EENVELOPE: 'Invalid mail envelope (sender or recipients)',
|
EENVELOPE: 'Invalid mail envelope (sender or recipients)',
|
||||||
EMESSAGE: 'Message delivery error',
|
EMESSAGE: 'Message delivery error',
|
||||||
|
EOTP: 'Invalid one-time passcode data (otp option)',
|
||||||
ESTREAM: 'Stream processing error',
|
ESTREAM: 'Stream processing error',
|
||||||
// Authentication errors
|
// Authentication errors
|
||||||
EAUTH: 'Authentication failed',
|
EAUTH: 'Authentication failed',
|
||||||
@@ -55,6 +56,7 @@ exports.EREQUIRETLS = 'EREQUIRETLS';
|
|||||||
exports.EPROTOCOL = 'EPROTOCOL';
|
exports.EPROTOCOL = 'EPROTOCOL';
|
||||||
exports.EENVELOPE = 'EENVELOPE';
|
exports.EENVELOPE = 'EENVELOPE';
|
||||||
exports.EMESSAGE = 'EMESSAGE';
|
exports.EMESSAGE = 'EMESSAGE';
|
||||||
|
exports.EOTP = 'EOTP';
|
||||||
exports.ESTREAM = 'ESTREAM';
|
exports.ESTREAM = 'ESTREAM';
|
||||||
exports.EAUTH = 'EAUTH';
|
exports.EAUTH = 'EAUTH';
|
||||||
exports.ENOAUTH = 'ENOAUTH';
|
exports.ENOAUTH = 'ENOAUTH';
|
||||||
|
|||||||
+12
@@ -72,6 +72,16 @@ export type MailComposerListHeaderEntry = string | {
|
|||||||
export interface MailComposerListHeaders {
|
export interface MailComposerListHeaders {
|
||||||
[key: string]: MailComposerListHeaderEntry | (MailComposerListHeaderEntry | MailComposerListHeaderEntry[])[];
|
[key: string]: MailComposerListHeaderEntry | (MailComposerListHeaderEntry | MailComposerListHeaderEntry[])[];
|
||||||
}
|
}
|
||||||
|
/**
|
||||||
|
* The otp value: a one-time passcode and the origin it is bound to, sent as the OTP-Token
|
||||||
|
* header (draft-goto-otp-token)
|
||||||
|
*/
|
||||||
|
export interface MailComposerOtp {
|
||||||
|
/** The passcode, printable ascii */
|
||||||
|
code: string;
|
||||||
|
/** Origin the code is bound to, https or a loopback http origin. A full url is reduced to its origin */
|
||||||
|
origin: string;
|
||||||
|
}
|
||||||
/**
|
/**
|
||||||
* Encoding for the non-ascii header values: quoted-printable ('Q', the default) or base64
|
* Encoding for the non-ascii header values: quoted-printable ('Q', the default) or base64
|
||||||
* ('B'). Only the first letter is read, so the short forms work as well
|
* ('B'). Only the first letter is read, so the short forms work as well
|
||||||
@@ -114,6 +124,8 @@ export interface MailComposerOptions {
|
|||||||
headers?: MimeNodeHeaders | undefined;
|
headers?: MimeNodeHeaders | undefined;
|
||||||
/** List-* headers, read by the mailer */
|
/** List-* headers, read by the mailer */
|
||||||
list?: MailComposerListHeaders | undefined;
|
list?: MailComposerListHeaders | undefined;
|
||||||
|
/** One-time passcode for the OTP-Token header, read by the mailer */
|
||||||
|
otp?: MailComposerOtp | undefined;
|
||||||
/** SMTP envelope to use instead of the one generated from the headers */
|
/** SMTP envelope to use instead of the one generated from the headers */
|
||||||
envelope?: MimeNodeEnvelopeInput | undefined;
|
envelope?: MimeNodeEnvelopeInput | undefined;
|
||||||
/** Content-Transfer-Encoding to force for the text/* nodes that do not set their own */
|
/** Content-Transfer-Encoding to force for the text/* nodes that do not set their own */
|
||||||
|
|||||||
+3
-2
@@ -4,7 +4,7 @@ import DKIM, { type DKIMOptions } from '../dkim/index.js';
|
|||||||
import MailMessage, { type MailDefaults, type SendMailOptions } from './mail-message.js';
|
import MailMessage, { type MailDefaults, type SendMailOptions } from './mail-message.js';
|
||||||
import net from 'node:net';
|
import net from 'node:net';
|
||||||
import type { ConnectionOptions } from 'node:tls';
|
import type { ConnectionOptions } from 'node:tls';
|
||||||
import type { MailComposerAlternative, MailComposerAttachment, MailComposerIcalEvent, MailComposerListHeaderEntry, MailComposerListHeaders, MailComposerTextEncoding } from '../mail-composer/index.js';
|
import type { MailComposerAlternative, MailComposerAttachment, MailComposerIcalEvent, MailComposerListHeaderEntry, MailComposerListHeaders, MailComposerOtp, MailComposerTextEncoding } from '../mail-composer/index.js';
|
||||||
import type { Callback, NodemailerError, ResultCallback } from '../errors.js';
|
import type { Callback, NodemailerError, ResultCallback } from '../errors.js';
|
||||||
import type { ParsedUrl } from '../shared/url.js';
|
import type { ParsedUrl } from '../shared/url.js';
|
||||||
import type { MimeNodeAddress, MimeNodeEnvelope, MimeNodeEnvelopeInput, MimeNodeHeaders, MimeNodeOptions } from '../mime-node/index.js';
|
import type { MimeNodeAddress, MimeNodeEnvelope, MimeNodeEnvelopeInput, MimeNodeHeaders, MimeNodeOptions } from '../mime-node/index.js';
|
||||||
@@ -227,11 +227,12 @@ declare namespace Mail {
|
|||||||
type Headers = MimeNodeHeaders;
|
type Headers = MimeNodeHeaders;
|
||||||
type ListHeader = MailComposerListHeaderEntry;
|
type ListHeader = MailComposerListHeaderEntry;
|
||||||
type ListHeaders = MailComposerListHeaders;
|
type ListHeaders = MailComposerListHeaders;
|
||||||
|
type Otp = MailComposerOtp;
|
||||||
type Envelope = MimeNodeEnvelopeInput;
|
type Envelope = MimeNodeEnvelopeInput;
|
||||||
type Connection = SocketOptions;
|
type Connection = SocketOptions;
|
||||||
type TextEncoding = MailComposerTextEncoding;
|
type TextEncoding = MailComposerTextEncoding;
|
||||||
type PluginFunction<T = SentMessageInfo> = MailPluginFunction<T>;
|
type PluginFunction<T = SentMessageInfo> = MailPluginFunction<T>;
|
||||||
}
|
}
|
||||||
/** The same aliases as module level exports, for `import * as Mail` and `import Mail = require()` */
|
/** The same aliases as module level exports, for `import * as Mail` and `import Mail = require()` */
|
||||||
export type { SendMailOptions as Options, MimeNodeAddress as Address, MailComposerAttachment as Attachment, MailComposerAlternative as AttachmentLike, MailComposerAlternative as AmpAttachment, MailComposerIcalEvent as IcalAttachment, MimeNodeHeaders as Headers, MailComposerListHeaderEntry as ListHeader, MailComposerListHeaders as ListHeaders, MimeNodeEnvelopeInput as Envelope, SocketOptions as Connection, MailComposerTextEncoding as TextEncoding };
|
export type { SendMailOptions as Options, MimeNodeAddress as Address, MailComposerAttachment as Attachment, MailComposerAlternative as AttachmentLike, MailComposerAlternative as AmpAttachment, MailComposerIcalEvent as IcalAttachment, MimeNodeHeaders as Headers, MailComposerListHeaderEntry as ListHeader, MailComposerListHeaders as ListHeaders, MailComposerOtp as Otp, MimeNodeEnvelopeInput as Envelope, SocketOptions as Connection, MailComposerTextEncoding as TextEncoding };
|
||||||
export default Mail;
|
export default Mail;
|
||||||
+1
@@ -183,6 +183,7 @@ class Mail extends node_events_1.EventEmitter {
|
|||||||
mail.setMailerHeader();
|
mail.setMailerHeader();
|
||||||
mail.setPriorityHeaders();
|
mail.setPriorityHeaders();
|
||||||
mail.setListHeaders();
|
mail.setListHeaders();
|
||||||
|
mail.setOtpHeader();
|
||||||
recipientCount = mail.message.getEnvelope().to.length;
|
recipientCount = mail.message.getEnvelope().to.length;
|
||||||
}
|
}
|
||||||
catch (err) {
|
catch (err) {
|
||||||
|
|||||||
+7
@@ -103,4 +103,11 @@ export default class MailMessage<T = SentMessageInfo> {
|
|||||||
setMailerHeader(): void;
|
setMailerHeader(): void;
|
||||||
setPriorityHeaders(): void;
|
setPriorityHeaders(): void;
|
||||||
setListHeaders(): void;
|
setListHeaders(): void;
|
||||||
|
/**
|
||||||
|
* Sets the OTP-Token header from the otp option. The header is defined by
|
||||||
|
* draft-goto-otp-token-01, an Internet-Draft that expires on 25 March 2027. By then check
|
||||||
|
* for a newer revision of the draft or a published RFC, the header name and its
|
||||||
|
* parameters are still open questions in the draft.
|
||||||
|
*/
|
||||||
|
setOtpHeader(): void;
|
||||||
}
|
}
|
||||||
+54
@@ -39,6 +39,7 @@ Object.defineProperty(exports, "__esModule", { value: true });
|
|||||||
const shared = __importStar(require("../shared/index.js"));
|
const shared = __importStar(require("../shared/index.js"));
|
||||||
const index_js_1 = __importDefault(require("../mime-node/index.js"));
|
const index_js_1 = __importDefault(require("../mime-node/index.js"));
|
||||||
const mimeFuncs = __importStar(require("../mime-funcs/index.js"));
|
const mimeFuncs = __importStar(require("../mime-funcs/index.js"));
|
||||||
|
const errors = __importStar(require("../errors.js"));
|
||||||
// Only an own key counts as already set. `key in obj` also matches every member of
|
// Only an own key counts as already set. `key in obj` also matches every member of
|
||||||
// Object.prototype, which silently drops a transporter default legitimately named
|
// Object.prototype, which silently drops a transporter default legitimately named
|
||||||
// toString or constructor.
|
// toString or constructor.
|
||||||
@@ -300,6 +301,26 @@ class MailMessage {
|
|||||||
});
|
});
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
/**
|
||||||
|
* Sets the OTP-Token header from the otp option. The header is defined by
|
||||||
|
* draft-goto-otp-token-01, an Internet-Draft that expires on 25 March 2027. By then check
|
||||||
|
* for a newer revision of the draft or a published RFC, the header name and its
|
||||||
|
* parameters are still open questions in the draft.
|
||||||
|
*/
|
||||||
|
setOtpHeader() {
|
||||||
|
const otp = this.data.otp;
|
||||||
|
if (!this.message || !otp) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
// a structured field String can only hold printable ascii. The code is a secret the recipient
|
||||||
|
// compares against the one in the message body, so it is rejected rather than cleaned up
|
||||||
|
if (typeof otp.code !== 'string' || !/^[\x20-\x7e]+$/.test(otp.code)) {
|
||||||
|
throw otpError('The otp code must be a non-empty string of printable ASCII characters');
|
||||||
|
}
|
||||||
|
// a structured field String escapes the same two characters as a quoted-string. setHeader
|
||||||
|
// replaces any OTP-Token from the custom headers, the field may appear only once
|
||||||
|
this.message.setHeader('OTP-Token', mimeFuncs.quoteString(otp.code) + '; origin=' + mimeFuncs.quoteString(getOtpOrigin(otp.origin)));
|
||||||
|
}
|
||||||
/** @internal */
|
/** @internal */
|
||||||
_getListHeaders(listData) {
|
_getListHeaders(listData) {
|
||||||
// make sure an url looks like <protocol:url>
|
// make sure an url looks like <protocol:url>
|
||||||
@@ -360,5 +381,38 @@ class MailMessage {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
exports.default = MailMessage;
|
exports.default = MailMessage;
|
||||||
|
/**
|
||||||
|
* Returns the canonical ascii serialization of the origin the code is bound to. The draft
|
||||||
|
* requires a potentially trustworthy origin, so only https is accepted, plus http on a
|
||||||
|
* loopback host for local development
|
||||||
|
*/
|
||||||
|
function getOtpOrigin(origin) {
|
||||||
|
let url;
|
||||||
|
try {
|
||||||
|
url = new URL(String(origin));
|
||||||
|
}
|
||||||
|
catch {
|
||||||
|
// handled below
|
||||||
|
}
|
||||||
|
// the WHATWG parser lets characters such as a quote or a brace into a host, which the
|
||||||
|
// origin grammar the draft requires (RFC 6454) does not allow, so a receiver would discard
|
||||||
|
// the header. Only a DNS name, an IPv4 address or a bracketed IPv6 address is accepted.
|
||||||
|
// Userinfo is refused as well, an origin never has it and it turns a value built as
|
||||||
|
// 'https://example.com' + input into the origin of whatever host follows an "@"
|
||||||
|
if (!url || url.username || url.password || !/^([a-z0-9_-]+\.)*[a-z0-9_-]+$|^\[[0-9a-f:.]+\]$/.test(url.hostname)) {
|
||||||
|
throw otpError('The otp origin must be an absolute https URL');
|
||||||
|
}
|
||||||
|
const isLoopback = /^(localhost|.+\.localhost|127(\.\d+){3}|\[::1\])$/.test(url.hostname);
|
||||||
|
if (url.protocol !== 'https:' && !(url.protocol === 'http:' && isLoopback)) {
|
||||||
|
throw otpError('The otp origin must be an https URL, or an http URL on a loopback host');
|
||||||
|
}
|
||||||
|
// the URL parser already lowercases the host, converts it to punycode and drops the default port
|
||||||
|
return url.origin;
|
||||||
|
}
|
||||||
|
function otpError(message) {
|
||||||
|
const err = new Error(message);
|
||||||
|
err.code = errors.EOTP;
|
||||||
|
return err;
|
||||||
|
}
|
||||||
module.exports = exports.default;
|
module.exports = exports.default;
|
||||||
Object.defineProperty(module.exports, 'default', { value: exports.default, enumerable: false, writable: true, configurable: true });
|
Object.defineProperty(module.exports, 'default', { value: exports.default, enumerable: false, writable: true, configurable: true });
|
||||||
+18
-1
@@ -107,6 +107,22 @@ export declare function buildHeaderValue(structured: StructuredHeaderValue): str
|
|||||||
* @return A list of encoded keys and headers
|
* @return A list of encoded keys and headers
|
||||||
*/
|
*/
|
||||||
export declare function buildHeaderParam(key: string, data: string | Buffer, maxLength?: number): EncodedHeaderParam[];
|
export declare function buildHeaderParam(key: string, data: string | Buffer, maxLength?: number): EncodedHeaderParam[];
|
||||||
|
/**
|
||||||
|
* Options of parseHeaderValue()
|
||||||
|
*/
|
||||||
|
export interface ParseHeaderValueOptions {
|
||||||
|
/**
|
||||||
|
* Drop the RFC 822 comments outside quoted strings, which the MIME headers allow and
|
||||||
|
* which are not part of the value: 'boundary=b (comment)' names the boundary 'b'. A
|
||||||
|
* parenthesis right behind the '=' of a parameter or behind a token character of its
|
||||||
|
* value continues the token, as it does for Python and postal-mime, so 'Invoice(1).pdf'
|
||||||
|
* keeps its name and 'boundary=(c)b' names the boundary '(c)b'. A comment that never
|
||||||
|
* closes runs to the end of the value, unless a parameter follows it, in which case it is
|
||||||
|
* content and the parameter survives. Comments are kept by default for the sake of the
|
||||||
|
* callers of this exported function, not because keeping them is ever the better reading
|
||||||
|
*/
|
||||||
|
stripComments?: boolean | undefined;
|
||||||
|
}
|
||||||
/**
|
/**
|
||||||
* Parses a header value with key=value arguments into a structured
|
* Parses a header value with key=value arguments into a structured
|
||||||
* object.
|
* object.
|
||||||
@@ -120,9 +136,10 @@ export declare function buildHeaderParam(key: string, data: string | Buffer, max
|
|||||||
* }
|
* }
|
||||||
*
|
*
|
||||||
* @param str Header value
|
* @param str Header value
|
||||||
|
* @param options Options, see ParseHeaderValueOptions
|
||||||
* @return Header value as a parsed structure
|
* @return Header value as a parsed structure
|
||||||
*/
|
*/
|
||||||
export declare function parseHeaderValue(str: string): ParsedHeaderValue;
|
export declare function parseHeaderValue(str: string, options?: ParseHeaderValueOptions): ParsedHeaderValue;
|
||||||
/**
|
/**
|
||||||
* Returns file extension for a content type string. If no suitable extensions
|
* Returns file extension for a content type string. If no suitable extensions
|
||||||
* are found, 'bin' is used as the default extension
|
* are found, 'bin' is used as the default extension
|
||||||
|
|||||||
+43
-1
@@ -414,6 +414,26 @@ function buildHeaderParam(key, data, maxLength) {
|
|||||||
* goes in. Whitespace used to come off it only because the value was trimmed first.
|
* goes in. Whitespace used to come off it only because the value was trimmed first.
|
||||||
*/
|
*/
|
||||||
const TOKEN = /^[^\x00-\x20\x7f()<>@,;:\\"/[\]?=]+$/;
|
const TOKEN = /^[^\x00-\x20\x7f()<>@,;:\\"/[\]?=]+$/;
|
||||||
|
/**
|
||||||
|
* Offset of the ')' that closes the RFC 822 comment opening at `start`, or -1 when the
|
||||||
|
* comment never closes. Comments nest, and a backslash escapes the character after it
|
||||||
|
*/
|
||||||
|
function _findCommentEnd(str, start) {
|
||||||
|
let depth = 0;
|
||||||
|
for (let i = start, len = str.length; i < len; i++) {
|
||||||
|
const c = str.charAt(i);
|
||||||
|
if (c === '\\') {
|
||||||
|
i++;
|
||||||
|
}
|
||||||
|
else if (c === '(') {
|
||||||
|
depth++;
|
||||||
|
}
|
||||||
|
else if (c === ')' && --depth === 0) {
|
||||||
|
return i;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
/**
|
/**
|
||||||
* Whether a string can be a header parameter name.
|
* Whether a string can be a header parameter name.
|
||||||
*
|
*
|
||||||
@@ -440,13 +460,16 @@ function _isParamName(name) {
|
|||||||
* }
|
* }
|
||||||
*
|
*
|
||||||
* @param str Header value
|
* @param str Header value
|
||||||
|
* @param options Options, see ParseHeaderValueOptions
|
||||||
* @return Header value as a parsed structure
|
* @return Header value as a parsed structure
|
||||||
*/
|
*/
|
||||||
function parseHeaderValue(str) {
|
function parseHeaderValue(str, options) {
|
||||||
const response = {
|
const response = {
|
||||||
value: '',
|
value: '',
|
||||||
params: {}
|
params: {}
|
||||||
};
|
};
|
||||||
|
// Cleared again by an unclosed '(', see the loop
|
||||||
|
let stripComments = !!options?.stripComments;
|
||||||
// A duplicated parameter resolves to its first occurrence, the way a duplicated header
|
// A duplicated parameter resolves to its first occurrence, the way a duplicated header
|
||||||
// does. Letting the last one win disagrees with the receivers that take the first, and
|
// does. Letting the last one win disagrees with the receivers that take the first, and
|
||||||
// the two readings of 'boundary="b"; boundary="c"' name different delimiters. The
|
// the two readings of 'boundary="b"; boundary="c"' name different delimiters. The
|
||||||
@@ -508,6 +531,25 @@ function parseHeaderValue(str) {
|
|||||||
};
|
};
|
||||||
for (let i = 0, len = str.length; i < len; i++) {
|
for (let i = 0, len = str.length; i < len; i++) {
|
||||||
chr = str.charAt(i);
|
chr = str.charAt(i);
|
||||||
|
// With stripComments, an RFC 822 comment stands anywhere outside a quoted string but
|
||||||
|
// right behind a token it would continue, see ParseHeaderValueOptions. In the value
|
||||||
|
// of a parameter, no pending whitespace means the last significant character was the
|
||||||
|
// '=' or a character of the token
|
||||||
|
if (stripComments && chr === '(' && !quote && !quoteClosed && !(stage === 'value' && key !== false && !pendingSpace)) {
|
||||||
|
let end = _findCommentEnd(str, i);
|
||||||
|
if (end < 0 && str.indexOf(';', i) < 0) {
|
||||||
|
// a comment that never closes runs to the end of the value, unless a
|
||||||
|
// parameter follows it, which is content then and survives
|
||||||
|
end = len - 1;
|
||||||
|
}
|
||||||
|
if (end >= 0) {
|
||||||
|
i = end;
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
// looking for a closing ')' again would scan a run of unclosed '(' to its end
|
||||||
|
// once for each of them
|
||||||
|
stripComments = false;
|
||||||
|
}
|
||||||
if (stage === 'key') {
|
if (stage === 'key') {
|
||||||
if (chr === '=') {
|
if (chr === '=') {
|
||||||
key = takeValue().trim();
|
key = takeValue().trim();
|
||||||
|
|||||||
+15
-4
@@ -654,15 +654,18 @@ class MimeNode {
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
switch (header.key) {
|
switch (header.key) {
|
||||||
|
// The comments of a MIME header are not part of its value and are dropped, so a
|
||||||
|
// caller supplied 'boundary=b (comment)' is the boundary b it names, and the
|
||||||
|
// header goes out without the comment
|
||||||
case 'Content-Disposition':
|
case 'Content-Disposition':
|
||||||
structured = mimeFuncs.parseHeaderValue(value);
|
structured = mimeFuncs.parseHeaderValue(value, { stripComments: true });
|
||||||
if (this.filename) {
|
if (this.filename) {
|
||||||
structured.params.filename = this.filename;
|
structured.params.filename = this.filename;
|
||||||
}
|
}
|
||||||
value = mimeFuncs.buildHeaderValue(structured);
|
value = mimeFuncs.buildHeaderValue(structured);
|
||||||
break;
|
break;
|
||||||
case 'Content-Type':
|
case 'Content-Type':
|
||||||
structured = mimeFuncs.parseHeaderValue(value);
|
structured = mimeFuncs.parseHeaderValue(value, { stripComments: true });
|
||||||
// the type token decides multipart and charset below, so clean it before
|
// the type token decides multipart and charset below, so clean it before
|
||||||
// those run and not just on the way out, otherwise a control char makes
|
// those run and not just on the way out, otherwise a control char makes
|
||||||
// the checks miss and the header ends up claiming a type it is not set up for
|
// the checks miss and the header ends up claiming a type it is not set up for
|
||||||
@@ -1299,7 +1302,7 @@ class MimeNode {
|
|||||||
.replace(/[\s\x00-\x20\x7f:]/g, '')
|
.replace(/[\s\x00-\x20\x7f:]/g, '')
|
||||||
.toLowerCase()
|
.toLowerCase()
|
||||||
// use uppercase words, except MIME
|
// use uppercase words, except MIME
|
||||||
.replace(/^X-SMTPAPI$|^(MIME|DKIM|ARC|BIMI)\b|^[a-z]|-(SPF|FBL|ID|MD5)$|-[a-z]/gi, c => c.toUpperCase())
|
.replace(/^X-SMTPAPI$|^(MIME|DKIM|ARC|BIMI)\b|^OTP(?=-Token$)|^[a-z]|-(SPF|FBL|ID|MD5)$|-[a-z]/gi, c => c.toUpperCase())
|
||||||
// special case
|
// special case
|
||||||
.replace(/^Content-Features$/i, 'Content-features');
|
.replace(/^Content-Features$/i, 'Content-features');
|
||||||
return key;
|
return key;
|
||||||
@@ -1371,7 +1374,7 @@ class MimeNode {
|
|||||||
// values enclosed in <>
|
// values enclosed in <>
|
||||||
case 'Message-ID':
|
case 'Message-ID':
|
||||||
case 'In-Reply-To':
|
case 'In-Reply-To':
|
||||||
case 'Content-Id':
|
case 'Content-ID':
|
||||||
// a msg-id is structured, so an encoded word inside the angle brackets would
|
// a msg-id is structured, so an encoded word inside the angle brackets would
|
||||||
// be read as literal text. drop the characters that can not appear in a header
|
// be read as literal text. drop the characters that can not appear in a header
|
||||||
// at all, but leave HT alone, it separates the ids of a multi id value
|
// at all, but leave HT alone, it separates the ids of a multi id value
|
||||||
@@ -1386,6 +1389,14 @@ class MimeNode {
|
|||||||
value = value + '>';
|
value = value + '>';
|
||||||
}
|
}
|
||||||
return value;
|
return value;
|
||||||
|
// a structured field (RFC 9651) that can not be mime encoded, an encoded word would
|
||||||
|
// turn its quoted strings into text that no structured field parser reads. Only
|
||||||
|
// the characters that can not appear in a header at all are removed
|
||||||
|
case 'OTP-Token':
|
||||||
|
return (value || '')
|
||||||
|
.toString()
|
||||||
|
.replace(/\r?\n|\r/g, ' ')
|
||||||
|
.replace(/[\x00-\x08\x0b\x0c\x0e-\x1f\x7f]/g, '');
|
||||||
// space separated list of values enclosed in <>
|
// space separated list of values enclosed in <>
|
||||||
case 'References': {
|
case 'References': {
|
||||||
const ids = [];
|
const ids = [];
|
||||||
|
|||||||
+1
-1
@@ -111,7 +111,7 @@ export type { Mail, MailDefaults, SentMessageInfo, Transport, TransportOptions }
|
|||||||
export type { SendMailOptions, Transporter } from './mailer/index.js';
|
export type { SendMailOptions, Transporter } from './mailer/index.js';
|
||||||
export type { NodemailerError, ErrorCode } from './errors.js';
|
export type { NodemailerError, ErrorCode } from './errors.js';
|
||||||
export type { MailMessage, MailMessageData, PluginFunction, VerifyCallback } from './mailer/index.js';
|
export type { MailMessage, MailMessageData, PluginFunction, VerifyCallback } from './mailer/index.js';
|
||||||
export type { MailComposerOptions, MailComposerAttachment as Attachment, MailComposerAlternative as AttachmentLike, MailComposerIcalEvent as IcalAttachment, MailComposerListHeaders as ListHeaders } from './mail-composer/index.js';
|
export type { MailComposerOptions, MailComposerAttachment as Attachment, MailComposerAlternative as AttachmentLike, MailComposerIcalEvent as IcalAttachment, MailComposerListHeaders as ListHeaders, MailComposerOtp as Otp } from './mail-composer/index.js';
|
||||||
export type { MimeNodeAddress as Address, MimeNodeHeaders as Headers } from './mime-node/index.js';
|
export type { MimeNodeAddress as Address, MimeNodeHeaders as Headers } from './mime-node/index.js';
|
||||||
export type { SMTPConnectionOptions, SMTPConnectionAuth, SMTPEnvelope, SMTPEnvelopeDsn } from './smtp-connection/index.js';
|
export type { SMTPConnectionOptions, SMTPConnectionAuth, SMTPEnvelope, SMTPEnvelopeDsn } from './smtp-connection/index.js';
|
||||||
export type { DKIMOptions } from './dkim/index.js';
|
export type { DKIMOptions } from './dkim/index.js';
|
||||||
|
|||||||
+1
-1
@@ -1,3 +1,3 @@
|
|||||||
export declare const name = "nodemailer";
|
export declare const name = "nodemailer";
|
||||||
export declare const version = "10.2.0";
|
export declare const version = "10.3.0";
|
||||||
export declare const homepage = "https://nodemailer.com/";
|
export declare const homepage = "https://nodemailer.com/";
|
||||||
+1
-1
@@ -3,5 +3,5 @@
|
|||||||
Object.defineProperty(exports, "__esModule", { value: true });
|
Object.defineProperty(exports, "__esModule", { value: true });
|
||||||
exports.homepage = exports.version = exports.name = void 0;
|
exports.homepage = exports.version = exports.name = void 0;
|
||||||
exports.name = 'nodemailer';
|
exports.name = 'nodemailer';
|
||||||
exports.version = '10.2.0';
|
exports.version = '10.3.0';
|
||||||
exports.homepage = 'https://nodemailer.com/';
|
exports.homepage = 'https://nodemailer.com/';
|
||||||
+7
@@ -208,6 +208,13 @@ exports.services = {
|
|||||||
"host": "smtp.europe.secureserver.net",
|
"host": "smtp.europe.secureserver.net",
|
||||||
"port": 25
|
"port": 25
|
||||||
},
|
},
|
||||||
|
"Groundmail": {
|
||||||
|
"description": "Groundmail",
|
||||||
|
"host": "smtp.groundmail.dev",
|
||||||
|
"port": 587,
|
||||||
|
"secure": false,
|
||||||
|
"requireTLS": true
|
||||||
|
},
|
||||||
"hot.ee": {
|
"hot.ee": {
|
||||||
"description": "Hot.ee (Estonian email provider)",
|
"description": "Hot.ee (Estonian email provider)",
|
||||||
"host": "mail.hot.ee"
|
"host": "mail.hot.ee"
|
||||||
|
|||||||
+1
-1
@@ -30,7 +30,7 @@ function sign(headers, hashAlgo, bodyHash, options) {
|
|||||||
'Content-Description:Resent-Date:Resent-From:Resent-Sender:' +
|
'Content-Description:Resent-Date:Resent-From:Resent-Sender:' +
|
||||||
'Resent-To:Resent-Cc:Resent-Message-ID:In-Reply-To:References:' +
|
'Resent-To:Resent-Cc:Resent-Message-ID:In-Reply-To:References:' +
|
||||||
'List-Id:List-Help:List-Unsubscribe:List-Subscribe:List-Post:' +
|
'List-Id:List-Help:List-Unsubscribe:List-Subscribe:List-Post:' +
|
||||||
'List-Owner:List-Archive';
|
'List-Owner:List-Archive:OTP-Token';
|
||||||
const fieldNames = options.headerFieldNames || defaultFieldNames;
|
const fieldNames = options.headerFieldNames || defaultFieldNames;
|
||||||
const canonicalizedHeaderData = relaxedHeaders(headers, fieldNames, options.skipFields);
|
const canonicalizedHeaderData = relaxedHeaders(headers, fieldNames, options.skipFields);
|
||||||
const dkimHeader = generateDKIMHeader(options.domainName, options.keySelector, canonicalizedHeaderData.fieldNames, hashAlgo, bodyHash);
|
const dkimHeader = generateDKIMHeader(options.domainName, options.keySelector, canonicalizedHeaderData.fieldNames, hashAlgo, bodyHash);
|
||||||
|
|||||||
+2
@@ -21,6 +21,7 @@ export declare const ERROR_CODES: {
|
|||||||
readonly EPROTOCOL: "Invalid SMTP server response";
|
readonly EPROTOCOL: "Invalid SMTP server response";
|
||||||
readonly EENVELOPE: "Invalid mail envelope (sender or recipients)";
|
readonly EENVELOPE: "Invalid mail envelope (sender or recipients)";
|
||||||
readonly EMESSAGE: "Message delivery error";
|
readonly EMESSAGE: "Message delivery error";
|
||||||
|
readonly EOTP: "Invalid one-time passcode data (otp option)";
|
||||||
readonly ESTREAM: "Stream processing error";
|
readonly ESTREAM: "Stream processing error";
|
||||||
readonly EAUTH: "Authentication failed";
|
readonly EAUTH: "Authentication failed";
|
||||||
readonly ENOAUTH: "Authentication credentials not provided";
|
readonly ENOAUTH: "Authentication credentials not provided";
|
||||||
@@ -48,6 +49,7 @@ export declare const EREQUIRETLS = "EREQUIRETLS";
|
|||||||
export declare const EPROTOCOL = "EPROTOCOL";
|
export declare const EPROTOCOL = "EPROTOCOL";
|
||||||
export declare const EENVELOPE = "EENVELOPE";
|
export declare const EENVELOPE = "EENVELOPE";
|
||||||
export declare const EMESSAGE = "EMESSAGE";
|
export declare const EMESSAGE = "EMESSAGE";
|
||||||
|
export declare const EOTP = "EOTP";
|
||||||
export declare const ESTREAM = "ESTREAM";
|
export declare const ESTREAM = "ESTREAM";
|
||||||
export declare const EAUTH = "EAUTH";
|
export declare const EAUTH = "EAUTH";
|
||||||
export declare const ENOAUTH = "ENOAUTH";
|
export declare const ENOAUTH = "ENOAUTH";
|
||||||
|
|||||||
+2
@@ -24,6 +24,7 @@ export const ERROR_CODES = {
|
|||||||
EPROTOCOL: 'Invalid SMTP server response',
|
EPROTOCOL: 'Invalid SMTP server response',
|
||||||
EENVELOPE: 'Invalid mail envelope (sender or recipients)',
|
EENVELOPE: 'Invalid mail envelope (sender or recipients)',
|
||||||
EMESSAGE: 'Message delivery error',
|
EMESSAGE: 'Message delivery error',
|
||||||
|
EOTP: 'Invalid one-time passcode data (otp option)',
|
||||||
ESTREAM: 'Stream processing error',
|
ESTREAM: 'Stream processing error',
|
||||||
// Authentication errors
|
// Authentication errors
|
||||||
EAUTH: 'Authentication failed',
|
EAUTH: 'Authentication failed',
|
||||||
@@ -52,6 +53,7 @@ export const EREQUIRETLS = 'EREQUIRETLS';
|
|||||||
export const EPROTOCOL = 'EPROTOCOL';
|
export const EPROTOCOL = 'EPROTOCOL';
|
||||||
export const EENVELOPE = 'EENVELOPE';
|
export const EENVELOPE = 'EENVELOPE';
|
||||||
export const EMESSAGE = 'EMESSAGE';
|
export const EMESSAGE = 'EMESSAGE';
|
||||||
|
export const EOTP = 'EOTP';
|
||||||
export const ESTREAM = 'ESTREAM';
|
export const ESTREAM = 'ESTREAM';
|
||||||
export const EAUTH = 'EAUTH';
|
export const EAUTH = 'EAUTH';
|
||||||
export const ENOAUTH = 'ENOAUTH';
|
export const ENOAUTH = 'ENOAUTH';
|
||||||
|
|||||||
+12
@@ -72,6 +72,16 @@ export type MailComposerListHeaderEntry = string | {
|
|||||||
export interface MailComposerListHeaders {
|
export interface MailComposerListHeaders {
|
||||||
[key: string]: MailComposerListHeaderEntry | (MailComposerListHeaderEntry | MailComposerListHeaderEntry[])[];
|
[key: string]: MailComposerListHeaderEntry | (MailComposerListHeaderEntry | MailComposerListHeaderEntry[])[];
|
||||||
}
|
}
|
||||||
|
/**
|
||||||
|
* The otp value: a one-time passcode and the origin it is bound to, sent as the OTP-Token
|
||||||
|
* header (draft-goto-otp-token)
|
||||||
|
*/
|
||||||
|
export interface MailComposerOtp {
|
||||||
|
/** The passcode, printable ascii */
|
||||||
|
code: string;
|
||||||
|
/** Origin the code is bound to, https or a loopback http origin. A full url is reduced to its origin */
|
||||||
|
origin: string;
|
||||||
|
}
|
||||||
/**
|
/**
|
||||||
* Encoding for the non-ascii header values: quoted-printable ('Q', the default) or base64
|
* Encoding for the non-ascii header values: quoted-printable ('Q', the default) or base64
|
||||||
* ('B'). Only the first letter is read, so the short forms work as well
|
* ('B'). Only the first letter is read, so the short forms work as well
|
||||||
@@ -114,6 +124,8 @@ export interface MailComposerOptions {
|
|||||||
headers?: MimeNodeHeaders | undefined;
|
headers?: MimeNodeHeaders | undefined;
|
||||||
/** List-* headers, read by the mailer */
|
/** List-* headers, read by the mailer */
|
||||||
list?: MailComposerListHeaders | undefined;
|
list?: MailComposerListHeaders | undefined;
|
||||||
|
/** One-time passcode for the OTP-Token header, read by the mailer */
|
||||||
|
otp?: MailComposerOtp | undefined;
|
||||||
/** SMTP envelope to use instead of the one generated from the headers */
|
/** SMTP envelope to use instead of the one generated from the headers */
|
||||||
envelope?: MimeNodeEnvelopeInput | undefined;
|
envelope?: MimeNodeEnvelopeInput | undefined;
|
||||||
/** Content-Transfer-Encoding to force for the text/* nodes that do not set their own */
|
/** Content-Transfer-Encoding to force for the text/* nodes that do not set their own */
|
||||||
|
|||||||
+3
-2
@@ -4,7 +4,7 @@ import DKIM, { type DKIMOptions } from '../dkim/index.js';
|
|||||||
import MailMessage, { type MailDefaults, type SendMailOptions } from './mail-message.js';
|
import MailMessage, { type MailDefaults, type SendMailOptions } from './mail-message.js';
|
||||||
import net from 'node:net';
|
import net from 'node:net';
|
||||||
import type { ConnectionOptions } from 'node:tls';
|
import type { ConnectionOptions } from 'node:tls';
|
||||||
import type { MailComposerAlternative, MailComposerAttachment, MailComposerIcalEvent, MailComposerListHeaderEntry, MailComposerListHeaders, MailComposerTextEncoding } from '../mail-composer/index.js';
|
import type { MailComposerAlternative, MailComposerAttachment, MailComposerIcalEvent, MailComposerListHeaderEntry, MailComposerListHeaders, MailComposerOtp, MailComposerTextEncoding } from '../mail-composer/index.js';
|
||||||
import type { Callback, NodemailerError, ResultCallback } from '../errors.js';
|
import type { Callback, NodemailerError, ResultCallback } from '../errors.js';
|
||||||
import type { ParsedUrl } from '../shared/url.js';
|
import type { ParsedUrl } from '../shared/url.js';
|
||||||
import type { MimeNodeAddress, MimeNodeEnvelope, MimeNodeEnvelopeInput, MimeNodeHeaders, MimeNodeOptions } from '../mime-node/index.js';
|
import type { MimeNodeAddress, MimeNodeEnvelope, MimeNodeEnvelopeInput, MimeNodeHeaders, MimeNodeOptions } from '../mime-node/index.js';
|
||||||
@@ -227,11 +227,12 @@ declare namespace Mail {
|
|||||||
type Headers = MimeNodeHeaders;
|
type Headers = MimeNodeHeaders;
|
||||||
type ListHeader = MailComposerListHeaderEntry;
|
type ListHeader = MailComposerListHeaderEntry;
|
||||||
type ListHeaders = MailComposerListHeaders;
|
type ListHeaders = MailComposerListHeaders;
|
||||||
|
type Otp = MailComposerOtp;
|
||||||
type Envelope = MimeNodeEnvelopeInput;
|
type Envelope = MimeNodeEnvelopeInput;
|
||||||
type Connection = SocketOptions;
|
type Connection = SocketOptions;
|
||||||
type TextEncoding = MailComposerTextEncoding;
|
type TextEncoding = MailComposerTextEncoding;
|
||||||
type PluginFunction<T = SentMessageInfo> = MailPluginFunction<T>;
|
type PluginFunction<T = SentMessageInfo> = MailPluginFunction<T>;
|
||||||
}
|
}
|
||||||
/** The same aliases as module level exports, for `import * as Mail` and `import Mail = require()` */
|
/** The same aliases as module level exports, for `import * as Mail` and `import Mail = require()` */
|
||||||
export type { SendMailOptions as Options, MimeNodeAddress as Address, MailComposerAttachment as Attachment, MailComposerAlternative as AttachmentLike, MailComposerAlternative as AmpAttachment, MailComposerIcalEvent as IcalAttachment, MimeNodeHeaders as Headers, MailComposerListHeaderEntry as ListHeader, MailComposerListHeaders as ListHeaders, MimeNodeEnvelopeInput as Envelope, SocketOptions as Connection, MailComposerTextEncoding as TextEncoding };
|
export type { SendMailOptions as Options, MimeNodeAddress as Address, MailComposerAttachment as Attachment, MailComposerAlternative as AttachmentLike, MailComposerAlternative as AmpAttachment, MailComposerIcalEvent as IcalAttachment, MimeNodeHeaders as Headers, MailComposerListHeaderEntry as ListHeader, MailComposerListHeaders as ListHeaders, MailComposerOtp as Otp, MimeNodeEnvelopeInput as Envelope, SocketOptions as Connection, MailComposerTextEncoding as TextEncoding };
|
||||||
export default Mail;
|
export default Mail;
|
||||||
+1
@@ -145,6 +145,7 @@ class Mail extends EventEmitter {
|
|||||||
mail.setMailerHeader();
|
mail.setMailerHeader();
|
||||||
mail.setPriorityHeaders();
|
mail.setPriorityHeaders();
|
||||||
mail.setListHeaders();
|
mail.setListHeaders();
|
||||||
|
mail.setOtpHeader();
|
||||||
recipientCount = mail.message.getEnvelope().to.length;
|
recipientCount = mail.message.getEnvelope().to.length;
|
||||||
}
|
}
|
||||||
catch (err) {
|
catch (err) {
|
||||||
|
|||||||
+7
@@ -103,4 +103,11 @@ export default class MailMessage<T = SentMessageInfo> {
|
|||||||
setMailerHeader(): void;
|
setMailerHeader(): void;
|
||||||
setPriorityHeaders(): void;
|
setPriorityHeaders(): void;
|
||||||
setListHeaders(): void;
|
setListHeaders(): void;
|
||||||
|
/**
|
||||||
|
* Sets the OTP-Token header from the otp option. The header is defined by
|
||||||
|
* draft-goto-otp-token-01, an Internet-Draft that expires on 25 March 2027. By then check
|
||||||
|
* for a newer revision of the draft or a published RFC, the header name and its
|
||||||
|
* parameters are still open questions in the draft.
|
||||||
|
*/
|
||||||
|
setOtpHeader(): void;
|
||||||
}
|
}
|
||||||
+54
@@ -1,6 +1,7 @@
|
|||||||
import * as shared from '../shared/index.js';
|
import * as shared from '../shared/index.js';
|
||||||
import MimeNode from '../mime-node/index.js';
|
import MimeNode from '../mime-node/index.js';
|
||||||
import * as mimeFuncs from '../mime-funcs/index.js';
|
import * as mimeFuncs from '../mime-funcs/index.js';
|
||||||
|
import * as errors from '../errors.js';
|
||||||
// Only an own key counts as already set. `key in obj` also matches every member of
|
// Only an own key counts as already set. `key in obj` also matches every member of
|
||||||
// Object.prototype, which silently drops a transporter default legitimately named
|
// Object.prototype, which silently drops a transporter default legitimately named
|
||||||
// toString or constructor.
|
// toString or constructor.
|
||||||
@@ -262,6 +263,26 @@ export default class MailMessage {
|
|||||||
});
|
});
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
/**
|
||||||
|
* Sets the OTP-Token header from the otp option. The header is defined by
|
||||||
|
* draft-goto-otp-token-01, an Internet-Draft that expires on 25 March 2027. By then check
|
||||||
|
* for a newer revision of the draft or a published RFC, the header name and its
|
||||||
|
* parameters are still open questions in the draft.
|
||||||
|
*/
|
||||||
|
setOtpHeader() {
|
||||||
|
const otp = this.data.otp;
|
||||||
|
if (!this.message || !otp) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
// a structured field String can only hold printable ascii. The code is a secret the recipient
|
||||||
|
// compares against the one in the message body, so it is rejected rather than cleaned up
|
||||||
|
if (typeof otp.code !== 'string' || !/^[\x20-\x7e]+$/.test(otp.code)) {
|
||||||
|
throw otpError('The otp code must be a non-empty string of printable ASCII characters');
|
||||||
|
}
|
||||||
|
// a structured field String escapes the same two characters as a quoted-string. setHeader
|
||||||
|
// replaces any OTP-Token from the custom headers, the field may appear only once
|
||||||
|
this.message.setHeader('OTP-Token', mimeFuncs.quoteString(otp.code) + '; origin=' + mimeFuncs.quoteString(getOtpOrigin(otp.origin)));
|
||||||
|
}
|
||||||
/** @internal */
|
/** @internal */
|
||||||
_getListHeaders(listData) {
|
_getListHeaders(listData) {
|
||||||
// make sure an url looks like <protocol:url>
|
// make sure an url looks like <protocol:url>
|
||||||
@@ -321,3 +342,36 @@ export default class MailMessage {
|
|||||||
return '<http://' + url + '>';
|
return '<http://' + url + '>';
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
/**
|
||||||
|
* Returns the canonical ascii serialization of the origin the code is bound to. The draft
|
||||||
|
* requires a potentially trustworthy origin, so only https is accepted, plus http on a
|
||||||
|
* loopback host for local development
|
||||||
|
*/
|
||||||
|
function getOtpOrigin(origin) {
|
||||||
|
let url;
|
||||||
|
try {
|
||||||
|
url = new URL(String(origin));
|
||||||
|
}
|
||||||
|
catch {
|
||||||
|
// handled below
|
||||||
|
}
|
||||||
|
// the WHATWG parser lets characters such as a quote or a brace into a host, which the
|
||||||
|
// origin grammar the draft requires (RFC 6454) does not allow, so a receiver would discard
|
||||||
|
// the header. Only a DNS name, an IPv4 address or a bracketed IPv6 address is accepted.
|
||||||
|
// Userinfo is refused as well, an origin never has it and it turns a value built as
|
||||||
|
// 'https://example.com' + input into the origin of whatever host follows an "@"
|
||||||
|
if (!url || url.username || url.password || !/^([a-z0-9_-]+\.)*[a-z0-9_-]+$|^\[[0-9a-f:.]+\]$/.test(url.hostname)) {
|
||||||
|
throw otpError('The otp origin must be an absolute https URL');
|
||||||
|
}
|
||||||
|
const isLoopback = /^(localhost|.+\.localhost|127(\.\d+){3}|\[::1\])$/.test(url.hostname);
|
||||||
|
if (url.protocol !== 'https:' && !(url.protocol === 'http:' && isLoopback)) {
|
||||||
|
throw otpError('The otp origin must be an https URL, or an http URL on a loopback host');
|
||||||
|
}
|
||||||
|
// the URL parser already lowercases the host, converts it to punycode and drops the default port
|
||||||
|
return url.origin;
|
||||||
|
}
|
||||||
|
function otpError(message) {
|
||||||
|
const err = new Error(message);
|
||||||
|
err.code = errors.EOTP;
|
||||||
|
return err;
|
||||||
|
}
|
||||||
+18
-1
@@ -107,6 +107,22 @@ export declare function buildHeaderValue(structured: StructuredHeaderValue): str
|
|||||||
* @return A list of encoded keys and headers
|
* @return A list of encoded keys and headers
|
||||||
*/
|
*/
|
||||||
export declare function buildHeaderParam(key: string, data: string | Buffer, maxLength?: number): EncodedHeaderParam[];
|
export declare function buildHeaderParam(key: string, data: string | Buffer, maxLength?: number): EncodedHeaderParam[];
|
||||||
|
/**
|
||||||
|
* Options of parseHeaderValue()
|
||||||
|
*/
|
||||||
|
export interface ParseHeaderValueOptions {
|
||||||
|
/**
|
||||||
|
* Drop the RFC 822 comments outside quoted strings, which the MIME headers allow and
|
||||||
|
* which are not part of the value: 'boundary=b (comment)' names the boundary 'b'. A
|
||||||
|
* parenthesis right behind the '=' of a parameter or behind a token character of its
|
||||||
|
* value continues the token, as it does for Python and postal-mime, so 'Invoice(1).pdf'
|
||||||
|
* keeps its name and 'boundary=(c)b' names the boundary '(c)b'. A comment that never
|
||||||
|
* closes runs to the end of the value, unless a parameter follows it, in which case it is
|
||||||
|
* content and the parameter survives. Comments are kept by default for the sake of the
|
||||||
|
* callers of this exported function, not because keeping them is ever the better reading
|
||||||
|
*/
|
||||||
|
stripComments?: boolean | undefined;
|
||||||
|
}
|
||||||
/**
|
/**
|
||||||
* Parses a header value with key=value arguments into a structured
|
* Parses a header value with key=value arguments into a structured
|
||||||
* object.
|
* object.
|
||||||
@@ -120,9 +136,10 @@ export declare function buildHeaderParam(key: string, data: string | Buffer, max
|
|||||||
* }
|
* }
|
||||||
*
|
*
|
||||||
* @param str Header value
|
* @param str Header value
|
||||||
|
* @param options Options, see ParseHeaderValueOptions
|
||||||
* @return Header value as a parsed structure
|
* @return Header value as a parsed structure
|
||||||
*/
|
*/
|
||||||
export declare function parseHeaderValue(str: string): ParsedHeaderValue;
|
export declare function parseHeaderValue(str: string, options?: ParseHeaderValueOptions): ParsedHeaderValue;
|
||||||
/**
|
/**
|
||||||
* Returns file extension for a content type string. If no suitable extensions
|
* Returns file extension for a content type string. If no suitable extensions
|
||||||
* are found, 'bin' is used as the default extension
|
* are found, 'bin' is used as the default extension
|
||||||
|
|||||||
+43
-1
@@ -365,6 +365,26 @@ export function buildHeaderParam(key, data, maxLength) {
|
|||||||
* goes in. Whitespace used to come off it only because the value was trimmed first.
|
* goes in. Whitespace used to come off it only because the value was trimmed first.
|
||||||
*/
|
*/
|
||||||
const TOKEN = /^[^\x00-\x20\x7f()<>@,;:\\"/[\]?=]+$/;
|
const TOKEN = /^[^\x00-\x20\x7f()<>@,;:\\"/[\]?=]+$/;
|
||||||
|
/**
|
||||||
|
* Offset of the ')' that closes the RFC 822 comment opening at `start`, or -1 when the
|
||||||
|
* comment never closes. Comments nest, and a backslash escapes the character after it
|
||||||
|
*/
|
||||||
|
function _findCommentEnd(str, start) {
|
||||||
|
let depth = 0;
|
||||||
|
for (let i = start, len = str.length; i < len; i++) {
|
||||||
|
const c = str.charAt(i);
|
||||||
|
if (c === '\\') {
|
||||||
|
i++;
|
||||||
|
}
|
||||||
|
else if (c === '(') {
|
||||||
|
depth++;
|
||||||
|
}
|
||||||
|
else if (c === ')' && --depth === 0) {
|
||||||
|
return i;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
/**
|
/**
|
||||||
* Whether a string can be a header parameter name.
|
* Whether a string can be a header parameter name.
|
||||||
*
|
*
|
||||||
@@ -391,13 +411,16 @@ function _isParamName(name) {
|
|||||||
* }
|
* }
|
||||||
*
|
*
|
||||||
* @param str Header value
|
* @param str Header value
|
||||||
|
* @param options Options, see ParseHeaderValueOptions
|
||||||
* @return Header value as a parsed structure
|
* @return Header value as a parsed structure
|
||||||
*/
|
*/
|
||||||
export function parseHeaderValue(str) {
|
export function parseHeaderValue(str, options) {
|
||||||
const response = {
|
const response = {
|
||||||
value: '',
|
value: '',
|
||||||
params: {}
|
params: {}
|
||||||
};
|
};
|
||||||
|
// Cleared again by an unclosed '(', see the loop
|
||||||
|
let stripComments = !!options?.stripComments;
|
||||||
// A duplicated parameter resolves to its first occurrence, the way a duplicated header
|
// A duplicated parameter resolves to its first occurrence, the way a duplicated header
|
||||||
// does. Letting the last one win disagrees with the receivers that take the first, and
|
// does. Letting the last one win disagrees with the receivers that take the first, and
|
||||||
// the two readings of 'boundary="b"; boundary="c"' name different delimiters. The
|
// the two readings of 'boundary="b"; boundary="c"' name different delimiters. The
|
||||||
@@ -459,6 +482,25 @@ export function parseHeaderValue(str) {
|
|||||||
};
|
};
|
||||||
for (let i = 0, len = str.length; i < len; i++) {
|
for (let i = 0, len = str.length; i < len; i++) {
|
||||||
chr = str.charAt(i);
|
chr = str.charAt(i);
|
||||||
|
// With stripComments, an RFC 822 comment stands anywhere outside a quoted string but
|
||||||
|
// right behind a token it would continue, see ParseHeaderValueOptions. In the value
|
||||||
|
// of a parameter, no pending whitespace means the last significant character was the
|
||||||
|
// '=' or a character of the token
|
||||||
|
if (stripComments && chr === '(' && !quote && !quoteClosed && !(stage === 'value' && key !== false && !pendingSpace)) {
|
||||||
|
let end = _findCommentEnd(str, i);
|
||||||
|
if (end < 0 && str.indexOf(';', i) < 0) {
|
||||||
|
// a comment that never closes runs to the end of the value, unless a
|
||||||
|
// parameter follows it, which is content then and survives
|
||||||
|
end = len - 1;
|
||||||
|
}
|
||||||
|
if (end >= 0) {
|
||||||
|
i = end;
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
// looking for a closing ')' again would scan a run of unclosed '(' to its end
|
||||||
|
// once for each of them
|
||||||
|
stripComments = false;
|
||||||
|
}
|
||||||
if (stage === 'key') {
|
if (stage === 'key') {
|
||||||
if (chr === '=') {
|
if (chr === '=') {
|
||||||
key = takeValue().trim();
|
key = takeValue().trim();
|
||||||
|
|||||||
+15
-4
@@ -616,15 +616,18 @@ class MimeNode {
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
switch (header.key) {
|
switch (header.key) {
|
||||||
|
// The comments of a MIME header are not part of its value and are dropped, so a
|
||||||
|
// caller supplied 'boundary=b (comment)' is the boundary b it names, and the
|
||||||
|
// header goes out without the comment
|
||||||
case 'Content-Disposition':
|
case 'Content-Disposition':
|
||||||
structured = mimeFuncs.parseHeaderValue(value);
|
structured = mimeFuncs.parseHeaderValue(value, { stripComments: true });
|
||||||
if (this.filename) {
|
if (this.filename) {
|
||||||
structured.params.filename = this.filename;
|
structured.params.filename = this.filename;
|
||||||
}
|
}
|
||||||
value = mimeFuncs.buildHeaderValue(structured);
|
value = mimeFuncs.buildHeaderValue(structured);
|
||||||
break;
|
break;
|
||||||
case 'Content-Type':
|
case 'Content-Type':
|
||||||
structured = mimeFuncs.parseHeaderValue(value);
|
structured = mimeFuncs.parseHeaderValue(value, { stripComments: true });
|
||||||
// the type token decides multipart and charset below, so clean it before
|
// the type token decides multipart and charset below, so clean it before
|
||||||
// those run and not just on the way out, otherwise a control char makes
|
// those run and not just on the way out, otherwise a control char makes
|
||||||
// the checks miss and the header ends up claiming a type it is not set up for
|
// the checks miss and the header ends up claiming a type it is not set up for
|
||||||
@@ -1261,7 +1264,7 @@ class MimeNode {
|
|||||||
.replace(/[\s\x00-\x20\x7f:]/g, '')
|
.replace(/[\s\x00-\x20\x7f:]/g, '')
|
||||||
.toLowerCase()
|
.toLowerCase()
|
||||||
// use uppercase words, except MIME
|
// use uppercase words, except MIME
|
||||||
.replace(/^X-SMTPAPI$|^(MIME|DKIM|ARC|BIMI)\b|^[a-z]|-(SPF|FBL|ID|MD5)$|-[a-z]/gi, c => c.toUpperCase())
|
.replace(/^X-SMTPAPI$|^(MIME|DKIM|ARC|BIMI)\b|^OTP(?=-Token$)|^[a-z]|-(SPF|FBL|ID|MD5)$|-[a-z]/gi, c => c.toUpperCase())
|
||||||
// special case
|
// special case
|
||||||
.replace(/^Content-Features$/i, 'Content-features');
|
.replace(/^Content-Features$/i, 'Content-features');
|
||||||
return key;
|
return key;
|
||||||
@@ -1333,7 +1336,7 @@ class MimeNode {
|
|||||||
// values enclosed in <>
|
// values enclosed in <>
|
||||||
case 'Message-ID':
|
case 'Message-ID':
|
||||||
case 'In-Reply-To':
|
case 'In-Reply-To':
|
||||||
case 'Content-Id':
|
case 'Content-ID':
|
||||||
// a msg-id is structured, so an encoded word inside the angle brackets would
|
// a msg-id is structured, so an encoded word inside the angle brackets would
|
||||||
// be read as literal text. drop the characters that can not appear in a header
|
// be read as literal text. drop the characters that can not appear in a header
|
||||||
// at all, but leave HT alone, it separates the ids of a multi id value
|
// at all, but leave HT alone, it separates the ids of a multi id value
|
||||||
@@ -1348,6 +1351,14 @@ class MimeNode {
|
|||||||
value = value + '>';
|
value = value + '>';
|
||||||
}
|
}
|
||||||
return value;
|
return value;
|
||||||
|
// a structured field (RFC 9651) that can not be mime encoded, an encoded word would
|
||||||
|
// turn its quoted strings into text that no structured field parser reads. Only
|
||||||
|
// the characters that can not appear in a header at all are removed
|
||||||
|
case 'OTP-Token':
|
||||||
|
return (value || '')
|
||||||
|
.toString()
|
||||||
|
.replace(/\r?\n|\r/g, ' ')
|
||||||
|
.replace(/[\x00-\x08\x0b\x0c\x0e-\x1f\x7f]/g, '');
|
||||||
// space separated list of values enclosed in <>
|
// space separated list of values enclosed in <>
|
||||||
case 'References': {
|
case 'References': {
|
||||||
const ids = [];
|
const ids = [];
|
||||||
|
|||||||
+1
-1
@@ -111,7 +111,7 @@ export type { Mail, MailDefaults, SentMessageInfo, Transport, TransportOptions }
|
|||||||
export type { SendMailOptions, Transporter } from './mailer/index.js';
|
export type { SendMailOptions, Transporter } from './mailer/index.js';
|
||||||
export type { NodemailerError, ErrorCode } from './errors.js';
|
export type { NodemailerError, ErrorCode } from './errors.js';
|
||||||
export type { MailMessage, MailMessageData, PluginFunction, VerifyCallback } from './mailer/index.js';
|
export type { MailMessage, MailMessageData, PluginFunction, VerifyCallback } from './mailer/index.js';
|
||||||
export type { MailComposerOptions, MailComposerAttachment as Attachment, MailComposerAlternative as AttachmentLike, MailComposerIcalEvent as IcalAttachment, MailComposerListHeaders as ListHeaders } from './mail-composer/index.js';
|
export type { MailComposerOptions, MailComposerAttachment as Attachment, MailComposerAlternative as AttachmentLike, MailComposerIcalEvent as IcalAttachment, MailComposerListHeaders as ListHeaders, MailComposerOtp as Otp } from './mail-composer/index.js';
|
||||||
export type { MimeNodeAddress as Address, MimeNodeHeaders as Headers } from './mime-node/index.js';
|
export type { MimeNodeAddress as Address, MimeNodeHeaders as Headers } from './mime-node/index.js';
|
||||||
export type { SMTPConnectionOptions, SMTPConnectionAuth, SMTPEnvelope, SMTPEnvelopeDsn } from './smtp-connection/index.js';
|
export type { SMTPConnectionOptions, SMTPConnectionAuth, SMTPEnvelope, SMTPEnvelopeDsn } from './smtp-connection/index.js';
|
||||||
export type { DKIMOptions } from './dkim/index.js';
|
export type { DKIMOptions } from './dkim/index.js';
|
||||||
|
|||||||
+1
-1
@@ -1,3 +1,3 @@
|
|||||||
export declare const name = "nodemailer";
|
export declare const name = "nodemailer";
|
||||||
export declare const version = "10.2.0";
|
export declare const version = "10.3.0";
|
||||||
export declare const homepage = "https://nodemailer.com/";
|
export declare const homepage = "https://nodemailer.com/";
|
||||||
+1
-1
@@ -1,4 +1,4 @@
|
|||||||
// Generated by scripts/build.js from package.json. Do not edit by hand.
|
// Generated by scripts/build.js from package.json. Do not edit by hand.
|
||||||
export const name = 'nodemailer';
|
export const name = 'nodemailer';
|
||||||
export const version = '10.2.0';
|
export const version = '10.3.0';
|
||||||
export const homepage = 'https://nodemailer.com/';
|
export const homepage = 'https://nodemailer.com/';
|
||||||
+7
@@ -205,6 +205,13 @@ export const services = {
|
|||||||
"host": "smtp.europe.secureserver.net",
|
"host": "smtp.europe.secureserver.net",
|
||||||
"port": 25
|
"port": 25
|
||||||
},
|
},
|
||||||
|
"Groundmail": {
|
||||||
|
"description": "Groundmail",
|
||||||
|
"host": "smtp.groundmail.dev",
|
||||||
|
"port": 587,
|
||||||
|
"secure": false,
|
||||||
|
"requireTLS": true
|
||||||
|
},
|
||||||
"hot.ee": {
|
"hot.ee": {
|
||||||
"description": "Hot.ee (Estonian email provider)",
|
"description": "Hot.ee (Estonian email provider)",
|
||||||
"host": "mail.hot.ee"
|
"host": "mail.hot.ee"
|
||||||
|
|||||||
+8
@@ -180,6 +180,14 @@
|
|||||||
"port": 25
|
"port": 25
|
||||||
},
|
},
|
||||||
|
|
||||||
|
"Groundmail": {
|
||||||
|
"description": "Groundmail",
|
||||||
|
"host": "smtp.groundmail.dev",
|
||||||
|
"port": 587,
|
||||||
|
"secure": false,
|
||||||
|
"requireTLS": true
|
||||||
|
},
|
||||||
|
|
||||||
"hot.ee": {
|
"hot.ee": {
|
||||||
"description": "Hot.ee (Estonian email provider)",
|
"description": "Hot.ee (Estonian email provider)",
|
||||||
"host": "mail.hot.ee"
|
"host": "mail.hot.ee"
|
||||||
|
|||||||
+8
-8
@@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"name": "nodemailer",
|
"name": "nodemailer",
|
||||||
"version": "10.2.0",
|
"version": "10.3.0",
|
||||||
"description": "Easy as cake e-mail sending from your Node.js applications",
|
"description": "Easy as cake e-mail sending from your Node.js applications",
|
||||||
"type": "module",
|
"type": "module",
|
||||||
"main": "./dist/cjs/nodemailer.js",
|
"main": "./dist/cjs/nodemailer.js",
|
||||||
@@ -153,24 +153,24 @@
|
|||||||
},
|
},
|
||||||
"homepage": "https://nodemailer.com/",
|
"homepage": "https://nodemailer.com/",
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@aws-sdk/client-sesv2": "3.1151.0",
|
"@aws-sdk/client-sesv2": "3.1156.0",
|
||||||
"@types/node": "20.19.43",
|
"@types/node": "20.19.43",
|
||||||
"bunyan": "1.8.15",
|
"bunyan": "1.8.15",
|
||||||
"c8": "12.0.0",
|
"c8": "12.0.0",
|
||||||
"eslint": "10.13.0",
|
"eslint": "10.13.0",
|
||||||
"eslint-config-prettier": "10.1.8",
|
"eslint-config-prettier": "10.1.8",
|
||||||
"globals": "17.13.0",
|
"globals": "17.13.0",
|
||||||
"libbase64": "1.3.2",
|
"libbase64": "1.4.1",
|
||||||
"libmime": "5.4.7",
|
"libmime": "5.6.3",
|
||||||
"libqp": "2.1.2",
|
"libqp": "3.0.1",
|
||||||
"mailauth": "7.3.0",
|
"mailauth": "8.0.4",
|
||||||
"prettier": "3.9.10",
|
"prettier": "3.9.10",
|
||||||
"proxy": "1.0.2",
|
"proxy": "1.0.2",
|
||||||
"proxy-test-server": "1.0.0",
|
"proxy-test-server": "1.0.0",
|
||||||
"smtp-server": "3.19.21",
|
"smtp-server": "3.21.1",
|
||||||
"tsx": "4.23.15",
|
"tsx": "4.23.15",
|
||||||
"typescript": "6.0.3",
|
"typescript": "6.0.3",
|
||||||
"typescript-eslint": "8.71.2"
|
"typescript-eslint": "8.72.0"
|
||||||
},
|
},
|
||||||
"engines": {
|
"engines": {
|
||||||
"node": ">=20.0.0"
|
"node": ">=20.0.0"
|
||||||
|
|||||||
Reference in new issue
Block a user