From 7f8ce796d036657cd4d47866ed148cfb00b7141e Mon Sep 17 00:00:00 2001 From: Marika Marszalkowski <868536+marikaner@users.noreply.github.com> Date: Thu, 10 Oct 2024 15:59:15 +0200 Subject: [PATCH] =?UTF-8?q?fix:=20add=20workaround=20for=20=E2=80=9Cgreeti?= =?UTF-8?q?ng=20never=20received=E2=80=9D=20(#5079)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * fix: add workaround for “greeting never received” * add changeset * include suggestions from review * Changes from lint:fix * improve tests --------- Co-authored-by: cloud-sdk-js --- .changeset/yellow-poets-ring.md | 5 + packages/mail-client/package.json | 1 + packages/mail-client/src/mail-client-types.ts | 18 -- packages/mail-client/src/mail-client.spec.ts | 159 ++++++++++++----- packages/mail-client/src/mail-client.ts | 71 ++++++-- yarn.lock | 166 ++++++++---------- 6 files changed, 252 insertions(+), 168 deletions(-) create mode 100644 .changeset/yellow-poets-ring.md diff --git a/.changeset/yellow-poets-ring.md b/.changeset/yellow-poets-ring.md new file mode 100644 index 0000000000..75129c2032 --- /dev/null +++ b/.changeset/yellow-poets-ring.md @@ -0,0 +1,5 @@ +--- +'@sap-cloud-sdk/mail-client': patch +--- + +[Fixed Issue] Fix sending e-mails through socks proxies in Node 20 and higher. diff --git a/packages/mail-client/package.json b/packages/mail-client/package.json index 02fd4c2ad7..bf1f26f7f4 100644 --- a/packages/mail-client/package.json +++ b/packages/mail-client/package.json @@ -40,6 +40,7 @@ "dependencies": { "@sap-cloud-sdk/connectivity": "^3.22.1", "@sap-cloud-sdk/util": "^3.22.1", + "async-retry": "^1.3.3", "nodemailer": "6.9.15", "socks": "2.8.3" }, diff --git a/packages/mail-client/src/mail-client-types.ts b/packages/mail-client/src/mail-client-types.ts index 2cd25c37cf..9155ebb70a 100644 --- a/packages/mail-client/src/mail-client-types.ts +++ b/packages/mail-client/src/mail-client-types.ts @@ -339,21 +339,3 @@ export interface SmtpTransportOptions { */ proxy?: string; } - -/** - * @internal - */ -interface ReadableState { - readableListening: boolean; -} - -/** - * Represents a socket object used for On-Premise proxy. - * @internal - */ -export interface SocksSocket extends net.Socket { - /** - * @internal - */ - _readableState: ReadableState; -} diff --git a/packages/mail-client/src/mail-client.spec.ts b/packages/mail-client/src/mail-client.spec.ts index 5575b5ec3f..c2cb330b8a 100644 --- a/packages/mail-client/src/mail-client.spec.ts +++ b/packages/mail-client/src/mail-client.spec.ts @@ -1,3 +1,4 @@ +import { EventEmitter } from 'node:stream'; import nodemailer from 'nodemailer'; import { SocksClient } from 'socks'; import { registerDestination } from '@sap-cloud-sdk/connectivity'; @@ -25,15 +26,6 @@ describe('mail client', () => { jest.resetAllMocks(); }); - const mockSocket = { - socket: { - _readableState: { - readableListening: false - }, - end: jest.fn(), - destroy: jest.fn() - } - }; const mockTransport = { sendMail: jest.fn(), close: jest.fn(), @@ -86,12 +78,11 @@ describe('mail client', () => { }); it('should work with destination from service - proxy-type OnPremise', async () => { - jest - .spyOn(SocksClient, 'createConnection') - .mockReturnValue(mockSocket as any); + mockSocketConnection(); jest .spyOn(nodemailer, 'createTransport') .mockReturnValue(mockTransport as any); + const mailOptions1: MailConfig = { from: 'from2@example.com', to: 'to2@example.com' @@ -215,27 +206,17 @@ describe('mail client', () => { await expect( sendMail(destination, [mailOptions1, mailOptions2], mailClientOptions) ).resolves.not.toThrow(); - expect(spyCreateTransport).toBeCalledTimes(1); - expect(spyCreateTransport).toBeCalledWith( + expect(spyCreateTransport).toHaveBeenCalledTimes(1); + expect(spyCreateTransport).toHaveBeenCalledWith( expect.objectContaining(mailClientOptions) ); - expect(spySendMail).toBeCalledTimes(2); - expect(spySendMail).toBeCalledWith(mailOptions1); - expect(spySendMail).toBeCalledWith(mailOptions2); - expect(spyCloseTransport).toBeCalledTimes(1); + expect(spySendMail).toHaveBeenCalledTimes(2); + expect(spySendMail).toHaveBeenCalledWith(mailOptions1); + expect(spySendMail).toHaveBeenCalledWith(mailOptions2); + expect(spyCloseTransport).toHaveBeenCalledTimes(1); }); - it('[OnPrem] should create transport/socket, send mails and close the transport/socket', async () => { - const spyCreateSocket = jest - .spyOn(SocksClient, 'createConnection') - .mockReturnValue(mockSocket as any); - const spyCreateTransport = jest - .spyOn(nodemailer, 'createTransport') - .mockReturnValue(mockTransport as any); - const spySendMail = jest.spyOn(mockTransport, 'sendMail'); - const spyCloseTransport = jest.spyOn(mockTransport, 'close'); - const spyEndSocket = jest.spyOn(mockSocket.socket, 'end'); - const spyDestroySocket = jest.spyOn(mockSocket.socket, 'destroy'); + describe('on premise', () => { const destination: any = { originalProperties: { 'mail.password': 'password', @@ -258,25 +239,96 @@ describe('mail client', () => { from: 'from1@example.com', to: 'to1@example.com' }; - await expect( - sendMail(destination, mailOptions, { sdkOptions: { parallel: false } }) - ).resolves.not.toThrow(); - expect(spyCreateSocket).toBeCalledTimes(1); - expect(spyCreateTransport).toBeCalledTimes(1); - expect(spySendMail).toBeCalledTimes(1); - expect(spySendMail).toBeCalledWith(mailOptions); - expect(spyCloseTransport).toBeCalledTimes(1); - expect(spyEndSocket).toBeCalledTimes(1); - expect(spyDestroySocket).toBeCalledTimes(1); + + it('should create transport/socket, send mails and close the transport/socket', async () => { + const { connection, createConnectionSpy } = mockSocketConnection(); + const spyCreateTransport = jest + .spyOn(nodemailer, 'createTransport') + .mockReturnValue(mockTransport as any); + const spySendMail = jest.spyOn(mockTransport, 'sendMail'); + + const spyCloseTransport = jest.spyOn(mockTransport, 'close'); + const spyEndSocket = jest.spyOn(connection.socket, 'end'); + const spyDestroySocket = jest.spyOn(connection.socket, 'destroy'); + + await expect( + sendMail(destination, mailOptions, { sdkOptions: { parallel: false } }) + ).resolves.not.toThrow(); + expect(createConnectionSpy).toHaveBeenCalledTimes(1); + expect(spyCreateTransport).toHaveBeenCalledTimes(1); + expect(spySendMail).toHaveBeenCalledTimes(1); + expect(spySendMail).toHaveBeenCalledWith(mailOptions); + expect(spyCloseTransport).toHaveBeenCalledTimes(1); + expect(spyEndSocket).toHaveBeenCalledTimes(1); + expect(spyDestroySocket).toHaveBeenCalledTimes(1); + }); + + it('should resend greeting', async () => { + const { connection } = mockSocketConnection(); + jest + .spyOn(nodemailer, 'createTransport') + .mockReturnValue(mockTransport as any); + + const req = sendMail(destination, mailOptions, { + sdkOptions: { parallel: false } + }); + + // The socket emits data for the first time before nodemailer listens to it. + // We re-emit the data until a listener listened for it. + // In this test we listen for the data event to check that we in fact re-emit the message. + const emitsTwice = new Promise(resolve => { + let dataEmitCount = 0; + const collectedData: string[] = []; + connection.socket.on('data', data => { + dataEmitCount++; + collectedData.push(data.toString()); + if (dataEmitCount === 2) { + resolve(collectedData); + } + }); + }); + + await expect(emitsTwice).resolves.toEqual([ + '220 smtp.gmail.com ESMTP', + '220 smtp.gmail.com ESMTP' + ]); + await expect(req).resolves.not.toThrow(); + }); + + it('should fail if nodemailer never listens to greeting', async () => { + mockSocketConnection(); + + jest + .spyOn(nodemailer, 'createTransport') + .mockReturnValue(mockTransport as any); + + const req = sendMail(destination, mailOptions, { + sdkOptions: { parallel: false } + }); + // jest.useFakeTimers(); + // jest.advanceTimersByTime(1000); + + await expect(req).rejects.toThrow(); + }, 10000); + + it('should throw if greeting (really) was not received', async () => { + mockSocketConnection(true); + + await expect(() => + sendMail(destination, mailOptions, { + sdkOptions: { parallel: false } + }) + ).rejects.toThrowErrorMatchingInlineSnapshot('"Something went wrong"'); + }); }); }); describe('isMailSentInSequential', () => { - it('should return false when the mail client options is undefined', () => { + it('should return false when the mail client options are undefined', () => { expect(isMailSentInSequential()).toBe(false); }); - it('should return false when the sdk options is undefined', () => { + it('should return false when the sdk options are undefined', () => { const mailClientOptions: MailClientOptions = {}; expect(isMailSentInSequential(mailClientOptions)).toBe(false); }); @@ -327,3 +379,28 @@ function isValidSocksProxy(proxy) { (proxy.type === 4 || proxy.type === 5) ); } + +function mockSocketConnection(fail = false) { + class MockSocket extends EventEmitter { + end = jest.fn(); + destroy = jest.fn(); + } + + const connection = { + socket: new MockSocket() + }; + const createConnectionSpy = jest + .spyOn(SocksClient, 'createConnection') + .mockImplementation(() => { + setImmediate(() => { + if (fail) { + connection.socket.emit('error', 'Something went wrong'); + } else { + connection.socket.emit('data', '220 smtp.gmail.com ESMTP'); + } + }); + return connection as any; + }); + + return { connection, createConnectionSpy }; +} diff --git a/packages/mail-client/src/mail-client.ts b/packages/mail-client/src/mail-client.ts index 1931c5f2ae..acd67ca0a3 100644 --- a/packages/mail-client/src/mail-client.ts +++ b/packages/mail-client/src/mail-client.ts @@ -3,10 +3,12 @@ import { resolveDestination } from '@sap-cloud-sdk/connectivity/internal'; import { createLogger } from '@sap-cloud-sdk/util'; import nodemailer from 'nodemailer'; import { SocksClient } from 'socks'; +import retry from 'async-retry'; import { customAuthRequestHandler, customAuthResponseHandler } from './socket-proxy'; +import type { Socket } from 'node:net'; import type { SentMessageInfo, Transporter } from 'nodemailer'; import type { SocksClientOptions, SocksProxy } from 'socks'; // eslint-disable-next-line import/no-internal-modules @@ -15,8 +17,7 @@ import type { MailClientOptions, MailConfig, MailDestination, - MailResponse, - SocksSocket + MailResponse } from './mail-client-types'; import type { Destination, @@ -129,9 +130,7 @@ export function buildSocksProxy(mailDestination: MailDestination): SocksProxy { }; } -async function createSocket( - mailDestination: MailDestination -): Promise { +async function createSocket(mailDestination: MailDestination): Promise { const connectionOptions: SocksClientOptions = { proxy: buildSocksProxy(mailDestination), command: 'connect', @@ -140,20 +139,57 @@ async function createSocket( port: mailDestination.port! } }; - const socketConnection = - await SocksClient.createConnection(connectionOptions); - - const socksSocket = socketConnection.socket as SocksSocket; - // Setting `_readableListening` to true in the next line makes the socket readable. - // Otherwise nodemailer is not able to receive the SMTP greeting message and send emails. - socksSocket._readableState.readableListening = true; - return socksSocket; + const { socket } = await SocksClient.createConnection(connectionOptions); + + // Workaround for incorrect order of events in nodemailer https://github.com/nodemailer/nodemailer/issues/1684 + await resendGreetingUntilReceived(socket); + + return socket; +} + +function retrieveGreeting(socket: Socket): Promise { + logger.debug('Waiting for SMTP greeting message...'); + return new Promise((resolve, reject) => { + const onData = data => { + logger.debug(`Data received from mail socket: ${data?.toString()}`); + if (data?.toString().startsWith('220')) { + logger.debug('Removing mail socket listeners...'); + socket.removeListener('data', onData); + socket.removeListener('error', onError); + resolve(data); + } + }; + + const onError = err => { + reject(new Error(err)); + }; + + socket.on('data', onData); + socket.on('error', onError); + }); +} + +async function resendGreetingUntilReceived(socket: Socket): Promise { + const greeting = await retrieveGreeting(socket); + + await retry( + () => { + // resend the greeting message until a listener is attached + // note: this is dangerous because there could be another listener that is not the mailer + if (!socket.emit('data', greeting)) { + throw new Error( + 'Failed to re-emit greeting message. No data listener found.' + ); + } + }, + { maxRetryTime: 5000 } + ); } function createTransport( mailDestination: MailDestination, mailClientOptions?: MailClientOptions, - socket?: SocksSocket + socket?: Socket ): Transporter { const baseOptions: Options = { pool: true, @@ -239,7 +275,7 @@ async function sendMailWithNodemailer( mailConfigs: T[], mailClientOptions?: MailClientOptions ): Promise { - let socket: SocksSocket | undefined; + let socket: Socket | undefined; if (mailDestination.proxyType === 'OnPremise') { socket = await createSocket(mailDestination); } @@ -271,10 +307,7 @@ async function sendMailWithNodemailer( return response; } -function teardown( - transport: Transporter, - socket?: SocksSocket -) { +function teardown(transport: Transporter, socket?: Socket) { transport.close(); logger.debug('SMTP transport connection closed.'); if (socket) { diff --git a/yarn.lock b/yarn.lock index cb7aacc67d..5082701b56 100644 --- a/yarn.lock +++ b/yarn.lock @@ -453,6 +453,11 @@ resolved "https://registry.npmjs.org/@bcoe/v8-coverage/-/v8-coverage-0.2.3.tgz#75a2e8b51cb758a7553d6804a5932d7aace75c39" integrity sha512-0hYQ8SB4Db5zvZB4axdMHGwEaQjkZzFjQiN9LVYvIFB2nSUHW9tYpxWriPrWDASIxiaXax83REcLxuSdnGPZtw== +"@cap-js/cds-types@<=0.2.0": + version "0.2.0" + resolved "https://registry.npmjs.org/@cap-js/cds-types/-/cds-types-0.2.0.tgz#065c3df9f8891531e3d51a1e62398be14c665aa7" + integrity sha512-s4iVwAjf+rRIUu6jaEooXFcJv16+sP5CTkreQPxDUyxLWWGlhvEr67TuIH0C6Cnp4PPIsYmBK3AVxSW2mNc2wg== + "@changesets/apply-release-plan@^7.0.5": version "7.0.5" resolved "https://registry.npmjs.org/@changesets/apply-release-plan/-/apply-release-plan-7.0.5.tgz#3323c97afc08abc15e5136488f9c7cf1a864832e" @@ -841,13 +846,6 @@ wrap-ansi "^8.1.0" wrap-ansi-cjs "npm:wrap-ansi@^7.0.0" -"@isaacs/fs-minipass@^4.0.0": - version "4.0.1" - resolved "https://registry.npmjs.org/@isaacs/fs-minipass/-/fs-minipass-4.0.1.tgz#2d59ae3ab4b38fb4270bfa23d30f8e2e86c7fe32" - integrity sha512-wgm9Ehl2jpeqP3zw/7mo3kRHFp5MEDhqAdwy1fTGkHAwnkGOVsgpvQhL8B5n1qlb01jV3n/bI0ZfZp5lWA1k4w== - dependencies: - minipass "^7.0.4" - "@istanbuljs/load-nyc-config@^1.0.0": version "1.1.0" resolved "https://registry.npmjs.org/@istanbuljs/load-nyc-config/-/load-nyc-config-1.1.0.tgz#fd3db1d59ecf7cf121e80650bb86712f9b55eced" @@ -1393,6 +1391,13 @@ resolved "https://registry.npmjs.org/@rtsao/scc/-/scc-1.1.0.tgz#927dd2fae9bc3361403ac2c7a00c32ddce9ad7e8" integrity sha512-zt6OdqaDoOnJ1ZYsCYGt9YmWzDXl4vQdKTyJev62gFhRGKdx7mcT54V9KIjg+d2wi9EXsPvAPKe7i7WjfVWB8g== +"@sap/cds-compiler@>=5.1": + version "5.3.2" + resolved "https://registry.npmjs.org/@sap/cds-compiler/-/cds-compiler-5.3.2.tgz#572aee933bda3f59f3d23edcebaa661f4cb55a42" + integrity sha512-aePHQMZHb13+oQuHU+ug5bwxXO11NThBEfUyA3uMGlnjkAxa8pbzuot9pHiiAIMavXLcqTzbCfm/uFGKcfjqBQ== + dependencies: + antlr4 "4.9.3" + "@sap/cds-compiler@^3.2.0": version "3.9.12" resolved "https://registry.npmjs.org/@sap/cds-compiler/-/cds-compiler-3.9.12.tgz#3e40c0956bfbe2106b50a0ee9efabeca1b68f6c8" @@ -1400,6 +1405,13 @@ dependencies: antlr4 "4.9.3" +"@sap/cds-compiler@^4": + version "4.9.8" + resolved "https://registry.npmjs.org/@sap/cds-compiler/-/cds-compiler-4.9.8.tgz#7b3eb7dc31a3463060a5e29425ab45cd6e8c5123" + integrity sha512-DaodIJoYPpVygVf+9JU6XNW+WLDqX1sHklpj8qkrONLxM6G1TFBm5yrAT0P2YCibgNsGVhizA4eydgoHnVxUVQ== + dependencies: + antlr4 "4.9.3" + "@sap/cds-dk@7.9.4": version "7.9.4" resolved "https://registry.npmjs.org/@sap/cds-dk/-/cds-dk-7.9.4.tgz#199b4698468eb88f6eb1a92508f22ecbb5fd9d89" @@ -1422,6 +1434,11 @@ optionalDependencies: sqlite3 "^5.0.4" +"@sap/cds-fiori@^1": + version "1.2.7" + resolved "https://registry.npmjs.org/@sap/cds-fiori/-/cds-fiori-1.2.7.tgz#cdfec5437e6c35f3ce1f22db35701570abfc2787" + integrity sha512-F6Uf9wvkv0fXW+Fh7PiV2BbB/k+p1cFJLkQCCKDRJH8HvlxWEcXcn/YIvBrQGuX+GToi125MxB3wd712d8OLTA== + "@sap/cds-foss@^4": version "4.0.2" resolved "https://registry.npmjs.org/@sap/cds-foss/-/cds-foss-4.0.2.tgz#2910fa0a2f3dffb6a66c787de27cbfe564467412" @@ -1451,7 +1468,7 @@ "@sap/hdi-deploy" "^4" axios "^1" -"@sap/cds@>=5.6.0", "@sap/cds@^6.1.1", "@sap/cds@^7", "@sap/cds@^8.3.1": +"@sap/cds@>=5.6.0": version "6.8.4" resolved "https://registry.npmjs.org/@sap/cds/-/cds-6.8.4.tgz#c73adb8e61f9efb4eff705f125a717f214f8e49f" integrity sha512-CTf6Y732ABTIAQPwtFZpfLUzZgQLOs9ZqBIrHhFJRx3VQvopfBZlbWAhujz092xETM7u4msZudoUprkmRo0+hg== @@ -1459,6 +1476,25 @@ "@sap/cds-compiler" "^3.2.0" "@sap/cds-foss" "^4" +"@sap/cds@^7": + version "7.9.5" + resolved "https://registry.npmjs.org/@sap/cds/-/cds-7.9.5.tgz#8e3af0438badf7232ed3cbc418e43054a6269a0f" + integrity sha512-DNCpXWhwZzIShqjoxOTW9cyYB/mE8k/7d+lSp1BCRLnc30R2vEU0ScqZVngjIZfQFRKWkLePy9/FulAEhqv+wQ== + dependencies: + "@cap-js/cds-types" "<=0.2.0" + "@sap/cds-compiler" "^4" + "@sap/cds-fiori" "^1" + "@sap/cds-foss" "^5.0.0" + +"@sap/cds@^8.3.1": + version "8.3.1" + resolved "https://registry.npmjs.org/@sap/cds/-/cds-8.3.1.tgz#755bceadc0696e9e31620bff4a5ab865bbcca157" + integrity sha512-v44OYZqeFYrH6ghfO/RqQ7eqgcNYAAQmVVFy9pIbOkxXay0uVDXB1nC3hu6cuqIYvp2PFInL0c3l1e+ycFSkPA== + dependencies: + "@sap/cds-compiler" ">=5.1" + "@sap/cds-fiori" "^1" + "@sap/cds-foss" "^5.0.0" + "@sap/eslint-plugin-cds@^3.0.1": version "3.0.2" resolved "https://registry.npmjs.org/@sap/eslint-plugin-cds/-/eslint-plugin-cds-3.0.2.tgz#c35d364fbd5a09287ea900e03db2ef5bb23aab0b" @@ -1785,11 +1821,6 @@ resolved "https://registry.npmjs.org/@types/license-checker/-/license-checker-25.0.6.tgz#c346285ee7e42bac58a4922059453f50a5d4175d" integrity sha512-ju/75+YPkNE5vX1iPer+qtI1eI/LqJVYZgOsmSHI1iiEM1bQL5Gh1lEvyjR9T7ZXVE1FwJa2doWJEEmPNwbZkw== -"@types/lodash@>=4.14.200": - version "4.17.10" - resolved "https://registry.npmjs.org/@types/lodash/-/lodash-4.17.10.tgz#64f3edf656af2fe59e7278b73d3e62404144a6e6" - integrity sha512-YpS0zzoduEhuOWjAotS6A5AVCva7X4lVlYLF0FYHAY9sdraBfnatttHItlWeZdGhuEkf+OzMNg2ZYAx8t+52uQ== - "@types/minimatch@^3.0.3": version "3.0.5" resolved "https://registry.npmjs.org/@types/minimatch/-/minimatch-3.0.5.tgz#1001cc5e6a3704b83c236027e77f2f58ea010f40" @@ -2801,7 +2832,7 @@ brace-expansion@^2.0.1: dependencies: balanced-match "^1.0.0" -braces@>=3.0.2, braces@^3.0.1, braces@^3.0.3, braces@~3.0.2: +braces@^3.0.1, braces@^3.0.3, braces@~3.0.2: version "3.0.3" resolved "https://registry.npmjs.org/braces/-/braces-3.0.3.tgz#490332f40919452272d55a8480adc0c441358789" integrity sha512-yQbXgO/OSZVD2IsiLlro+7Hf6Q18EJrKSEsdoMzKePKXct3gvD8oLcOQdIzGupr5Fj+EDe8gO/lxc1BzfMpxvA== @@ -3041,11 +3072,6 @@ chownr@^2.0.0: resolved "https://registry.npmjs.org/chownr/-/chownr-2.0.0.tgz#15bfbe53d2eab4cf70f18a8cd68ebe5b3cb1dece" integrity sha512-bIomtDF5KGpdogkLd9VspvFzk9KfpyyGlS8YFVZl7TGPBHL5snIOnxeshwVgPteQ9b4Eydl+pVbIyE1DcvCWgQ== -chownr@^3.0.0: - version "3.0.0" - resolved "https://registry.npmjs.org/chownr/-/chownr-3.0.0.tgz#9855e64ecd240a9cc4267ce8a4aa5d24a1da15e4" - integrity sha512-+IxzY9BZOQd/XuYPRmrvEVjF/nqj5kgT4kEq7VofrDoM1MxoRjEWkrCC3EtLi59TVawxTAn+orJwFQcrqEN1+g== - chromium-bidi@0.8.0: version "0.8.0" resolved "https://registry.npmjs.org/chromium-bidi/-/chromium-bidi-0.8.0.tgz#ffd79dad7db1fcc874f1c55fcf46ded05a884269" @@ -3182,7 +3208,7 @@ color-name@^1.0.0, color-name@^1.1.4, color-name@~1.1.4: resolved "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz#c2a09a87acbde69543de6f63fa3995c826c536a2" integrity sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA== -color-string@>=1.5.5, color-string@^1.6.0: +color-string@^1.6.0: version "1.9.1" resolved "https://registry.npmjs.org/color-string/-/color-string-1.9.1.tgz#4467f9146f036f855b764dfb5bf8582bf342c7a4" integrity sha512-shrVawQFojnZv6xM40anx4CkoDP+fZsw/ZerEMsW/pyzsRbElpsL/DBVW7q3ExxwusdNXI3lXpuhEZkzs8p5Eg== @@ -3659,13 +3685,12 @@ detective-es6@^5.0.0: dependencies: node-source-walk "^7.0.0" -detective-postcss@^6.1.0, detective-postcss@^7.0.0: - version "6.1.3" - resolved "https://registry.npmjs.org/detective-postcss/-/detective-postcss-6.1.3.tgz#51a2d4419327ad85d0af071c7054c79fafca7e73" - integrity sha512-7BRVvE5pPEvk2ukUWNQ+H2XOq43xENWbH0LcdCE14mwgTBEAMoAx+Fc1rdp76SmyZ4Sp48HlV7VedUnP6GA1Tw== +detective-postcss@^7.0.0: + version "7.0.0" + resolved "https://registry.npmjs.org/detective-postcss/-/detective-postcss-7.0.0.tgz#e9cff50836d67339a0bf4378f22dba4ed5809c01" + integrity sha512-pSXA6dyqmBPBuERpoOKKTUUjQCZwZPLRbd1VdsTbt6W+m/+6ROl4BbE87yQBUtLoK7yX8pvXHdKyM/xNIW9F7A== dependencies: is-url "^1.2.4" - postcss "^8.4.23" postcss-values-parser "^6.0.2" detective-sass@^6.0.0: @@ -3754,13 +3779,6 @@ doctrine@^3.0.0: dependencies: esutils "^2.0.2" -dot-prop@^6.0.1: - version "6.0.1" - resolved "https://registry.npmjs.org/dot-prop/-/dot-prop-6.0.1.tgz#fc26b3cf142b9e59b74dbd39ed66ce620c681083" - integrity sha512-tE7ztYzXHIeyvc7N+hR3oi7FIbf/NIjVP9hmAt3yMXzrQ072/fpjGLx2GxNxGxUl5V73MEqYzioOMoVhGMJ5cA== - dependencies: - is-obj "^2.0.0" - dotenv@10.0.0: version "10.0.0" resolved "https://registry.npmjs.org/dotenv/-/dotenv-10.0.0.tgz#3d4227b8fb95f81096cdd2b66653fb2c7085ba81" @@ -4909,18 +4927,6 @@ glob-parent@^6.0.2: dependencies: is-glob "^4.0.3" -glob@^10.3.7: - version "10.4.5" - resolved "https://registry.npmjs.org/glob/-/glob-10.4.5.tgz#f4d9f0b90ffdbab09c9d77f5f29b4262517b0956" - integrity sha512-7Bv8RF0k6xjo7d4A/PxYLbUCfb6c+Vpd2/mB2yRDlew7Jb5hEXiCD9ibfO7wpk8i4sevK6DFny9h7EYbM3/sHg== - dependencies: - foreground-child "^3.1.0" - jackspeak "^3.1.2" - minimatch "^9.0.4" - minipass "^7.1.2" - package-json-from-dist "^1.0.0" - path-scurry "^1.11.1" - glob@^10.4.3: version "10.4.3" resolved "https://registry.npmjs.org/glob/-/glob-10.4.3.tgz#e0ba2253dd21b3d0acdfb5d507c59a29f513fc7a" @@ -5477,11 +5483,6 @@ is-obj@^1.0.1: resolved "https://registry.npmjs.org/is-obj/-/is-obj-1.0.1.tgz#3e4729ac1f5fde025cd7d83a896dab9f4f67db0f" integrity sha512-l4RyHgRqGN4Y3+9JHVrNqO+tN0rV5My76uW5/nuO4K1b6vw5G8d/cmFjP9tRfEsdhZNt0IFdZuK/c2Vr4Nb+Qg== -is-obj@^2.0.0: - version "2.0.0" - resolved "https://registry.npmjs.org/is-obj/-/is-obj-2.0.0.tgz#473fb05d973705e3fd9620545018ca8e22ef4982" - integrity sha512-drqDG3cbczxxEJRoOXcOjtdp1J/lyp1mNn0xaznRs8+muBhgQcrnbspox5X5fOw0HnMnbfDzvnEMEtqDEJEo8w== - is-path-inside@^3.0.3: version "3.0.3" resolved "https://registry.npmjs.org/is-path-inside/-/is-path-inside-3.0.3.tgz#d231362e53a07ff2b0e0ea7fed049161ffd16283" @@ -6144,7 +6145,7 @@ json-schema-traverse@^1.0.0: resolved "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-1.0.0.tgz#ae7bcb3656ab77a73ba5c49bf654f38e6b6860e2" integrity sha512-NM8/P9n3XjXhIZn1lLhkFaACTOURQXjWhV4BA/RnOv8xvgqtqpAX9IO4mRQxSx1Rlo4tqzeqb0sOlruaOy3dug== -json-schema@0.4.0, json-schema@>=0.4.0: +json-schema@0.4.0: version "0.4.0" resolved "https://registry.npmjs.org/json-schema/-/json-schema-0.4.0.tgz#f7de4cf6efab838ebaeb3236474cbba5a1930ab5" integrity sha512-es94M3nTIfsEPisRafak+HDLfHXnKBhV3vU5eqPcS3flIWqcxJWgXHXiey3YrpaNsanY5ei1VoYEbOzijuq9BA== @@ -6735,12 +6736,17 @@ minipass@^3.0.0, minipass@^3.1.0, minipass@^3.1.1, minipass@^3.1.3: dependencies: yallist "^4.0.0" -"minipass@^5.0.0 || ^6.0.2 || ^7.0.0", minipass@^7.0.4, minipass@^7.1.2: +minipass@^5.0.0: + version "5.0.0" + resolved "https://registry.npmjs.org/minipass/-/minipass-5.0.0.tgz#3e9788ffb90b694a5d0ec94479a45b5d8738133d" + integrity sha512-3FnjYuehv9k6ovOEbyOswadCDPX1piCfhV8ncmYtHOjuPwylVWsghTLo7rabjC3Rx5xD4HDx8Wm1xnMF7S5qFQ== + +"minipass@^5.0.0 || ^6.0.2 || ^7.0.0", minipass@^7.1.2: version "7.1.2" resolved "https://registry.npmjs.org/minipass/-/minipass-7.1.2.tgz#93a9626ce5e5e66bd4db86849e7515e92340a707" integrity sha512-qOOzS1cBTWYF4BH8fVePDBOO9iptMnGUEZwNc/cMWnTV2nVLZ7VoNWEPHkYczZA0pdoA7dl6e7FL659nX9S2aw== -minizlib@^2.0.0: +minizlib@^2.0.0, minizlib@^2.1.1: version "2.1.2" resolved "https://registry.npmjs.org/minizlib/-/minizlib-2.1.2.tgz#e90d3466ba209b932451508a11ce3d3632145931" integrity sha512-bAxsR8BVfj60DWXHE3u30oHzfl4G7khkSuPW+qvpd7jFRHm7dLxOjUk1EHACJ/hxLY8phGJ0YhYHZo7jil7Qdg== @@ -6748,14 +6754,6 @@ minizlib@^2.0.0: minipass "^3.0.0" yallist "^4.0.0" -minizlib@^3.0.1: - version "3.0.1" - resolved "https://registry.npmjs.org/minizlib/-/minizlib-3.0.1.tgz#46d5329d1eb3c83924eff1d3b858ca0a31581012" - integrity sha512-umcy022ILvb5/3Djuu8LWeqUa8D68JaBzlttKeMWen48SjabqS3iY5w/vzeMzMUNhLDifyhbOwKDSznB1vvrwg== - dependencies: - minipass "^7.0.4" - rimraf "^5.0.5" - mitt@3.0.1: version "3.0.1" resolved "https://registry.npmjs.org/mitt/-/mitt-3.0.1.tgz#ea36cf0cc30403601ae074c8f77b7092cdab36d1" @@ -6778,11 +6776,6 @@ mkdirp@^0.5.1: dependencies: minimist "^1.2.6" -mkdirp@^3.0.1: - version "3.0.1" - resolved "https://registry.npmjs.org/mkdirp/-/mkdirp-3.0.1.tgz#e44e4c5607fb279c168241713cc6e0fea9adcb50" - integrity sha512-+NsyUUAZDmo6YVHzL/stxSu3t9YS1iljliy3BSDrXJ/dkn1KYdmtZODGGjLcc9XLgVVpH4KshHB8XmZgMhaBXg== - mock-fs@^5.3.0: version "5.3.0" resolved "https://registry.npmjs.org/mock-fs/-/mock-fs-5.3.0.tgz#7dfc95ce5528aff8e10fa117161b91d8129e0e9e" @@ -6907,7 +6900,7 @@ neo-async@^2.6.0: resolved "https://registry.npmjs.org/neo-async/-/neo-async-2.6.2.tgz#b4aafb93e3aeb2d8174ca53cf163ab7d7308305f" integrity sha512-Yd3UES5mWCSqR+qNT93S3UoYUkqAZ9lLg8a7g9rimsWmYGK8cVToA4/sF3RrshdyV3sAGMXVUmpMYOw+dLpOuw== -"netmask@>=2.0.1 ", netmask@^2.0.2: +netmask@^2.0.2: version "2.0.2" resolved "https://registry.npmjs.org/netmask/-/netmask-2.0.2.tgz#8b01a07644065d536383835823bc52004ebac5e7" integrity sha512-dBpDMdxv9Irdq66304OLfEmQ9tbNRFnFTuZiLo+bD+r332bBmMJ8GBLXklIXXgxd3+v9+KUnZaUR5PJMa75Gsg== @@ -7503,7 +7496,7 @@ path-key@^3.0.0, path-key@^3.1.0: resolved "https://registry.npmjs.org/path-key/-/path-key-3.1.1.tgz#581f6ade658cbba65a0d3380de7753295054f375" integrity sha512-ojmeN0qd+y0jszEtoY48r0Peq5dwMEkIlCOu6Q5f41lfkswXuKtYrhgoTpLnyIcHm24Uhqx+5Tqm2InSwLhE6Q== -path-parse@>=1.0.7, path-parse@^1.0.7: +path-parse@^1.0.7: version "1.0.7" resolved "https://registry.npmjs.org/path-parse/-/path-parse-1.0.7.tgz#fbc114b60ca42b30d9daf5858e4bd68bbedb6735" integrity sha512-LDJzPVEEEPR+y48z93A0Ed0yXb8pAByGWo/k5YYdYgpY2/2EsOsksJrq7lOHxryrVOn1ejG6oAp8ahvOIQD8sw== @@ -7695,7 +7688,7 @@ postcss-values-parser@^6.0.2: is-url-superb "^4.0.0" quote-unquote "^1.0.0" -postcss@>=8.2.10, postcss@^8.4.23, postcss@^8.4.38, postcss@^8.4.40, postcss@^8.4.47: +postcss@^8.4.38, postcss@^8.4.40, postcss@^8.4.47: version "8.4.47" resolved "https://registry.npmjs.org/postcss/-/postcss-8.4.47.tgz#5bf6c9a010f3e724c503bf03ef7947dcb0fea365" integrity sha512-56rxCq7G/XfB4EkXq9Egn5GCqugWvDFjafDOThIdMBsI15iqPqR5r15TfSr1YPYeEI19YeaXMCbY6u88Y76GLQ== @@ -8279,13 +8272,6 @@ rimraf@^3.0.2: dependencies: glob "^7.1.3" -rimraf@^5.0.5: - version "5.0.10" - resolved "https://registry.npmjs.org/rimraf/-/rimraf-5.0.10.tgz#23b9843d3dc92db71f96e1a2ce92e39fd2a8221c" - integrity sha512-l0OE8wL34P4nJH/H2ffoaniAokM2qSmrtXHmlpvYr5AVVX8msAyW0l8NVJFDxlSK4u3Uh/f41cQheDVdnYijwQ== - dependencies: - glob "^10.3.7" - run-parallel@^1.1.9: version "1.2.0" resolved "https://registry.npmjs.org/run-parallel/-/run-parallel-1.2.0.tgz#66d1368da7bdf921eb9d95bd1a9229e7f21a43ee" @@ -9092,17 +9078,17 @@ tar-stream@^3.1.5, tar-stream@^3.1.6: fast-fifo "^1.2.0" streamx "^2.15.0" -tar@>=6.1.2, tar@^6.0.2, tar@^6.1.11, tar@^6.1.2, tar@^6.2.1: - version "7.4.3" - resolved "https://registry.npmjs.org/tar/-/tar-7.4.3.tgz#88bbe9286a3fcd900e94592cda7a22b192e80571" - integrity sha512-5S7Va8hKfV7W5U6g3aYxXmlPoZVAwUMy9AOKyF2fVuZa2UD3qZjg578OrLRt8PcNN1PleVaL/5/yYATNL0ICUw== +tar@^6.0.2, tar@^6.1.11, tar@^6.1.2, tar@^6.2.1: + version "6.2.1" + resolved "https://registry.npmjs.org/tar/-/tar-6.2.1.tgz#717549c541bc3c2af15751bea94b1dd068d4b03a" + integrity sha512-DZ4yORTwrbTj/7MZYq2w+/ZFdI6OZ/f9SFHR+71gIVUZhOQPHzVCLpvRnPgyaMpfWxxk/4ONva3GQSyNIKRv6A== dependencies: - "@isaacs/fs-minipass" "^4.0.0" - chownr "^3.0.0" - minipass "^7.1.2" - minizlib "^3.0.1" - mkdirp "^3.0.1" - yallist "^5.0.0" + chownr "^2.0.0" + fs-minipass "^2.0.0" + minipass "^5.0.0" + minizlib "^2.1.1" + mkdirp "^1.0.3" + yallist "^4.0.0" term-size@^2.1.0: version "2.2.1" @@ -9989,12 +9975,12 @@ yallist@^4.0.0: resolved "https://registry.npmjs.org/yallist/-/yallist-4.0.0.tgz#9bb92790d9c0effec63be73519e11a35019a3a72" integrity sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A== -yallist@^5.0.0: - version "5.0.0" - resolved "https://registry.npmjs.org/yallist/-/yallist-5.0.0.tgz#00e2de443639ed0d78fd87de0d27469fbcffb533" - integrity sha512-YgvUTfwqyc7UXVMrB+SImsVYSmTS8X/tSrtdNZMImM+n7+QTriRXyXim0mBrTXNeqzVF0KWGgHPeiyViFFrNDw== +yaml@^1.10.0: + version "1.10.2" + resolved "https://registry.npmjs.org/yaml/-/yaml-1.10.2.tgz#2301c5ffbf12b467de8da2333a459e29e7920e4b" + integrity sha512-r3vXyErRCYJ7wg28yvBY5VSoAF8ZvlcW9/BwUzEtUsjvX/DKs24dIkuwjtuprwJJHsbyUbLApepYTR1BN4uHrg== -yaml@>=2.2.2, yaml@^1.10.0, yaml@^2.2.2, yaml@^2.5.1: +yaml@^2.2.2, yaml@^2.5.1: version "2.5.1" resolved "https://registry.npmjs.org/yaml/-/yaml-2.5.1.tgz#c9772aacf62cb7494a95b0c4f1fb065b563db130" integrity sha512-bLQOjaX/ADgQ20isPJRvF0iRUHIxVhYvr53Of7wGcWlO2jvtUlH5m87DsmulFVxRpNLOnI4tB6p/oh8D7kpn9Q==