diff --git a/changelog.md b/changelog.md index 8f51d82..17cc24b 100644 --- a/changelog.md +++ b/changelog.md @@ -1,5 +1,14 @@ # Changelog +## 2026-02-10 - 4.1.1 - fix(smartproxy) +upgrade @push.rocks/smartproxy to ^23.1.0 and adapt code/tests for its async getStatistics() API + +- Bumped dependency @push.rocks/smartproxy 22.4.2 → 23.1.0 in package.json +- Changed ts/monitoring/classes.metricsmanager.ts to await smartProxy.getStatistics() (was synchronous) +- Updated multiple tests to set cacheConfig: { enabled: false } and added socketTimeouts where appropriate +- Improved SMTP test servers: handle multi-line input, drop data for packet-loss simulation, and ignore socket errors to make tests more robust +- Added migration notes to readme.hints.md documenting SmartProxy v23.1.0 changes (async getStatistics, Rust proxy behavior) + ## 2026-02-10 - 4.1.0 - feat(cache) add persistent smartdata-backed cache with LocalTsmDb, cache cleaner, and DcRouter integration diff --git a/package.json b/package.json index 50232ca..2efa159 100644 --- a/package.json +++ b/package.json @@ -50,7 +50,7 @@ "@push.rocks/smartnetwork": "^4.4.0", "@push.rocks/smartpath": "^6.0.0", "@push.rocks/smartpromise": "^4.2.3", - "@push.rocks/smartproxy": "^22.4.2", + "@push.rocks/smartproxy": "^23.1.0", "@push.rocks/smartradius": "^1.1.0", "@push.rocks/smartrequest": "^5.0.1", "@push.rocks/smartrule": "^2.0.1", diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 70bb6ba..306fc82 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -75,8 +75,8 @@ importers: specifier: ^4.2.3 version: 4.2.3 '@push.rocks/smartproxy': - specifier: ^22.4.2 - version: 22.4.2(socks@2.8.7) + specifier: ^23.1.0 + version: 23.1.0(socks@2.8.7) '@push.rocks/smartradius': specifier: ^1.1.0 version: 1.1.0 @@ -677,6 +677,10 @@ packages: resolution: {integrity: sha512-ZT55BDLV0yv0RBm2czMiZ+SqCGO7AvmOM3G/w2xhVPH+te0aKgFjmBvGlL1dH+ql2tgGO3MVrbb3jCKyvpgnxA==} engines: {node: 20 || >=22} + '@isaacs/brace-expansion@5.0.1': + resolution: {integrity: sha512-WMz71T1JS624nWj2n2fnYAuPovhv7EUhk69R6i9dsVyzxt5eM3bjwvgk9L+APE1TRscGysAVMANkB0jh0LQZrQ==} + engines: {node: 20 || >=22} + '@isaacs/cliui@8.0.2': resolution: {integrity: sha512-O8jcjabXaleOG9DQ0+ARXWZBTfnP4WNAqzuiJK7ll44AmxGKv/J2M4TPjxjY3znBCfvBXFzucm1twdyFybFqEA==} engines: {node: '>=12'} @@ -1075,8 +1079,8 @@ packages: '@push.rocks/smartpromise@4.2.3': resolution: {integrity: sha512-Ycg/TJR+tMt+S3wSFurOpEoW6nXv12QBtKXgBcjMZ4RsdO28geN46U09osPn9N9WuwQy1PkmTV5J/V4F9U8qEw==} - '@push.rocks/smartproxy@22.4.2': - resolution: {integrity: sha512-JdDa1VxGOnWfF5HuJRvkX3/zHuIKz+IV9n/XOsNZQA9zMZdLVlWPqjGio9GLWsPOWA2l1YZKymjMH4ybPbGQtA==} + '@push.rocks/smartproxy@23.1.0': + resolution: {integrity: sha512-2EhMFeQytDwnqooK9BNkLw9oz8M1LUFuMEg6271xRnwf8gUkDq5WT0brrmLdOmpqkU/3h/wDeZUrn65zq3VAcA==} '@push.rocks/smartpuppeteer@2.0.5': resolution: {integrity: sha512-yK/qSeWVHIGWRp3c8S5tfdGP6WCKllZC4DR8d8CQlEjszOSBmHtlTdyyqOMBZ/BA4kd+eU5f3A1r4K2tGYty1g==} @@ -1099,6 +1103,9 @@ packages: '@push.rocks/smartrule@2.0.1': resolution: {integrity: sha512-8oYEnS9z+NgCAcUtXPMguYyZpHqA/ROp0bxVQwUaHDwa3YzzA8jHIXvA94hk3sxvkk0xmIpp4UhBEelzIwwJow==} + '@push.rocks/smartrust@1.1.1': + resolution: {integrity: sha512-NtfTOrVpw0K+z/jW24OmunvZBqkJHfe1tJhTMPFYUb4a5Yt5mtTc3oUvlX+bHarn94Jq0oh0HCLh8xcPQ2Sd7w==} + '@push.rocks/smartrx@3.0.10': resolution: {integrity: sha512-USjIYcsSfzn14cwOsxgq/bBmWDTTzy3ouWAnW5NdMyRRzEbmeNrvmy6TRqNeDlJ2PsYNTt1rr/zGUqvIy72ITg==} @@ -1160,6 +1167,9 @@ packages: '@push.rocks/taskbuffer@3.5.0': resolution: {integrity: sha512-Y9WwIEIyp6oVFdj06j84tfrZIvjhbMb3DF52rYxlTeYLk3W7RPhSg1bGPCbtkXWeKdBrSe37V90BkOG7Qq8Pqg==} + '@push.rocks/taskbuffer@4.2.0': + resolution: {integrity: sha512-ttoBe5y/WXkAo5/wSMcC/Y4Zbyw4XG8kwAsEaqnAPCxa3M9MI1oV/yM1e9gU1IH97HVPidzbTxRU5/PcHDdUsg==} + '@push.rocks/webrequest@3.0.37': resolution: {integrity: sha512-fLN7kP6GeHFxE4UH4r9C9pjcQb0QkJxHeAMwXvbOqB9hh0MFNKhtGU7GoaTn8SVRGRMPc9UqZVNwo6u5l8Wn0A==} @@ -3336,6 +3346,10 @@ packages: resolution: {integrity: sha512-enIvLvRAFZYXJzkCYG5RKmPfrFArdLv+R+lbQ53BmIMLIry74bjKzX6iHAm8WYamJkhSSEabrWN5D97XnKObjQ==} engines: {node: 20 || >=22} + minimatch@10.1.2: + resolution: {integrity: sha512-fu656aJ0n2kcXwsnwnv9g24tkU5uSmOlTjd6WyyaKm2Z+h1qmY6bAjrcaIxF/BslFqbZ8UBtbJi7KgQOZD2PTw==} + engines: {node: 20 || >=22} + minimatch@3.1.2: resolution: {integrity: sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==} @@ -5551,6 +5565,10 @@ snapshots: dependencies: '@isaacs/balanced-match': 4.0.1 + '@isaacs/brace-expansion@5.0.1': + dependencies: + '@isaacs/balanced-match': 4.0.1 + '@isaacs/cliui@8.0.2': dependencies: string-width: 5.1.2 @@ -6513,7 +6531,7 @@ snapshots: '@push.rocks/smartpromise@4.2.3': {} - '@push.rocks/smartproxy@22.4.2(socks@2.8.7)': + '@push.rocks/smartproxy@23.1.0(socks@2.8.7)': dependencies: '@push.rocks/lik': 6.2.2 '@push.rocks/smartacme': 8.0.0(socks@2.8.7) @@ -6524,13 +6542,14 @@ snapshots: '@push.rocks/smartnetwork': 4.4.0 '@push.rocks/smartpromise': 4.2.3 '@push.rocks/smartrequest': 5.0.1 + '@push.rocks/smartrust': 1.1.1 '@push.rocks/smartrx': 3.0.10 '@push.rocks/smartstring': 4.1.0 - '@push.rocks/taskbuffer': 3.5.0 + '@push.rocks/taskbuffer': 4.2.0 '@tsclass/tsclass': 9.3.0 '@types/minimatch': 6.0.0 '@types/ws': 8.18.1 - minimatch: 10.1.1 + minimatch: 10.1.2 pretty-ms: 9.3.0 ws: 8.19.0 transitivePeerDependencies: @@ -6604,6 +6623,10 @@ snapshots: '@push.rocks/smartrule@2.0.1': {} + '@push.rocks/smartrust@1.1.1': + dependencies: + '@push.rocks/smartpath': 6.0.0 + '@push.rocks/smartrx@3.0.10': dependencies: '@push.rocks/smartpromise': 4.2.3 @@ -6775,6 +6798,22 @@ snapshots: - supports-color - vue + '@push.rocks/taskbuffer@4.2.0': + dependencies: + '@design.estate/dees-element': 2.1.6 + '@push.rocks/lik': 6.2.2 + '@push.rocks/smartdelay': 3.0.5 + '@push.rocks/smartlog': 3.1.10 + '@push.rocks/smartpromise': 4.2.3 + '@push.rocks/smartrx': 3.0.10 + '@push.rocks/smarttime': 4.1.1 + '@push.rocks/smartunique': 3.0.9 + transitivePeerDependencies: + - '@nuxt/kit' + - react + - supports-color + - vue + '@push.rocks/webrequest@3.0.37': dependencies: '@push.rocks/smartdelay': 3.0.5 @@ -7631,7 +7670,7 @@ snapshots: '@types/minimatch@6.0.0': dependencies: - minimatch: 10.1.1 + minimatch: 10.1.2 '@types/ms@2.1.0': {} @@ -9397,6 +9436,10 @@ snapshots: dependencies: '@isaacs/brace-expansion': 5.0.0 + minimatch@10.1.2: + dependencies: + '@isaacs/brace-expansion': 5.0.1 + minimatch@3.1.2: dependencies: brace-expansion: 1.1.12 diff --git a/readme.hints.md b/readme.hints.md index 252a338..4b36fb2 100644 --- a/readme.hints.md +++ b/readme.hints.md @@ -1,5 +1,28 @@ # Implementation Hints and Learnings +## Dependency Upgrade (2026-02-10) + +### SmartProxy v23.1.0 Upgrade +- `@push.rocks/smartproxy`: 22.4.2 → 23.1.0 + +**Key Changes:** +- Rust-based proxy components for improved performance +- Rust binary runs as separate process via IPC +- `getStatistics()` now returns `Promise` (was synchronous) +- nftables-proxy removed (not used by dcrouter) + +**Code Changes Required:** +```typescript +// Old (synchronous) +const proxyStats = this.dcRouter.smartProxy.getStatistics(); + +// New (async) +const proxyStats = await this.dcRouter.smartProxy.getStatistics(); +``` + +**Files Modified:** +- `ts/monitoring/classes.metricsmanager.ts` - Added `await` to `getStatistics()` call + ## Dependency Upgrade (2026-02-01) ### Major Upgrades Completed diff --git a/test/suite/smtpclient_error-handling/test.cerr-03.network-failures.ts b/test/suite/smtpclient_error-handling/test.cerr-03.network-failures.ts index b5b9f11..295e305 100644 --- a/test/suite/smtpclient_error-handling/test.cerr-03.network-failures.ts +++ b/test/suite/smtpclient_error-handling/test.cerr-03.network-failures.ts @@ -197,65 +197,55 @@ tap.test('CERR-03: Network Failures - should handle EHOSTUNREACH', async () => { }); tap.test('CERR-03: Network Failures - should handle packet loss simulation', async () => { - // Create a server that randomly drops data - let packetCount = 0; + // Create a server that sends a greeting but never responds to commands, + // simulating complete packet loss after the initial connection. const lossyServer = net.createServer((socket) => { + socket.on('error', () => {}); socket.write('220 Lossy server ready\r\n'); - - socket.on('data', (data) => { - packetCount++; - - // Simulate 30% packet loss - if (Math.random() > 0.3) { - const command = data.toString().trim(); - if (command.startsWith('EHLO')) { - socket.write('250 OK\r\n'); - } else if (command === 'QUIT') { - socket.write('221 Bye\r\n'); - socket.end(); - } - } - // Otherwise, don't respond (simulate packet loss) + + // Never respond to any commands - simulates total packet loss + socket.on('data', () => { + // Intentionally drop all data to simulate packet loss }); }); - + await new Promise((resolve) => { lossyServer.listen(2558, () => resolve()); }); - + const client = createSmtpClient({ host: 'localhost', port: 2558, secure: false, - connectionTimeout: 1000, - socketTimeout: 1000 // Short timeout to detect loss + connectionTimeout: 2000, + socketTimeout: 2000 // Short timeout to detect loss }); - + let verifyResult = false; let errorOccurred = false; - + try { verifyResult = await client.verify(); if (verifyResult) { - console.log('✅ Connected despite simulated packet loss'); + console.log('Connection succeeded unexpectedly'); } else { console.log('✅ Connection failed due to packet loss'); } } catch (error) { errorOccurred = true; - console.log(`✅ Packet loss detected after ${packetCount} packets: ${error.message}`); + console.log(`✅ Packet loss detected: ${error.message}`); } - - // Either verification failed or an error occurred - both are expected with packet loss + + // verify() must have returned false or thrown - both indicate packet loss was detected expect(!verifyResult || errorOccurred).toBeTrue(); - + // Clean up client first try { await client.close(); } catch (closeError) { // Ignore close errors in this test } - + // Then close server await new Promise((resolve) => { lossyServer.close(() => resolve()); diff --git a/test/suite/smtpclient_error-handling/test.cerr-04.greylisting-handling.ts b/test/suite/smtpclient_error-handling/test.cerr-04.greylisting-handling.ts index 0432842..ba9dbaf 100644 --- a/test/suite/smtpclient_error-handling/test.cerr-04.greylisting-handling.ts +++ b/test/suite/smtpclient_error-handling/test.cerr-04.greylisting-handling.ts @@ -19,23 +19,31 @@ tap.test('setup - start SMTP server for greylisting tests', async () => { tap.test('CERR-04: Basic greylisting response handling', async () => { // Create server that simulates greylisting const greylistServer = net.createServer((socket) => { + socket.on('error', () => {}); socket.write('220 Greylist Test Server\r\n'); socket.on('data', (data) => { - const command = data.toString().trim(); - - if (command.startsWith('EHLO') || command.startsWith('HELO')) { - socket.write('250 OK\r\n'); - } else if (command.startsWith('MAIL FROM')) { - socket.write('250 OK\r\n'); - } else if (command.startsWith('RCPT TO')) { - // Simulate greylisting response - socket.write('451 4.7.1 Greylisting in effect, please retry later\r\n'); - } else if (command === 'QUIT') { - socket.write('221 Bye\r\n'); - socket.end(); - } else { - socket.write('250 OK\r\n'); + const lines = data.toString().split('\r\n').filter((l: string) => l.trim()); + for (const line of lines) { + const command = line.trim(); + + if (command.startsWith('EHLO') || command.startsWith('HELO')) { + socket.write('250 OK\r\n'); + } else if (command.startsWith('MAIL FROM')) { + socket.write('250 OK\r\n'); + } else if (command.startsWith('RCPT TO')) { + // Simulate greylisting response + socket.write('451 4.7.1 Greylisting in effect, please retry later\r\n'); + } else if (command.startsWith('RSET')) { + socket.write('250 OK\r\n'); + } else if (command.startsWith('DATA')) { + socket.write('503 Bad sequence of commands\r\n'); + } else if (command === 'QUIT') { + socket.write('221 Bye\r\n'); + socket.end(); + } else if (command.length > 0) { + socket.write('250 OK\r\n'); + } } }); }); @@ -48,7 +56,8 @@ tap.test('CERR-04: Basic greylisting response handling', async () => { host: '127.0.0.1', port: 2560, secure: false, - connectionTimeout: 5000 + connectionTimeout: 5000, + socketTimeout: 5000 }); const email = new Email({ @@ -59,7 +68,7 @@ tap.test('CERR-04: Basic greylisting response handling', async () => { }); const result = await smtpClient.sendMail(email); - + // Should get a failed result due to greylisting expect(result.success).toBeFalse(); console.log('Actual error:', result.error?.message); @@ -107,20 +116,30 @@ tap.test('CERR-04: Different greylisting response codes', async () => { tap.test('CERR-04: Greylisting with temporary failure', async () => { // Create server that sends 450 response (temporary failure) const tempFailServer = net.createServer((socket) => { + socket.on('error', () => {}); socket.write('220 Temp Fail Server\r\n'); socket.on('data', (data) => { - const command = data.toString().trim(); - - if (command.startsWith('EHLO')) { - socket.write('250 OK\r\n'); - } else if (command.startsWith('MAIL FROM')) { - socket.write('250 OK\r\n'); - } else if (command.startsWith('RCPT TO')) { - socket.write('450 4.7.1 Mailbox temporarily unavailable\r\n'); - } else if (command === 'QUIT') { - socket.write('221 Bye\r\n'); - socket.end(); + const lines = data.toString().split('\r\n').filter((l: string) => l.trim()); + for (const line of lines) { + const command = line.trim(); + + if (command.startsWith('EHLO') || command.startsWith('HELO')) { + socket.write('250 OK\r\n'); + } else if (command.startsWith('MAIL FROM')) { + socket.write('250 OK\r\n'); + } else if (command.startsWith('RCPT TO')) { + socket.write('450 4.7.1 Mailbox temporarily unavailable\r\n'); + } else if (command.startsWith('RSET')) { + socket.write('250 OK\r\n'); + } else if (command.startsWith('DATA')) { + socket.write('503 Bad sequence of commands\r\n'); + } else if (command === 'QUIT') { + socket.write('221 Bye\r\n'); + socket.end(); + } else if (command.length > 0) { + socket.write('250 OK\r\n'); + } } }); }); @@ -133,7 +152,8 @@ tap.test('CERR-04: Greylisting with temporary failure', async () => { host: '127.0.0.1', port: 2561, secure: false, - connectionTimeout: 5000 + connectionTimeout: 5000, + socketTimeout: 5000 }); const email = new Email({ @@ -144,7 +164,7 @@ tap.test('CERR-04: Greylisting with temporary failure', async () => { }); const result = await smtpClient.sendMail(email); - + expect(result.success).toBeFalse(); console.log('Actual error:', result.error?.message); expect(result.error?.message).toMatch(/450|temporary|rejected/i); @@ -199,20 +219,30 @@ tap.test('CERR-04: Basic connection verification', async () => { tap.test('CERR-04: Server with RCPT rejection', async () => { // Test server rejecting at RCPT TO stage const rejectServer = net.createServer((socket) => { + socket.on('error', () => {}); socket.write('220 Reject Server\r\n'); socket.on('data', (data) => { - const command = data.toString().trim(); - - if (command.startsWith('EHLO')) { - socket.write('250 OK\r\n'); - } else if (command.startsWith('MAIL FROM')) { - socket.write('250 OK\r\n'); - } else if (command.startsWith('RCPT TO')) { - socket.write('451 4.2.1 Recipient rejected temporarily\r\n'); - } else if (command === 'QUIT') { - socket.write('221 Bye\r\n'); - socket.end(); + const lines = data.toString().split('\r\n').filter((l: string) => l.trim()); + for (const line of lines) { + const command = line.trim(); + + if (command.startsWith('EHLO') || command.startsWith('HELO')) { + socket.write('250 OK\r\n'); + } else if (command.startsWith('MAIL FROM')) { + socket.write('250 OK\r\n'); + } else if (command.startsWith('RCPT TO')) { + socket.write('451 4.2.1 Recipient rejected temporarily\r\n'); + } else if (command.startsWith('RSET')) { + socket.write('250 OK\r\n'); + } else if (command.startsWith('DATA')) { + socket.write('503 Bad sequence of commands\r\n'); + } else if (command === 'QUIT') { + socket.write('221 Bye\r\n'); + socket.end(); + } else if (command.length > 0) { + socket.write('250 OK\r\n'); + } } }); }); @@ -225,7 +255,8 @@ tap.test('CERR-04: Server with RCPT rejection', async () => { host: '127.0.0.1', port: 2562, secure: false, - connectionTimeout: 5000 + connectionTimeout: 5000, + socketTimeout: 5000 }); const email = new Email({ @@ -236,7 +267,7 @@ tap.test('CERR-04: Server with RCPT rejection', async () => { }); const result = await smtpClient.sendMail(email); - + expect(result.success).toBeFalse(); console.log('Actual error:', result.error?.message); expect(result.error?.message).toMatch(/451|reject|recipient/i); diff --git a/test/test.dns-socket-handler.ts b/test/test.dns-socket-handler.ts index d28fd41..bf49e88 100644 --- a/test/test.dns-socket-handler.ts +++ b/test/test.dns-socket-handler.ts @@ -8,7 +8,8 @@ tap.test('should NOT instantiate DNS server when dnsNsDomains is not set', async dcRouter = new DcRouter({ smartProxyConfig: { routes: [] - } + }, + cacheConfig: { enabled: false } }); await dcRouter.start(); diff --git a/test/test.email-socket-handler.ts b/test/test.email-socket-handler.ts index 2946b7a..56c9d3e 100644 --- a/test/test.email-socket-handler.ts +++ b/test/test.email-socket-handler.ts @@ -15,7 +15,8 @@ tap.test('should use traditional port forwarding when useSocketHandler is false' }, smartProxyConfig: { routes: [] - } + }, + cacheConfig: { enabled: false } }); await dcRouter.start(); @@ -51,7 +52,8 @@ tap.test('should use socket-handler mode when useSocketHandler is true', async ( }, smartProxyConfig: { routes: [] - } + }, + cacheConfig: { enabled: false } }); await dcRouter.start(); @@ -85,7 +87,7 @@ tap.test('should generate correct email routes for each port', async () => { useSocketHandler: true }; - dcRouter = new DcRouter({ emailConfig }); + dcRouter = new DcRouter({ emailConfig, cacheConfig: { enabled: false } }); // Access the private method to generate routes const emailRoutes = (dcRouter as any).generateEmailRoutes(emailConfig); @@ -119,7 +121,8 @@ tap.test('email socket handler should handle different ports correctly', async ( domains: ['test.local'], routes: [], useSocketHandler: true - } + }, + cacheConfig: { enabled: false } }); await dcRouter.start(); @@ -145,7 +148,8 @@ tap.test('email server handleSocket method should work', async () => { domains: ['test.local'], routes: [], useSocketHandler: true - } + }, + cacheConfig: { enabled: false } }); await dcRouter.start(); @@ -182,11 +186,12 @@ tap.test('should not create SMTP servers when useSocketHandler is true', async ( domains: ['test.local'], routes: [], useSocketHandler: true - } + }, + cacheConfig: { enabled: false } }); - + await dcRouter.start(); - + // The email server should not have any SMTP server instances const emailServer = (dcRouter as any).emailServer; expect(emailServer).toBeDefined(); @@ -209,7 +214,7 @@ tap.test('TLS handling should differ between ports', async () => { useSocketHandler: false // Use traditional mode to check TLS config }; - dcRouter = new DcRouter({ emailConfig }); + dcRouter = new DcRouter({ emailConfig, cacheConfig: { enabled: false } }); const emailRoutes = (dcRouter as any).generateEmailRoutes(emailConfig); diff --git a/ts/00_commitinfo_data.ts b/ts/00_commitinfo_data.ts index e08957f..d66d9d7 100644 --- a/ts/00_commitinfo_data.ts +++ b/ts/00_commitinfo_data.ts @@ -3,6 +3,6 @@ */ export const commitinfo = { name: '@serve.zone/dcrouter', - version: '4.1.0', + version: '4.1.1', description: 'A multifaceted routing service handling mail and SMS delivery functions.' } diff --git a/ts/monitoring/classes.metricsmanager.ts b/ts/monitoring/classes.metricsmanager.ts index 3cfebaf..427fe1f 100644 --- a/ts/monitoring/classes.metricsmanager.ts +++ b/ts/monitoring/classes.metricsmanager.ts @@ -123,8 +123,8 @@ export class MetricsManager { return this.metricsCache.get('serverStats', async () => { const smartMetricsData = await this.smartMetrics.getMetrics(); const proxyMetrics = this.dcRouter.smartProxy ? this.dcRouter.smartProxy.getMetrics() : null; - const proxyStats = this.dcRouter.smartProxy ? this.dcRouter.smartProxy.getStatistics() : null; - + const proxyStats = this.dcRouter.smartProxy ? await this.dcRouter.smartProxy.getStatistics() : null; + return { uptime: process.uptime(), startTime: Date.now() - (process.uptime() * 1000), diff --git a/ts_web/00_commitinfo_data.ts b/ts_web/00_commitinfo_data.ts index e08957f..d66d9d7 100644 --- a/ts_web/00_commitinfo_data.ts +++ b/ts_web/00_commitinfo_data.ts @@ -3,6 +3,6 @@ */ export const commitinfo = { name: '@serve.zone/dcrouter', - version: '4.1.0', + version: '4.1.1', description: 'A multifaceted routing service handling mail and SMS delivery functions.' }