From 20e2ef27b2e72858ca673131de2ca0197d862040 Mon Sep 17 00:00:00 2001 From: Mike Clark Date: Thu, 10 Oct 2024 20:04:27 +0100 Subject: [PATCH 01/55] start to this epic idea --- .../src/browser-preload.browser.js | 1 + packages/desktop-electron/index.ts | 28 +++++++++++++++++++ packages/desktop-electron/package.json | 1 + packages/desktop-electron/preload.ts | 6 ++++ packages/loot-core/typings/window.d.ts | 1 + yarn.lock | 1 + 6 files changed, 38 insertions(+) diff --git a/packages/desktop-client/src/browser-preload.browser.js b/packages/desktop-client/src/browser-preload.browser.js index a65e4b09a24..d433fcc50dd 100644 --- a/packages/desktop-client/src/browser-preload.browser.js +++ b/packages/desktop-client/src/browser-preload.browser.js @@ -139,6 +139,7 @@ global.Actual = { openURLInBrowser: url => { window.open(url, '_blank'); }, + downloadActualServer: () => {}, onEventFromMain: () => {}, applyAppUpdate: () => {}, updateAppMenu: () => {}, diff --git a/packages/desktop-electron/index.ts b/packages/desktop-electron/index.ts index 92649a96c1f..b64aaaa5c24 100644 --- a/packages/desktop-electron/index.ts +++ b/packages/desktop-electron/index.ts @@ -26,6 +26,7 @@ import { } from './window-state'; import './security'; +import AdmZip from 'adm-zip'; const isDev = !app.isPackaged; // dev mode if not packaged @@ -373,6 +374,33 @@ ipcMain.handle('open-external-url', (event, url) => { shell.openExternal(url); }); +ipcMain.handle( + 'download-actual-server', + async (_event, payload: { releaseVersion: string }) => { + console.info({ payload }); + const downloadUrl = `https://github.com/MikesGlitch/actual-server/releases/download/${payload.releaseVersion}/${payload.releaseVersion}-server-sync-dist.zip`; + + try { + const res = await fetch(downloadUrl); + const arrBuffer = await res.arrayBuffer(); + const zipped = new AdmZip(Buffer.from(arrBuffer)); + console.info( + 'actual-server will be installed here:', + process.env.ACTUAL_DATA_DIR, + ); + zipped.extractAllTo( + process.env.ACTUAL_DATA_DIR + '/actual-server-releases', + true, + false, + ); + return { error: undefined }; + } catch (error) { + console.error('Error retrieving actual-server:', error); + return { error }; + } + }, +); + ipcMain.on('message', (_event, msg) => { if (!serverProcess) { return; diff --git a/packages/desktop-electron/package.json b/packages/desktop-electron/package.json index 2a353b11360..001c26f1b79 100644 --- a/packages/desktop-electron/package.json +++ b/packages/desktop-electron/package.json @@ -85,6 +85,7 @@ "npmRebuild": false }, "dependencies": { + "adm-zip": "^0.5.10", "better-sqlite3": "^9.6.0", "fs-extra": "^11.2.0", "node-fetch": "^2.7.0", diff --git a/packages/desktop-electron/preload.ts b/packages/desktop-electron/preload.ts index c6f8327ebf5..770106892cc 100644 --- a/packages/desktop-electron/preload.ts +++ b/packages/desktop-electron/preload.ts @@ -58,6 +58,12 @@ contextBridge.exposeInMainWorld('Actual', { ipcRenderer.invoke('open-external-url', url); }, + downloadActualServer: async (releaseVersion: string) => { + await ipcRenderer.invoke('download-actual-server', { + releaseVersion, + }); + }, + onEventFromMain: (type: string, handler: (...args: unknown[]) => void) => { ipcRenderer.on(type, handler); }, diff --git a/packages/loot-core/typings/window.d.ts b/packages/loot-core/typings/window.d.ts index 1a56a14fb20..b8f2f0a06f5 100644 --- a/packages/loot-core/typings/window.d.ts +++ b/packages/loot-core/typings/window.d.ts @@ -6,6 +6,7 @@ declare global { IS_FAKE_WEB: boolean; ACTUAL_VERSION: string; openURLInBrowser: (url: string) => void; + downloadActualServer: (releaseVersion: string) => Promise; saveFile: ( contents: string | Buffer, filename: string, diff --git a/yarn.lock b/yarn.lock index 29ce52e201a..c79af54cdfe 100644 --- a/yarn.lock +++ b/yarn.lock @@ -8432,6 +8432,7 @@ __metadata: "@electron/rebuild": "npm:3.6.0" "@types/copyfiles": "npm:^2" "@types/fs-extra": "npm:^11" + adm-zip: "npm:^0.5.10" better-sqlite3: "npm:^9.6.0" copyfiles: "npm:^2.4.1" cross-env: "npm:^7.0.3" From 361200e26c3d76fa30ed436c6ba6da17da9b60da Mon Sep 17 00:00:00 2001 From: Mike Clark Date: Thu, 10 Oct 2024 20:46:57 +0100 Subject: [PATCH 02/55] bits --- packages/desktop-electron/index.ts | 3 +-- packages/desktop-electron/preload.ts | 4 ++-- 2 files changed, 3 insertions(+), 4 deletions(-) diff --git a/packages/desktop-electron/index.ts b/packages/desktop-electron/index.ts index b64aaaa5c24..485a1bd4285 100644 --- a/packages/desktop-electron/index.ts +++ b/packages/desktop-electron/index.ts @@ -393,10 +393,9 @@ ipcMain.handle( true, false, ); - return { error: undefined }; } catch (error) { console.error('Error retrieving actual-server:', error); - return { error }; + throw error; } }, ); diff --git a/packages/desktop-electron/preload.ts b/packages/desktop-electron/preload.ts index 770106892cc..60da50b7b10 100644 --- a/packages/desktop-electron/preload.ts +++ b/packages/desktop-electron/preload.ts @@ -58,8 +58,8 @@ contextBridge.exposeInMainWorld('Actual', { ipcRenderer.invoke('open-external-url', url); }, - downloadActualServer: async (releaseVersion: string) => { - await ipcRenderer.invoke('download-actual-server', { + downloadActualServer: (releaseVersion: string) => { + return ipcRenderer.invoke('download-actual-server', { releaseVersion, }); }, From c718b2c442b34f5fa7ac4e2c0fcac8184c75b787 Mon Sep 17 00:00:00 2001 From: Mike Clark Date: Thu, 10 Oct 2024 20:48:33 +0100 Subject: [PATCH 03/55] add a note --- packages/desktop-electron/index.ts | 1 + 1 file changed, 1 insertion(+) diff --git a/packages/desktop-electron/index.ts b/packages/desktop-electron/index.ts index 485a1bd4285..6944ec5270a 100644 --- a/packages/desktop-electron/index.ts +++ b/packages/desktop-electron/index.ts @@ -374,6 +374,7 @@ ipcMain.handle('open-external-url', (event, url) => { shell.openExternal(url); }); +// NOTE: We could just bundle it in the package, but it would be a large download - consider it though... ipcMain.handle( 'download-actual-server', async (_event, payload: { releaseVersion: string }) => { From e32fb8b9589aa85ee83731847cc192b0fc016bdb Mon Sep 17 00:00:00 2001 From: Mike Clark Date: Fri, 11 Oct 2024 21:11:32 +0100 Subject: [PATCH 04/55] server starting button erroring on imports due to env differences --- .../src/browser-preload.browser.js | 1 + .../src/components/manager/BudgetList.tsx | 14 ++++++++++ packages/desktop-electron/actualServer.ts | 19 +++++++++++++ packages/desktop-electron/index.ts | 28 +++++++++++++++++++ packages/desktop-electron/preload.ts | 6 ++++ packages/loot-core/typings/window.d.ts | 1 + 6 files changed, 69 insertions(+) create mode 100644 packages/desktop-electron/actualServer.ts diff --git a/packages/desktop-client/src/browser-preload.browser.js b/packages/desktop-client/src/browser-preload.browser.js index d433fcc50dd..43a3037cc92 100644 --- a/packages/desktop-client/src/browser-preload.browser.js +++ b/packages/desktop-client/src/browser-preload.browser.js @@ -140,6 +140,7 @@ global.Actual = { window.open(url, '_blank'); }, downloadActualServer: () => {}, + startActualServer: () => {}, onEventFromMain: () => {}, applyAppUpdate: () => {}, updateAppMenu: () => {}, diff --git a/packages/desktop-client/src/components/manager/BudgetList.tsx b/packages/desktop-client/src/components/manager/BudgetList.tsx index 1cd0f5e3c96..2afadfe3c7f 100644 --- a/packages/desktop-client/src/components/manager/BudgetList.tsx +++ b/packages/desktop-client/src/components/manager/BudgetList.tsx @@ -441,6 +441,10 @@ export function BudgetList({ showHeader = true, quickSwitchMode = false }) { } }; + const startActualServer = async () => { + await globalThis.Actual.startActualServer('v24.10.1'); + }; + return ( Create test file )} + )} diff --git a/packages/desktop-electron/actualServer.ts b/packages/desktop-electron/actualServer.ts new file mode 100644 index 00000000000..feda2931d2a --- /dev/null +++ b/packages/desktop-electron/actualServer.ts @@ -0,0 +1,19 @@ +// Sync server (actual-server) + +const _node = process.argv[0]; +const _script = process.argv[1]; +const _subProcess = process.argv[2]; +const actualServerDir = process.argv[3]; + +const lazyLoadActualServer = async () => { + try { + console.info('Starting actual-server...'); + await import(`${actualServerDir}/app.js`); + } catch (error) { + console.error('Failed to start actual-server:', error); + throw new Error(`Failed to start actual-server: ${error}`); + } +}; + +// Start actual-server +lazyLoadActualServer(); diff --git a/packages/desktop-electron/index.ts b/packages/desktop-electron/index.ts index 6944ec5270a..c0949a6b632 100644 --- a/packages/desktop-electron/index.ts +++ b/packages/desktop-electron/index.ts @@ -52,6 +52,7 @@ if (!isDev || !process.env.ACTUAL_DATA_DIR) { // be closed automatically when the JavaScript object is garbage collected. let clientWin: BrowserWindow | null; let serverProcess: UtilityProcess | null; +let actualServerProcess: UtilityProcess | null; if (isDev) { process.traceProcessWarnings = true; @@ -401,6 +402,33 @@ ipcMain.handle( }, ); +ipcMain.handle( + 'start-actual-server', + async (_event, payload: { releaseVersion: string }) => { + const serverReleaseDir = path.resolve( + `${process.env.ACTUAL_DATA_DIR}/actual-server/${payload.releaseVersion}`, + ); + + const actualServerProcess = utilityProcess.fork( + __dirname + '/actualServer.js', + ['--subprocess', serverReleaseDir], + isDev ? { execArgv: ['--inspect'], stdio: 'pipe' } : { stdio: 'pipe' }, + ); + + actualServerProcess.stdout?.on('data', (chunk: Buffer) => { + // Send the Server console.log messages to the main browser window + clientWin?.webContents.executeJavaScript(` + console.info('Server Log:', ${JSON.stringify(chunk.toString('utf8'))})`); + }); + + actualServerProcess.stderr?.on('data', (chunk: Buffer) => { + // Send the Server console.error messages out to the main browser window + clientWin?.webContents.executeJavaScript(` + console.error('Server Log:', ${JSON.stringify(chunk.toString('utf8'))})`); + }); + }, +); + ipcMain.on('message', (_event, msg) => { if (!serverProcess) { return; diff --git a/packages/desktop-electron/preload.ts b/packages/desktop-electron/preload.ts index 60da50b7b10..2c65ddff64e 100644 --- a/packages/desktop-electron/preload.ts +++ b/packages/desktop-electron/preload.ts @@ -64,6 +64,12 @@ contextBridge.exposeInMainWorld('Actual', { }); }, + startActualServer: (releaseVersion: string) => { + return ipcRenderer.invoke('start-actual-server', { + releaseVersion, + }); + }, + onEventFromMain: (type: string, handler: (...args: unknown[]) => void) => { ipcRenderer.on(type, handler); }, diff --git a/packages/loot-core/typings/window.d.ts b/packages/loot-core/typings/window.d.ts index b8f2f0a06f5..805db112ba6 100644 --- a/packages/loot-core/typings/window.d.ts +++ b/packages/loot-core/typings/window.d.ts @@ -7,6 +7,7 @@ declare global { ACTUAL_VERSION: string; openURLInBrowser: (url: string) => void; downloadActualServer: (releaseVersion: string) => Promise; + startActualServer: (releaseVersion: string) => Promise; saveFile: ( contents: string | Buffer, filename: string, From 1802818f2f340e2fd0cf2136e72c8371ae17d9fc Mon Sep 17 00:00:00 2001 From: Mike Clark Date: Fri, 11 Oct 2024 21:31:39 +0100 Subject: [PATCH 05/55] working now --- packages/desktop-electron/actualServer.ts | 19 ------------------- packages/desktop-electron/index.ts | 2 +- 2 files changed, 1 insertion(+), 20 deletions(-) delete mode 100644 packages/desktop-electron/actualServer.ts diff --git a/packages/desktop-electron/actualServer.ts b/packages/desktop-electron/actualServer.ts deleted file mode 100644 index feda2931d2a..00000000000 --- a/packages/desktop-electron/actualServer.ts +++ /dev/null @@ -1,19 +0,0 @@ -// Sync server (actual-server) - -const _node = process.argv[0]; -const _script = process.argv[1]; -const _subProcess = process.argv[2]; -const actualServerDir = process.argv[3]; - -const lazyLoadActualServer = async () => { - try { - console.info('Starting actual-server...'); - await import(`${actualServerDir}/app.js`); - } catch (error) { - console.error('Failed to start actual-server:', error); - throw new Error(`Failed to start actual-server: ${error}`); - } -}; - -// Start actual-server -lazyLoadActualServer(); diff --git a/packages/desktop-electron/index.ts b/packages/desktop-electron/index.ts index c0949a6b632..fa8888c26da 100644 --- a/packages/desktop-electron/index.ts +++ b/packages/desktop-electron/index.ts @@ -410,7 +410,7 @@ ipcMain.handle( ); const actualServerProcess = utilityProcess.fork( - __dirname + '/actualServer.js', + serverReleaseDir + '/app.js', ['--subprocess', serverReleaseDir], isDev ? { execArgv: ['--inspect'], stdio: 'pipe' } : { stdio: 'pipe' }, ); From 194f50d889133bb2ceaf19e77f4d0985f975d15c Mon Sep 17 00:00:00 2001 From: Mike Clark Date: Sat, 12 Oct 2024 11:25:38 +0100 Subject: [PATCH 06/55] using node_modules and allowing electron-builder to bundle it --- packages/desktop-electron/index.ts | 29 +- packages/desktop-electron/package.json | 13 +- yarn.lock | 1000 +++++++++++++++++++++++- 3 files changed, 996 insertions(+), 46 deletions(-) diff --git a/packages/desktop-electron/index.ts b/packages/desktop-electron/index.ts index fa8888c26da..6b0f81403b1 100644 --- a/packages/desktop-electron/index.ts +++ b/packages/desktop-electron/index.ts @@ -409,22 +409,39 @@ ipcMain.handle( `${process.env.ACTUAL_DATA_DIR}/actual-server/${payload.releaseVersion}`, ); - const actualServerProcess = utilityProcess.fork( - serverReleaseDir + '/app.js', - ['--subprocess', serverReleaseDir], - isDev ? { execArgv: ['--inspect'], stdio: 'pipe' } : { stdio: 'pipe' }, + // actualServerProcess = utilityProcess.fork( + // serverReleaseDir + '/app.js', // if bundling it ourselves and manually including it + // ['--subprocess'], + // isDev ? { execArgv: ['--inspect'], stdio: 'pipe' } : { stdio: 'pipe' }, + // ); + + const serverPath = path.resolve( + __dirname, + '../../../node_modules/actual-sync/app.js', // if letting electron-builder bundle it (needs to be in our workspace) ); + // NOTE: config.json parameters will be relative to THIS directory at the moment - may need a fix? + // Or we can override the config.json location when starting the process + try { + actualServerProcess = utilityProcess.fork( + serverPath, // This requires actual-server depencies (crdt) to be built before running electron - they need to be manually specified because actual-server doesn't get bundled + [], + isDev ? { execArgv: ['--inspect'], stdio: 'pipe' } : { stdio: 'pipe' }, + ); + } catch (error) { + console.error(error); + } + actualServerProcess.stdout?.on('data', (chunk: Buffer) => { // Send the Server console.log messages to the main browser window clientWin?.webContents.executeJavaScript(` - console.info('Server Log:', ${JSON.stringify(chunk.toString('utf8'))})`); + console.info('Server Log:', ${JSON.stringify(chunk.toString('utf8'))})`); }); actualServerProcess.stderr?.on('data', (chunk: Buffer) => { // Send the Server console.error messages out to the main browser window clientWin?.webContents.executeJavaScript(` - console.error('Server Log:', ${JSON.stringify(chunk.toString('utf8'))})`); + console.error('Server Log:', ${JSON.stringify(chunk.toString('utf8'))})`); }); }, ); diff --git a/packages/desktop-electron/package.json b/packages/desktop-electron/package.json index 001c26f1b79..6eaeb555318 100644 --- a/packages/desktop-electron/package.json +++ b/packages/desktop-electron/package.json @@ -56,20 +56,10 @@ }, "win": { "target": [ - { - "target": "appx", - "arch": [ - "ia32", - "x64", - "arm64" - ] - }, { "target": "nsis", "arch": [ - "ia32", - "x64", - "arm64" + "x64" ] } ], @@ -85,6 +75,7 @@ "npmRebuild": false }, "dependencies": { + "actual-sync": "file:../../../actual-server", "adm-zip": "^0.5.10", "better-sqlite3": "^9.6.0", "fs-extra": "^11.2.0", diff --git a/yarn.lock b/yarn.lock index c79af54cdfe..439310d4fe2 100644 --- a/yarn.lock +++ b/yarn.lock @@ -38,7 +38,7 @@ __metadata: languageName: unknown linkType: soft -"@actual-app/crdt@npm:*, @actual-app/crdt@workspace:^, @actual-app/crdt@workspace:packages/crdt": +"@actual-app/crdt@npm:*, @actual-app/crdt@npm:2.1.0, @actual-app/crdt@workspace:^, @actual-app/crdt@workspace:packages/crdt": version: 0.0.0-use.local resolution: "@actual-app/crdt@workspace:packages/crdt" dependencies: @@ -55,6 +55,13 @@ __metadata: languageName: unknown linkType: soft +"@actual-app/web@npm:24.10.0": + version: 24.10.0 + resolution: "@actual-app/web@npm:24.10.0" + checksum: 10/e713d98f69d7a8598f863c0fe559c2a87d56f0adc31e3bea62338bf9f9e8cfefc1fc41597e8ef0e3cdc1c51a611e330d023c9cde92e1d7b2b0a3f90e8a690254 + languageName: node + linkType: hard + "@actual-app/web@workspace:packages/desktop-client": version: 0.0.0-use.local resolution: "@actual-app/web@workspace:packages/desktop-client" @@ -1507,6 +1514,13 @@ __metadata: languageName: node linkType: hard +"@colors/colors@npm:1.6.0, @colors/colors@npm:^1.6.0": + version: 1.6.0 + resolution: "@colors/colors@npm:1.6.0" + checksum: 10/66d00284a3a9a21e5e853b256942e17edbb295f4bd7b9aa7ef06bbb603568d5173eb41b0f64c1e51748bc29d382a23a67d99956e57e7431c64e47e74324182d9 + languageName: node + linkType: hard + "@cspotcode/source-map-support@npm:^0.8.0": version: 0.8.1 resolution: "@cspotcode/source-map-support@npm:0.8.1" @@ -1516,6 +1530,17 @@ __metadata: languageName: node linkType: hard +"@dabh/diagnostics@npm:^2.0.2": + version: 2.0.3 + resolution: "@dabh/diagnostics@npm:2.0.3" + dependencies: + colorspace: "npm:1.1.x" + enabled: "npm:2.0.x" + kuler: "npm:^2.0.0" + checksum: 10/14e449a7f42f063f959b472f6ce02d16457a756e852a1910aaa831b63fc21d86f6c32b2a1aa98a4835b856548c926643b51062d241fb6e9b2b7117996053e6b9 + languageName: node + linkType: hard + "@develar/schema-utils@npm:~2.6.5": version: 2.6.5 resolution: "@develar/schema-utils@npm:2.6.5" @@ -2428,6 +2453,25 @@ __metadata: languageName: node linkType: hard +"@mapbox/node-pre-gyp@npm:^1.0.11": + version: 1.0.11 + resolution: "@mapbox/node-pre-gyp@npm:1.0.11" + dependencies: + detect-libc: "npm:^2.0.0" + https-proxy-agent: "npm:^5.0.0" + make-dir: "npm:^3.1.0" + node-fetch: "npm:^2.6.7" + nopt: "npm:^5.0.0" + npmlog: "npm:^5.0.1" + rimraf: "npm:^3.0.2" + semver: "npm:^7.3.5" + tar: "npm:^6.1.11" + bin: + node-pre-gyp: bin/node-pre-gyp + checksum: 10/59529a2444e44fddb63057152452b00705aa58059079191126c79ac1388ae4565625afa84ed4dd1bf017d1111ab6e47907f7c5192e06d83c9496f2f3e708680a + languageName: node + linkType: hard + "@nodelib/fs.scandir@npm:2.1.5": version: 2.1.5 resolution: "@nodelib/fs.scandir@npm:2.1.5" @@ -5449,6 +5493,13 @@ __metadata: languageName: node linkType: hard +"@types/triple-beam@npm:^1.3.2": + version: 1.3.5 + resolution: "@types/triple-beam@npm:1.3.5" + checksum: 10/519b6a1b30d4571965c9706ad5400a200b94e4050feca3e7856e3ea7ac00ec9903e32e9a10e2762d0f7e472d5d03e5f4b29c16c0bd8c1f77c8876c683b2231f1 + languageName: node + linkType: hard + "@types/trusted-types@npm:^2.0.2": version: 2.0.7 resolution: "@types/trusted-types@npm:2.0.7" @@ -5967,13 +6018,22 @@ __metadata: languageName: node linkType: hard -"abbrev@npm:^1.0.0": +"abbrev@npm:1, abbrev@npm:^1.0.0": version: 1.1.1 resolution: "abbrev@npm:1.1.1" checksum: 10/2d882941183c66aa665118bafdab82b7a177e9add5eb2776c33e960a4f3c89cff88a1b38aba13a456de01d0dd9d66a8bea7c903268b21ea91dd1097e1e2e8243 languageName: node linkType: hard +"abort-controller@npm:^3.0.0": + version: 3.0.0 + resolution: "abort-controller@npm:3.0.0" + dependencies: + event-target-shim: "npm:^5.0.0" + checksum: 10/ed84af329f1828327798229578b4fe03a4dd2596ba304083ebd2252666bdc1d7647d66d0b18704477e1f8aa315f055944aa6e859afebd341f12d0a53c37b4b40 + languageName: node + linkType: hard + "absurd-sql@npm:0.0.54": version: 0.0.54 resolution: "absurd-sql@npm:0.0.54" @@ -5983,6 +6043,16 @@ __metadata: languageName: node linkType: hard +"accepts@npm:~1.3.8": + version: 1.3.8 + resolution: "accepts@npm:1.3.8" + dependencies: + mime-types: "npm:~2.1.34" + negotiator: "npm:0.6.3" + checksum: 10/67eaaa90e2917c58418e7a9b89392002d2b1ccd69bcca4799135d0c632f3b082f23f4ae4ddeedbced5aa59bcc7bdf4699c69ebed4593696c922462b7bc5744d6 + languageName: node + linkType: hard + "acorn-globals@npm:^6.0.0": version: 6.0.0 resolution: "acorn-globals@npm:6.0.0" @@ -6043,6 +6113,32 @@ __metadata: languageName: node linkType: hard +"actual-sync@file:../../../actual-server::locator=desktop-electron%40workspace%3Apackages%2Fdesktop-electron": + version: 24.10.0 + resolution: "actual-sync@file:../../../actual-server#../../../actual-server::hash=bcb84c&locator=desktop-electron%40workspace%3Apackages%2Fdesktop-electron" + dependencies: + "@actual-app/crdt": "npm:2.1.0" + "@actual-app/web": "npm:24.10.0" + bcrypt: "npm:^5.1.1" + better-sqlite3: "npm:^9.6.0" + body-parser: "npm:^1.20.3" + cors: "npm:^2.8.5" + date-fns: "npm:^2.30.0" + debug: "npm:^4.3.4" + express: "npm:4.20.0" + express-actuator: "npm:1.8.4" + express-rate-limit: "npm:^6.7.0" + express-response-size: "npm:^0.0.3" + express-winston: "npm:^4.2.0" + jws: "npm:^4.0.0" + migrate: "npm:^2.0.1" + nordigen-node: "npm:^1.4.0" + uuid: "npm:^9.0.0" + winston: "npm:^3.14.2" + checksum: 10/eb84130ce8e62e8292658b12f48b5cd36d4647a37c22277842781dc1425f98475f47b24cb5c77c0c1695ee33b3ba05e842bacb7fb0ea511a8ee1f5f5a83760ac + languageName: node + linkType: hard + "actual@workspace:.": version: 0.0.0-use.local resolution: "actual@workspace:." @@ -6290,6 +6386,16 @@ __metadata: languageName: node linkType: hard +"are-we-there-yet@npm:^2.0.0": + version: 2.0.0 + resolution: "are-we-there-yet@npm:2.0.0" + dependencies: + delegates: "npm:^1.0.0" + readable-stream: "npm:^3.6.0" + checksum: 10/ea6f47d14fc33ae9cbea3e686eeca021d9d7b9db83a306010dd04ad5f2c8b7675291b127d3fcbfcbd8fec26e47b3324ad5b469a6cc3733a582f2fe4e12fc6756 + languageName: node + linkType: hard + "are-we-there-yet@npm:^3.0.0": version: 3.0.1 resolution: "are-we-there-yet@npm:3.0.1" @@ -6342,6 +6448,13 @@ __metadata: languageName: node linkType: hard +"array-flatten@npm:1.1.1": + version: 1.1.1 + resolution: "array-flatten@npm:1.1.1" + checksum: 10/e13c9d247241be82f8b4ec71d035ed7204baa82fae820d4db6948d30d3c4a9f2b3905eb2eec2b937d4aa3565200bd3a1c500480114cff649fa748747d2a50feb + languageName: node + linkType: hard + "array-includes@npm:^3.1.6, array-includes@npm:^3.1.7, array-includes@npm:^3.1.8": version: 3.1.8 resolution: "array-includes@npm:3.1.8" @@ -6545,6 +6658,17 @@ __metadata: languageName: node linkType: hard +"axios@npm:^1.2.1": + version: 1.7.7 + resolution: "axios@npm:1.7.7" + dependencies: + follow-redirects: "npm:^1.15.6" + form-data: "npm:^4.0.0" + proxy-from-env: "npm:^1.1.0" + checksum: 10/7f875ea13b9298cd7b40fd09985209f7a38d38321f1118c701520939de2f113c4ba137832fe8e3f811f99a38e12c8225481011023209a77b0c0641270e20cde1 + languageName: node + linkType: hard + "axobject-query@npm:~3.1.1": version: 3.1.1 resolution: "axobject-query@npm:3.1.1" @@ -6709,6 +6833,16 @@ __metadata: languageName: node linkType: hard +"bcrypt@npm:^5.1.1": + version: 5.1.1 + resolution: "bcrypt@npm:5.1.1" + dependencies: + "@mapbox/node-pre-gyp": "npm:^1.0.11" + node-addon-api: "npm:^5.0.0" + checksum: 10/be6af3a93d90a0071c3b4412e8b82e2f319e26cb4e6cb14a1790cfe7c164792fa8add3ac9f30278a017d7d332ee8852601ce81a69737e9bfb9f10c878dd3d0dd + languageName: node + linkType: hard + "better-sqlite3@npm:^9.6.0": version: 9.6.0 resolution: "better-sqlite3@npm:9.6.0" @@ -6797,6 +6931,26 @@ __metadata: languageName: node linkType: hard +"body-parser@npm:1.20.3, body-parser@npm:^1.20.3": + version: 1.20.3 + resolution: "body-parser@npm:1.20.3" + dependencies: + bytes: "npm:3.1.2" + content-type: "npm:~1.0.5" + debug: "npm:2.6.9" + depd: "npm:2.0.0" + destroy: "npm:1.2.0" + http-errors: "npm:2.0.0" + iconv-lite: "npm:0.4.24" + on-finished: "npm:2.4.1" + qs: "npm:6.13.0" + raw-body: "npm:2.5.2" + type-is: "npm:~1.6.18" + unpipe: "npm:1.0.0" + checksum: 10/8723e3d7a672eb50854327453bed85ac48d045f4958e81e7d470c56bf111f835b97e5b73ae9f6393d0011cc9e252771f46fd281bbabc57d33d3986edf1e6aeca + languageName: node + linkType: hard + "boolbase@npm:^1.0.0": version: 1.0.0 resolution: "boolbase@npm:1.0.0" @@ -6949,6 +7103,13 @@ __metadata: languageName: node linkType: hard +"buffer-equal-constant-time@npm:1.0.1": + version: 1.0.1 + resolution: "buffer-equal-constant-time@npm:1.0.1" + checksum: 10/80bb945f5d782a56f374b292770901065bad21420e34936ecbe949e57724b4a13874f735850dd1cc61f078773c4fb5493a41391e7bda40d1fa388d6bd80daaab + languageName: node + linkType: hard + "buffer-equal@npm:^1.0.0": version: 1.0.1 resolution: "buffer-equal@npm:1.0.1" @@ -7031,6 +7192,13 @@ __metadata: languageName: node linkType: hard +"bytes@npm:3.1.2": + version: 3.1.2 + resolution: "bytes@npm:3.1.2" + checksum: 10/a10abf2ba70c784471d6b4f58778c0beeb2b5d405148e66affa91f23a9f13d07603d0a0354667310ae1d6dc141474ffd44e2a074be0f6e2254edb8fc21445388 + languageName: node + linkType: hard + "cac@npm:^6.7.14": version: 6.7.14 resolution: "cac@npm:6.7.14" @@ -7521,7 +7689,7 @@ __metadata: languageName: node linkType: hard -"color-convert@npm:^1.9.0": +"color-convert@npm:^1.9.0, color-convert@npm:^1.9.3": version: 1.9.3 resolution: "color-convert@npm:1.9.3" dependencies: @@ -7546,14 +7714,24 @@ __metadata: languageName: node linkType: hard -"color-name@npm:~1.1.4": +"color-name@npm:^1.0.0, color-name@npm:~1.1.4": version: 1.1.4 resolution: "color-name@npm:1.1.4" checksum: 10/b0445859521eb4021cd0fb0cc1a75cecf67fceecae89b63f62b201cca8d345baf8b952c966862a9d9a2632987d4f6581f0ec8d957dfacece86f0a7919316f610 languageName: node linkType: hard -"color-support@npm:^1.1.3": +"color-string@npm:^1.6.0": + version: 1.9.1 + resolution: "color-string@npm:1.9.1" + dependencies: + color-name: "npm:^1.0.0" + simple-swizzle: "npm:^0.2.2" + checksum: 10/72aa0b81ee71b3f4fb1ac9cd839cdbd7a011a7d318ef58e6cb13b3708dca75c7e45029697260488709f1b1c7ac4e35489a87e528156c1e365917d1c4ccb9b9cd + languageName: node + linkType: hard + +"color-support@npm:^1.1.2, color-support@npm:^1.1.3": version: 1.1.3 resolution: "color-support@npm:1.1.3" bin: @@ -7562,6 +7740,16 @@ __metadata: languageName: node linkType: hard +"color@npm:^3.1.3": + version: 3.2.1 + resolution: "color@npm:3.2.1" + dependencies: + color-convert: "npm:^1.9.3" + color-string: "npm:^1.6.0" + checksum: 10/bf70438e0192f4f62f4bfbb303e7231289e8cc0d15ff6b6cbdb722d51f680049f38d4fdfc057a99cb641895cf5e350478c61d98586400b060043afc44285e7ae + languageName: node + linkType: hard + "colorette@npm:^2.0.14, colorette@npm:^2.0.20": version: 2.0.20 resolution: "colorette@npm:2.0.20" @@ -7576,6 +7764,16 @@ __metadata: languageName: node linkType: hard +"colorspace@npm:1.1.x": + version: 1.1.4 + resolution: "colorspace@npm:1.1.4" + dependencies: + color: "npm:^3.1.3" + text-hex: "npm:1.0.x" + checksum: 10/bb3934ef3c417e961e6d03d7ca60ea6e175947029bfadfcdb65109b01881a1c0ecf9c2b0b59abcd0ee4a0d7c1eae93beed01b0e65848936472270a0b341ebce8 + languageName: node + linkType: hard + "combined-stream@npm:^1.0.8": version: 1.0.8 resolution: "combined-stream@npm:1.0.8" @@ -7599,7 +7797,7 @@ __metadata: languageName: node linkType: hard -"commander@npm:^2.20.0, commander@npm:^2.8.1": +"commander@npm:^2.20.0, commander@npm:^2.20.3, commander@npm:^2.8.1": version: 2.20.3 resolution: "commander@npm:2.20.3" checksum: 10/90c5b6898610cd075984c58c4f88418a4fb44af08c1b1415e9854c03171bec31b336b7f3e4cefe33de994b3f12b03c5e2d638da4316df83593b9e82554e7e95b @@ -7696,14 +7894,14 @@ __metadata: languageName: node linkType: hard -"console-control-strings@npm:^1.1.0": +"console-control-strings@npm:^1.0.0, console-control-strings@npm:^1.1.0": version: 1.1.0 resolution: "console-control-strings@npm:1.1.0" checksum: 10/27b5fa302bc8e9ae9e98c03c66d76ca289ad0c61ce2fe20ab288d288bee875d217512d2edb2363fc83165e88f1c405180cf3f5413a46e51b4fe1a004840c6cdb languageName: node linkType: hard -"content-disposition@npm:^0.5.2": +"content-disposition@npm:0.5.4, content-disposition@npm:^0.5.2": version: 0.5.4 resolution: "content-disposition@npm:0.5.4" dependencies: @@ -7712,6 +7910,13 @@ __metadata: languageName: node linkType: hard +"content-type@npm:~1.0.4, content-type@npm:~1.0.5": + version: 1.0.5 + resolution: "content-type@npm:1.0.5" + checksum: 10/585847d98dc7fb8035c02ae2cb76c7a9bd7b25f84c447e5ed55c45c2175e83617c8813871b4ee22f368126af6b2b167df655829007b21aa10302873ea9c62662 + languageName: node + linkType: hard + "convert-source-map@npm:^1.4.0, convert-source-map@npm:^1.6.0": version: 1.9.0 resolution: "convert-source-map@npm:1.9.0" @@ -7726,6 +7931,20 @@ __metadata: languageName: node linkType: hard +"cookie-signature@npm:1.0.6": + version: 1.0.6 + resolution: "cookie-signature@npm:1.0.6" + checksum: 10/f4e1b0a98a27a0e6e66fd7ea4e4e9d8e038f624058371bf4499cfcd8f3980be9a121486995202ba3fca74fbed93a407d6d54d43a43f96fd28d0bd7a06761591a + languageName: node + linkType: hard + +"cookie@npm:0.6.0": + version: 0.6.0 + resolution: "cookie@npm:0.6.0" + checksum: 10/c1f8f2ea7d443b9331680598b0ae4e6af18a618c37606d1bbdc75bec8361cce09fe93e727059a673f2ba24467131a9fb5a4eec76bb1b149c1b3e1ccb268dc583 + languageName: node + linkType: hard + "copyfiles@npm:^2.4.1": version: 2.4.1 resolution: "copyfiles@npm:2.4.1" @@ -7781,6 +8000,16 @@ __metadata: languageName: node linkType: hard +"cors@npm:^2.8.5": + version: 2.8.5 + resolution: "cors@npm:2.8.5" + dependencies: + object-assign: "npm:^4" + vary: "npm:^1" + checksum: 10/66e88e08edee7cbce9d92b4d28a2028c88772a4c73e02f143ed8ca76789f9b59444eed6b1c167139e76fa662998c151322720093ba229f9941365ada5a6fc2c6 + languageName: node + linkType: hard + "cosmiconfig@npm:^8.1.3": version: 8.2.0 resolution: "cosmiconfig@npm:8.2.0" @@ -8140,6 +8369,20 @@ __metadata: languageName: node linkType: hard +"dateformat@npm:^4.6.3": + version: 4.6.3 + resolution: "dateformat@npm:4.6.3" + checksum: 10/5c149c91bf9ce2142c89f84eee4c585f0cb1f6faf2536b1af89873f862666a28529d1ccafc44750aa01384da2197c4f76f4e149a3cc0c1cb2c46f5cc45f2bcb5 + languageName: node + linkType: hard + +"dayjs@npm:^1.11.3": + version: 1.11.13 + resolution: "dayjs@npm:1.11.13" + checksum: 10/7374d63ab179b8d909a95e74790def25c8986e329ae989840bacb8b1888be116d20e1c4eee75a69ea0dfbae13172efc50ef85619d304ee7ca3c01d5878b704f5 + languageName: node + linkType: hard + "debounce@npm:^1.2.1": version: 1.2.1 resolution: "debounce@npm:1.2.1" @@ -8147,6 +8390,15 @@ __metadata: languageName: node linkType: hard +"debug@npm:2.6.9, debug@npm:^2.2.0": + version: 2.6.9 + resolution: "debug@npm:2.6.9" + dependencies: + ms: "npm:2.0.0" + checksum: 10/e07005f2b40e04f1bd14a3dd20520e9c4f25f60224cb006ce9d6781732c917964e9ec029fc7f1a151083cd929025ad5133814d4dc624a9aaf020effe4914ed14 + languageName: node + linkType: hard + "debug@npm:4, debug@npm:^4.0.0, debug@npm:^4.1.0, debug@npm:^4.1.1, debug@npm:^4.3.1, debug@npm:^4.3.2, debug@npm:^4.3.3, debug@npm:^4.3.4, debug@npm:~4.3.6": version: 4.3.6 resolution: "debug@npm:4.3.6" @@ -8159,15 +8411,6 @@ __metadata: languageName: node linkType: hard -"debug@npm:^2.2.0": - version: 2.6.9 - resolution: "debug@npm:2.6.9" - dependencies: - ms: "npm:2.0.0" - checksum: 10/e07005f2b40e04f1bd14a3dd20520e9c4f25f60224cb006ce9d6781732c917964e9ec029fc7f1a151083cd929025ad5133814d4dc624a9aaf020effe4914ed14 - languageName: node - linkType: hard - "debug@npm:^3.2.7": version: 3.2.7 resolution: "debug@npm:3.2.7" @@ -8410,7 +8653,7 @@ __metadata: languageName: node linkType: hard -"depd@npm:^2.0.0": +"depd@npm:2.0.0, depd@npm:^2.0.0": version: 2.0.0 resolution: "depd@npm:2.0.0" checksum: 10/c0c8ff36079ce5ada64f46cc9d6fd47ebcf38241105b6e0c98f412e8ad91f084bcf906ff644cc3a4bd876ca27a62accb8b0fff72ea6ed1a414b89d8506f4a5ca @@ -8432,6 +8675,7 @@ __metadata: "@electron/rebuild": "npm:3.6.0" "@types/copyfiles": "npm:^2" "@types/fs-extra": "npm:^11" + actual-sync: "file:../../../actual-server" adm-zip: "npm:^0.5.10" better-sqlite3: "npm:^9.6.0" copyfiles: "npm:^2.4.1" @@ -8445,6 +8689,13 @@ __metadata: languageName: unknown linkType: soft +"destroy@npm:1.2.0": + version: 1.2.0 + resolution: "destroy@npm:1.2.0" + checksum: 10/0acb300b7478a08b92d810ab229d5afe0d2f4399272045ab22affa0d99dbaf12637659411530a6fcd597a9bdac718fc94373a61a95b4651bbc7b83684a565e38 + languageName: node + linkType: hard + "detect-libc@npm:^2.0.0, detect-libc@npm:^2.0.1": version: 2.0.2 resolution: "detect-libc@npm:2.0.2" @@ -8667,6 +8918,20 @@ __metadata: languageName: node linkType: hard +"dotenv@npm:^10.0.0": + version: 10.0.0 + resolution: "dotenv@npm:10.0.0" + checksum: 10/55f701ae213e3afe3f4232fae5edfb6e0c49f061a363ff9f1c5a0c2bf3fb990a6e49aeada11b2a116efb5fdc3bc3f1ef55ab330be43033410b267f7c0809a9dc + languageName: node + linkType: hard + +"dotenv@npm:^16.0.0": + version: 16.4.5 + resolution: "dotenv@npm:16.4.5" + checksum: 10/55a3134601115194ae0f924e54473459ed0d9fc340ae610b676e248cca45aa7c680d86365318ea964e6da4e2ea80c4514c1adab5adb43d6867fb57ff068f95c8 + languageName: node + linkType: hard + "dotenv@npm:^9.0.2": version: 9.0.2 resolution: "dotenv@npm:9.0.2" @@ -8729,6 +8994,22 @@ __metadata: languageName: node linkType: hard +"ecdsa-sig-formatter@npm:1.0.11": + version: 1.0.11 + resolution: "ecdsa-sig-formatter@npm:1.0.11" + dependencies: + safe-buffer: "npm:^5.0.1" + checksum: 10/878e1aab8a42773320bc04c6de420bee21aebd71810e40b1799880a8a1c4594bcd6adc3d4213a0fb8147d4c3f529d8f9a618d7f59ad5a9a41b142058aceda23f + languageName: node + linkType: hard + +"ee-first@npm:1.1.1": + version: 1.1.1 + resolution: "ee-first@npm:1.1.1" + checksum: 10/1b4cac778d64ce3b582a7e26b218afe07e207a0f9bfe13cc7395a6d307849cfe361e65033c3251e00c27dd060cab43014c2d6b2647676135e18b77d2d05b3f4f + languageName: node + linkType: hard + "ejs@npm:^3.1.6, ejs@npm:^3.1.8": version: 3.1.9 resolution: "ejs@npm:3.1.9" @@ -8832,6 +9113,27 @@ __metadata: languageName: node linkType: hard +"enabled@npm:2.0.x": + version: 2.0.0 + resolution: "enabled@npm:2.0.0" + checksum: 10/9d256d89f4e8a46ff988c6a79b22fa814b4ffd82826c4fdacd9b42e9b9465709d3b748866d0ab4d442dfc6002d81de7f7b384146ccd1681f6a7f868d2acca063 + languageName: node + linkType: hard + +"encodeurl@npm:~1.0.2": + version: 1.0.2 + resolution: "encodeurl@npm:1.0.2" + checksum: 10/e50e3d508cdd9c4565ba72d2012e65038e5d71bdc9198cb125beb6237b5b1ade6c0d343998da9e170fb2eae52c1bed37d4d6d98a46ea423a0cddbed5ac3f780c + languageName: node + linkType: hard + +"encodeurl@npm:~2.0.0": + version: 2.0.0 + resolution: "encodeurl@npm:2.0.0" + checksum: 10/abf5cd51b78082cf8af7be6785813c33b6df2068ce5191a40ca8b1afe6a86f9230af9a9ce694a5ce4665955e5c1120871826df9c128a642e09c58d592e2807fe + languageName: node + linkType: hard + "encoding@npm:^0.1.11, encoding@npm:^0.1.13": version: 0.1.13 resolution: "encoding@npm:0.1.13" @@ -9170,6 +9472,13 @@ __metadata: languageName: node linkType: hard +"escape-html@npm:~1.0.3": + version: 1.0.3 + resolution: "escape-html@npm:1.0.3" + checksum: 10/6213ca9ae00d0ab8bccb6d8d4e0a98e76237b2410302cf7df70aaa6591d509a2a37ce8998008cbecae8fc8ffaadf3fb0229535e6a145f3ce0b211d060decbb24 + languageName: node + linkType: hard + "escape-string-regexp@npm:^1.0.2, escape-string-regexp@npm:^1.0.5": version: 1.0.5 resolution: "escape-string-regexp@npm:1.0.5" @@ -9632,6 +9941,20 @@ __metadata: languageName: node linkType: hard +"etag@npm:~1.8.1": + version: 1.8.1 + resolution: "etag@npm:1.8.1" + checksum: 10/571aeb3dbe0f2bbd4e4fadbdb44f325fc75335cd5f6f6b6a091e6a06a9f25ed5392f0863c5442acb0646787446e816f13cbfc6edce5b07658541dff573cab1ff + languageName: node + linkType: hard + +"event-target-shim@npm:^5.0.0": + version: 5.0.1 + resolution: "event-target-shim@npm:5.0.1" + checksum: 10/49ff46c3a7facbad3decb31f597063e761785d7fdb3920d4989d7b08c97a61c2f51183e2f3a03130c9088df88d4b489b1b79ab632219901f184f85158508f4c8 + languageName: node + linkType: hard + "eventemitter3@npm:^4.0.1": version: 4.0.7 resolution: "eventemitter3@npm:4.0.7" @@ -9646,7 +9969,7 @@ __metadata: languageName: node linkType: hard -"events@npm:^3.2.0": +"events@npm:^3.2.0, events@npm:^3.3.0": version: 3.3.0 resolution: "events@npm:3.3.0" checksum: 10/a3d47e285e28d324d7180f1e493961a2bbb4cad6412090e4dec114f4db1f5b560c7696ee8e758f55e23913ede856e3689cd3aa9ae13c56b5d8314cd3b3ddd1be @@ -9772,6 +10095,85 @@ __metadata: languageName: node linkType: hard +"express-actuator@npm:1.8.4": + version: 1.8.4 + resolution: "express-actuator@npm:1.8.4" + dependencies: + dayjs: "npm:^1.11.3" + properties-reader: "npm:^2.2.0" + checksum: 10/5f55418bfd660e8781a777bf48c7eeda2d0a38fa11b2f4670555123380f522eed5bb884df297fb420904663bd2057834227c0eea68d6f852432ac3bc77842c09 + languageName: node + linkType: hard + +"express-rate-limit@npm:^6.7.0": + version: 6.11.2 + resolution: "express-rate-limit@npm:6.11.2" + peerDependencies: + express: ^4 || ^5 + checksum: 10/9b482cf91e030edcb88292831b2515208ddb9ec92330c54fb487c700fe8ac5000c7f5d2623ae4913b5a7fcce8e9ef65eb017e28edc96ace0ed111c16b996ccfc + languageName: node + linkType: hard + +"express-response-size@npm:^0.0.3": + version: 0.0.3 + resolution: "express-response-size@npm:0.0.3" + dependencies: + on-headers: "npm:1.0.1" + checksum: 10/6c97395d225e5aa98338569842ed84e5d861f19f6f1c535e70c97d65ffe7301826299607f491c824f240952b39b4b18c3269ed3652ecb8bd0abb93c7ece7048b + languageName: node + linkType: hard + +"express-winston@npm:^4.2.0": + version: 4.2.0 + resolution: "express-winston@npm:4.2.0" + dependencies: + chalk: "npm:^2.4.2" + lodash: "npm:^4.17.21" + peerDependencies: + winston: ">=3.x <4" + checksum: 10/3a4fb701d81b75815ccdf19f93585adb3af7ad61b4f67e435bb324486d9e3773e85e8761fb1e4c3833b0a493f363c792e8688eba018d1920fd3ee6d2505e5b3a + languageName: node + linkType: hard + +"express@npm:4.20.0": + version: 4.20.0 + resolution: "express@npm:4.20.0" + dependencies: + accepts: "npm:~1.3.8" + array-flatten: "npm:1.1.1" + body-parser: "npm:1.20.3" + content-disposition: "npm:0.5.4" + content-type: "npm:~1.0.4" + cookie: "npm:0.6.0" + cookie-signature: "npm:1.0.6" + debug: "npm:2.6.9" + depd: "npm:2.0.0" + encodeurl: "npm:~2.0.0" + escape-html: "npm:~1.0.3" + etag: "npm:~1.8.1" + finalhandler: "npm:1.2.0" + fresh: "npm:0.5.2" + http-errors: "npm:2.0.0" + merge-descriptors: "npm:1.0.3" + methods: "npm:~1.1.2" + on-finished: "npm:2.4.1" + parseurl: "npm:~1.3.3" + path-to-regexp: "npm:0.1.10" + proxy-addr: "npm:~2.0.7" + qs: "npm:6.11.0" + range-parser: "npm:~1.2.1" + safe-buffer: "npm:5.2.1" + send: "npm:0.19.0" + serve-static: "npm:1.16.0" + setprototypeof: "npm:1.2.0" + statuses: "npm:2.0.1" + type-is: "npm:~1.6.18" + utils-merge: "npm:1.0.1" + vary: "npm:~1.1.2" + checksum: 10/4131f566cf8f6d1611475d5ff5d0dbc5c628ad8b525aa2aa2b3da9a23a041efcce09ede10b8a31315b0258ac4e53208a009fd7669ee1eb385936a0d54adb3cde + languageName: node + linkType: hard + "ext-list@npm:^2.0.0": version: 2.2.2 resolution: "ext-list@npm:2.2.2" @@ -9951,6 +10353,13 @@ __metadata: languageName: node linkType: hard +"fecha@npm:^4.2.0": + version: 4.2.3 + resolution: "fecha@npm:4.2.3" + checksum: 10/534ce630c8f63c116292145607fc18c0f06bfa2fd74094357bf65daacc5d3f4f2b285bf8eb112c3bbf98c5caa6d386cced797f44b9b1b33da0c0a81020444826 + languageName: node + linkType: hard + "fetch-blob@npm:^3.1.2, fetch-blob@npm:^3.1.4": version: 3.2.0 resolution: "fetch-blob@npm:3.2.0" @@ -10034,6 +10443,21 @@ __metadata: languageName: node linkType: hard +"finalhandler@npm:1.2.0": + version: 1.2.0 + resolution: "finalhandler@npm:1.2.0" + dependencies: + debug: "npm:2.6.9" + encodeurl: "npm:~1.0.2" + escape-html: "npm:~1.0.3" + on-finished: "npm:2.4.1" + parseurl: "npm:~1.3.3" + statuses: "npm:2.0.1" + unpipe: "npm:~1.0.0" + checksum: 10/635718cb203c6d18e6b48dfbb6c54ccb08ea470e4f474ddcef38c47edcf3227feec316f886dd701235997d8af35240cae49856721ce18f539ad038665ebbf163 + languageName: node + linkType: hard + "find-up@npm:^2.0.0": version: 2.1.0 resolution: "find-up@npm:2.1.0" @@ -10080,6 +10504,13 @@ __metadata: languageName: node linkType: hard +"fn.name@npm:1.x.x": + version: 1.1.0 + resolution: "fn.name@npm:1.1.0" + checksum: 10/000198af190ae02f0138ac5fa4310da733224c628e0230c81e3fff7c4e094af7e0e8bb9f4357cabd21db601759d89f3445da744afbae20623cfa41edf3888397 + languageName: node + linkType: hard + "focus-visible@npm:^4.1.5": version: 4.1.5 resolution: "focus-visible@npm:4.1.5" @@ -10087,6 +10518,16 @@ __metadata: languageName: node linkType: hard +"follow-redirects@npm:^1.15.6": + version: 1.15.9 + resolution: "follow-redirects@npm:1.15.9" + peerDependenciesMeta: + debug: + optional: true + checksum: 10/e3ab42d1097e90d28b913903841e6779eb969b62a64706a3eb983e894a5db000fbd89296f45f08885a0e54cd558ef62e81be1165da9be25a6c44920da10f424c + languageName: node + linkType: hard + "for-each@npm:^0.3.3": version: 0.3.3 resolution: "for-each@npm:0.3.3" @@ -10137,6 +10578,20 @@ __metadata: languageName: node linkType: hard +"forwarded@npm:0.2.0": + version: 0.2.0 + resolution: "forwarded@npm:0.2.0" + checksum: 10/29ba9fd347117144e97cbb8852baae5e8b2acb7d1b591ef85695ed96f5b933b1804a7fac4a15dd09ca7ac7d0cdc104410e8102aae2dd3faa570a797ba07adb81 + languageName: node + linkType: hard + +"fresh@npm:0.5.2": + version: 0.5.2 + resolution: "fresh@npm:0.5.2" + checksum: 10/64c88e489b5d08e2f29664eb3c79c705ff9a8eb15d3e597198ef76546d4ade295897a44abb0abd2700e7ef784b2e3cbf1161e4fbf16f59129193fd1030d16da1 + languageName: node + linkType: hard + "fs-constants@npm:^1.0.0": version: 1.0.0 resolution: "fs-constants@npm:1.0.0" @@ -10321,6 +10776,23 @@ __metadata: languageName: node linkType: hard +"gauge@npm:^3.0.0": + version: 3.0.2 + resolution: "gauge@npm:3.0.2" + dependencies: + aproba: "npm:^1.0.3 || ^2.0.0" + color-support: "npm:^1.1.2" + console-control-strings: "npm:^1.0.0" + has-unicode: "npm:^2.0.1" + object-assign: "npm:^4.1.1" + signal-exit: "npm:^3.0.0" + string-width: "npm:^4.2.3" + strip-ansi: "npm:^6.0.1" + wide-align: "npm:^1.1.2" + checksum: 10/46df086451672a5fecd58f7ec86da74542c795f8e00153fbef2884286ce0e86653c3eb23be2d0abb0c4a82b9b2a9dec3b09b6a1cf31c28085fa0376599a26589 + languageName: node + linkType: hard + "gauge@npm:^4.0.3": version: 4.0.4 resolution: "gauge@npm:4.0.4" @@ -10918,6 +11390,19 @@ __metadata: languageName: node linkType: hard +"http-errors@npm:2.0.0": + version: 2.0.0 + resolution: "http-errors@npm:2.0.0" + dependencies: + depd: "npm:2.0.0" + inherits: "npm:2.0.4" + setprototypeof: "npm:1.2.0" + statuses: "npm:2.0.1" + toidentifier: "npm:1.0.1" + checksum: 10/0e7f76ee8ff8a33e58a3281a469815b893c41357378f408be8f6d4aa7d1efafb0da064625518e7078381b6a92325949b119dc38fcb30bdbc4e3a35f78c44c439 + languageName: node + linkType: hard + "http-proxy-agent@npm:^4.0.1": version: 4.0.1 resolution: "http-proxy-agent@npm:4.0.1" @@ -11160,7 +11645,7 @@ __metadata: languageName: node linkType: hard -"inherits@npm:2, inherits@npm:^2.0.1, inherits@npm:^2.0.3, inherits@npm:^2.0.4, inherits@npm:~2.0.1, inherits@npm:~2.0.3, inherits@npm:~2.0.4": +"inherits@npm:2, inherits@npm:2.0.4, inherits@npm:^2.0.1, inherits@npm:^2.0.3, inherits@npm:^2.0.4, inherits@npm:~2.0.1, inherits@npm:~2.0.3, inherits@npm:~2.0.4": version: 2.0.4 resolution: "inherits@npm:2.0.4" checksum: 10/cd45e923bee15186c07fa4c89db0aace24824c482fb887b528304694b2aa6ff8a898da8657046a5dcf3e46cd6db6c61629551f9215f208d7c3f157cf9b290521 @@ -11249,6 +11734,13 @@ __metadata: languageName: node linkType: hard +"ipaddr.js@npm:1.9.1": + version: 1.9.1 + resolution: "ipaddr.js@npm:1.9.1" + checksum: 10/864d0cced0c0832700e9621913a6429ccdc67f37c1bd78fb8c6789fff35c9d167cb329134acad2290497a53336813ab4798d2794fd675d5eb33b5fdf0982b9ca + languageName: node + linkType: hard + "is-arguments@npm:^1.0.4, is-arguments@npm:^1.1.1": version: 1.1.1 resolution: "is-arguments@npm:1.1.1" @@ -11276,6 +11768,13 @@ __metadata: languageName: node linkType: hard +"is-arrayish@npm:^0.3.1": + version: 0.3.2 + resolution: "is-arrayish@npm:0.3.2" + checksum: 10/81a78d518ebd8b834523e25d102684ee0f7e98637136d3bdc93fd09636350fa06f1d8ca997ea28143d4d13cb1b69c0824f082db0ac13e1ab3311c10ffea60ade + languageName: node + linkType: hard + "is-async-function@npm:^2.0.0": version: 2.0.0 resolution: "is-async-function@npm:2.0.0" @@ -12846,6 +13345,27 @@ __metadata: languageName: node linkType: hard +"jwa@npm:^2.0.0": + version: 2.0.0 + resolution: "jwa@npm:2.0.0" + dependencies: + buffer-equal-constant-time: "npm:1.0.1" + ecdsa-sig-formatter: "npm:1.0.11" + safe-buffer: "npm:^5.0.1" + checksum: 10/ab983f6685d99d13ddfbffef9b1c66309a536362a8412d49ba6e687d834a1240ce39290f30ac7dbe241e0ab6c76fee7ff795776ce534e11d148158c9b7193498 + languageName: node + linkType: hard + +"jws@npm:^4.0.0": + version: 4.0.0 + resolution: "jws@npm:4.0.0" + dependencies: + jwa: "npm:^2.0.0" + safe-buffer: "npm:^5.0.1" + checksum: 10/1d15f4cdea376c6bd6a81002bd2cb0bf3d51d83da8f0727947b5ba3e10cf366721b8c0d099bf8c1eb99eb036e2c55e5fd5efd378ccff75a2b4e0bd10002348b9 + languageName: node + linkType: hard + "keyv@npm:^4.0.0": version: 4.5.2 resolution: "keyv@npm:4.5.2" @@ -12876,6 +13396,13 @@ __metadata: languageName: node linkType: hard +"kuler@npm:^2.0.0": + version: 2.0.0 + resolution: "kuler@npm:2.0.0" + checksum: 10/9e10b5a1659f9ed8761d38df3c35effabffbd19fc6107324095238e4ef0ff044392cae9ac64a1c2dda26e532426485342226b93806bd97504b174b0dcf04ed81 + languageName: node + linkType: hard + "language-subtag-registry@npm:^0.3.20": version: 0.3.23 resolution: "language-subtag-registry@npm:0.3.23" @@ -13117,6 +13644,20 @@ __metadata: languageName: node linkType: hard +"logform@npm:^2.6.0, logform@npm:^2.6.1": + version: 2.6.1 + resolution: "logform@npm:2.6.1" + dependencies: + "@colors/colors": "npm:1.6.0" + "@types/triple-beam": "npm:^1.3.2" + fecha: "npm:^4.2.0" + ms: "npm:^2.1.1" + safe-stable-stringify: "npm:^2.3.1" + triple-beam: "npm:^1.3.0" + checksum: 10/e67f414787fbfe1e6a997f4c84300c7e06bee3d0bd579778af667e24b36db3ea200ed195d41b61311ff738dab7faabc615a07b174b22fe69e0b2f39e985be64b + languageName: node + linkType: hard + "longest-streak@npm:^3.0.0": version: 3.1.0 resolution: "longest-streak@npm:3.1.0" @@ -13313,7 +13854,7 @@ __metadata: languageName: node linkType: hard -"make-dir@npm:^3.0.0": +"make-dir@npm:^3.0.0, make-dir@npm:^3.1.0": version: 3.1.0 resolution: "make-dir@npm:3.1.0" dependencies: @@ -13622,6 +14163,13 @@ __metadata: languageName: node linkType: hard +"media-typer@npm:0.3.0": + version: 0.3.0 + resolution: "media-typer@npm:0.3.0" + checksum: 10/38e0984db39139604756903a01397e29e17dcb04207bb3e081412ce725ab17338ecc47220c1b186b6bbe79a658aad1b0d41142884f5a481f36290cdefbe6aa46 + languageName: node + linkType: hard + "mem@npm:^1.1.0": version: 1.1.0 resolution: "mem@npm:1.1.0" @@ -13654,6 +14202,13 @@ __metadata: languageName: node linkType: hard +"merge-descriptors@npm:1.0.3": + version: 1.0.3 + resolution: "merge-descriptors@npm:1.0.3" + checksum: 10/52117adbe0313d5defa771c9993fe081e2d2df9b840597e966aadafde04ae8d0e3da46bac7ca4efc37d4d2b839436582659cd49c6a43eacb3fe3050896a105d1 + languageName: node + linkType: hard + "merge-stream@npm:^2.0.0": version: 2.0.0 resolution: "merge-stream@npm:2.0.0" @@ -13668,6 +14223,13 @@ __metadata: languageName: node linkType: hard +"methods@npm:~1.1.2": + version: 1.1.2 + resolution: "methods@npm:1.1.2" + checksum: 10/a385dd974faa34b5dd021b2bbf78c722881bf6f003bfe6d391d7da3ea1ed625d1ff10ddd13c57531f628b3e785be38d3eed10ad03cebd90b76932413df9a1820 + languageName: node + linkType: hard + "micromark-core-commonmark@npm:^1.0.0, micromark-core-commonmark@npm:^1.0.1": version: 1.1.0 resolution: "micromark-core-commonmark@npm:1.1.0" @@ -14007,6 +14569,29 @@ __metadata: languageName: node linkType: hard +"migrate@npm:^2.0.1": + version: 2.1.0 + resolution: "migrate@npm:2.1.0" + dependencies: + chalk: "npm:^4.1.2" + commander: "npm:^2.20.3" + dateformat: "npm:^4.6.3" + dotenv: "npm:^16.0.0" + inherits: "npm:^2.0.3" + minimatch: "npm:^9.0.1" + mkdirp: "npm:^3.0.1" + slug: "npm:^8.2.2" + bin: + migrate: bin/migrate + migrate-create: bin/migrate-create + migrate-down: bin/migrate-down + migrate-init: bin/migrate-init + migrate-list: bin/migrate-list + migrate-up: bin/migrate-up + checksum: 10/13aabd8f018053f8db079925b02da808efc196150e0f28d536752e4dab2c81d4cb9dc69fb54268bfa2b86e79ce5241a6b6514bcbdc9250a4d71e065f61f774ae + languageName: node + linkType: hard + "mime-db@npm:1.52.0, mime-db@npm:^1.28.0": version: 1.52.0 resolution: "mime-db@npm:1.52.0" @@ -14014,7 +14599,7 @@ __metadata: languageName: node linkType: hard -"mime-types@npm:^2.1.12, mime-types@npm:^2.1.27": +"mime-types@npm:^2.1.12, mime-types@npm:^2.1.27, mime-types@npm:~2.1.24, mime-types@npm:~2.1.34": version: 2.1.35 resolution: "mime-types@npm:2.1.35" dependencies: @@ -14023,6 +14608,15 @@ __metadata: languageName: node linkType: hard +"mime@npm:1.6.0": + version: 1.6.0 + resolution: "mime@npm:1.6.0" + bin: + mime: cli.js + checksum: 10/b7d98bb1e006c0e63e2c91b590fe1163b872abf8f7ef224d53dd31499c2197278a6d3d0864c45239b1a93d22feaf6f9477e9fc847eef945838150b8c02d03170 + languageName: node + linkType: hard + "mime@npm:^2.5.2": version: 2.6.0 resolution: "mime@npm:2.6.0" @@ -14230,6 +14824,15 @@ __metadata: languageName: node linkType: hard +"mkdirp@npm:^3.0.1": + version: 3.0.1 + resolution: "mkdirp@npm:3.0.1" + bin: + mkdirp: dist/cjs/src/bin.js + checksum: 10/16fd79c28645759505914561e249b9a1f5fe3362279ad95487a4501e4467abeb714fd35b95307326b8fd03f3c7719065ef11a6f97b7285d7888306d1bd2232ba + languageName: node + linkType: hard + "mktemp@npm:~0.4.0": version: 0.4.0 resolution: "mktemp@npm:0.4.0" @@ -14284,7 +14887,7 @@ __metadata: languageName: node linkType: hard -"ms@npm:^2.0.0, ms@npm:^2.1.1": +"ms@npm:2.1.3, ms@npm:^2.0.0, ms@npm:^2.1.1": version: 2.1.3 resolution: "ms@npm:2.1.3" checksum: 10/aa92de608021b242401676e35cfa5aa42dd70cbdc082b916da7fb925c542173e36bce97ea3e804923fe92c0ad991434e4a38327e15a1b5b5f945d66df615ae6d @@ -14328,7 +14931,7 @@ __metadata: languageName: node linkType: hard -"negotiator@npm:^0.6.3": +"negotiator@npm:0.6.3, negotiator@npm:^0.6.3": version: 0.6.3 resolution: "negotiator@npm:0.6.3" checksum: 10/2723fb822a17ad55c93a588a4bc44d53b22855bf4be5499916ca0cab1e7165409d0b288ba2577d7b029f10ce18cf2ed8e703e5af31c984e1e2304277ef979837 @@ -14377,6 +14980,15 @@ __metadata: languageName: node linkType: hard +"node-addon-api@npm:^5.0.0": + version: 5.1.0 + resolution: "node-addon-api@npm:5.1.0" + dependencies: + node-gyp: "npm:latest" + checksum: 10/595f59ffb4630564f587c502119cbd980d302e482781021f3b479f5fc7e41cf8f2f7280fdc2795f32d148e4f3259bd15043c52d4a3442796aa6f1ae97b959636 + languageName: node + linkType: hard + "node-api-version@npm:^0.2.0": version: 0.2.0 resolution: "node-api-version@npm:0.2.0" @@ -14412,7 +15024,7 @@ __metadata: languageName: node linkType: hard -"node-fetch@npm:^2.7.0": +"node-fetch@npm:^2.6.7, node-fetch@npm:^2.7.0": version: 2.7.0 resolution: "node-fetch@npm:2.7.0" dependencies: @@ -14516,6 +15128,17 @@ __metadata: languageName: node linkType: hard +"nopt@npm:^5.0.0": + version: 5.0.0 + resolution: "nopt@npm:5.0.0" + dependencies: + abbrev: "npm:1" + bin: + nopt: bin/nopt.js + checksum: 10/00f9bb2d16449469ba8ffcf9b8f0eae6bae285ec74b135fec533e5883563d2400c0cd70902d0a7759e47ac031ccf206ace4e86556da08ed3f1c66dda206e9ccd + languageName: node + linkType: hard + "nopt@npm:^6.0.0": version: 6.0.0 resolution: "nopt@npm:6.0.0" @@ -14527,6 +15150,16 @@ __metadata: languageName: node linkType: hard +"nordigen-node@npm:^1.4.0": + version: 1.4.0 + resolution: "nordigen-node@npm:1.4.0" + dependencies: + axios: "npm:^1.2.1" + dotenv: "npm:^10.0.0" + checksum: 10/88def53ba66468f4ac05ec12c6958eb87c20907a51bfc1f02b974ac3d9efcc61e67e4e9408c3d008703ad3ae9723564b5c08a627c4d3ac412cea4fb7ff50d6f2 + languageName: node + linkType: hard + "normalize-package-data@npm:^2.3.2": version: 2.5.0 resolution: "normalize-package-data@npm:2.5.0" @@ -14620,6 +15253,18 @@ __metadata: languageName: node linkType: hard +"npmlog@npm:^5.0.1": + version: 5.0.1 + resolution: "npmlog@npm:5.0.1" + dependencies: + are-we-there-yet: "npm:^2.0.0" + console-control-strings: "npm:^1.1.0" + gauge: "npm:^3.0.0" + set-blocking: "npm:^2.0.0" + checksum: 10/f42c7b9584cdd26a13c41a21930b6f5912896b6419ab15be88cc5721fc792f1c3dd30eb602b26ae08575694628ba70afdcf3675d86e4f450fc544757e52726ec + languageName: node + linkType: hard + "npmlog@npm:^6.0.0": version: 6.0.2 resolution: "npmlog@npm:6.0.2" @@ -14655,7 +15300,7 @@ __metadata: languageName: node linkType: hard -"object-assign@npm:^4.0.1, object-assign@npm:^4.1.0, object-assign@npm:^4.1.1": +"object-assign@npm:^4, object-assign@npm:^4.0.1, object-assign@npm:^4.1.0, object-assign@npm:^4.1.1": version: 4.1.1 resolution: "object-assign@npm:4.1.1" checksum: 10/fcc6e4ea8c7fe48abfbb552578b1c53e0d194086e2e6bbbf59e0a536381a292f39943c6e9628af05b5528aa5e3318bb30d6b2e53cadaf5b8fe9e12c4b69af23f @@ -14743,6 +15388,22 @@ __metadata: languageName: node linkType: hard +"on-finished@npm:2.4.1": + version: 2.4.1 + resolution: "on-finished@npm:2.4.1" + dependencies: + ee-first: "npm:1.1.1" + checksum: 10/8e81472c5028125c8c39044ac4ab8ba51a7cdc19a9fbd4710f5d524a74c6d8c9ded4dd0eed83f28d3d33ac1d7a6a439ba948ccb765ac6ce87f30450a26bfe2ea + languageName: node + linkType: hard + +"on-headers@npm:1.0.1": + version: 1.0.1 + resolution: "on-headers@npm:1.0.1" + checksum: 10/7e5dc811cd8e16590385ac56a63e27aa9006c594069960655d37f96c9b1f7af4ce7e71f4f6a771ed746ebf180e0c1cbc1e5ecd125a4006b9eb0ef23125068cd2 + languageName: node + linkType: hard + "once@npm:^1.3.0, once@npm:^1.3.1, once@npm:^1.4.0": version: 1.4.0 resolution: "once@npm:1.4.0" @@ -14752,6 +15413,15 @@ __metadata: languageName: node linkType: hard +"one-time@npm:^1.0.0": + version: 1.0.0 + resolution: "one-time@npm:1.0.0" + dependencies: + fn.name: "npm:1.x.x" + checksum: 10/64d0160480eeae4e3b2a6fc0a02f452e05bb0cc8373a4ed56a4fc08c3939dcb91bc20075003ed499655bd16919feb63ca56f86eee7932c5251f7d629b55dfc90 + languageName: node + linkType: hard + "onetime@npm:^5.1.0, onetime@npm:^5.1.2": version: 5.1.2 resolution: "onetime@npm:5.1.2" @@ -15069,6 +15739,13 @@ __metadata: languageName: node linkType: hard +"parseurl@npm:~1.3.3": + version: 1.3.3 + resolution: "parseurl@npm:1.3.3" + checksum: 10/407cee8e0a3a4c5cd472559bca8b6a45b82c124e9a4703302326e9ab60fc1081442ada4e02628efef1eb16197ddc7f8822f5a91fd7d7c86b51f530aedb17dfa2 + languageName: node + linkType: hard + "path-browserify@npm:^1.0.1": version: 1.0.1 resolution: "path-browserify@npm:1.0.1" @@ -15142,6 +15819,13 @@ __metadata: languageName: node linkType: hard +"path-to-regexp@npm:0.1.10": + version: 0.1.10 + resolution: "path-to-regexp@npm:0.1.10" + checksum: 10/894e31f1b20e592732a87db61fff5b95c892a3fe430f9ab18455ebe69ee88ef86f8eb49912e261f9926fc53da9f93b46521523e33aefd9cb0a7b0d85d7096006 + languageName: node + linkType: hard + "path-type@npm:^2.0.0": version: 2.0.0 resolution: "path-type@npm:2.0.0" @@ -15539,6 +16223,15 @@ __metadata: languageName: node linkType: hard +"properties-reader@npm:^2.2.0": + version: 2.3.0 + resolution: "properties-reader@npm:2.3.0" + dependencies: + mkdirp: "npm:^1.0.4" + checksum: 10/0b41eb4136dc278ae0d97968ccce8de2d48d321655b319192e31f2424f1c6e052182204671e65aa8967216360cb3e7cbd9129830062e058fe9d6a1d74964c29a + languageName: node + linkType: hard + "property-information@npm:^6.0.0": version: 6.2.0 resolution: "property-information@npm:6.2.0" @@ -15553,6 +16246,23 @@ __metadata: languageName: node linkType: hard +"proxy-addr@npm:~2.0.7": + version: 2.0.7 + resolution: "proxy-addr@npm:2.0.7" + dependencies: + forwarded: "npm:0.2.0" + ipaddr.js: "npm:1.9.1" + checksum: 10/f24a0c80af0e75d31e3451398670d73406ec642914da11a2965b80b1898ca6f66a0e3e091a11a4327079b2b268795f6fa06691923fef91887215c3d0e8ea3f68 + languageName: node + linkType: hard + +"proxy-from-env@npm:^1.1.0": + version: 1.1.0 + resolution: "proxy-from-env@npm:1.1.0" + checksum: 10/f0bb4a87cfd18f77bc2fba23ae49c3b378fb35143af16cc478171c623eebe181678f09439707ad80081d340d1593cd54a33a0113f3ccb3f4bc9451488780ee23 + languageName: node + linkType: hard + "pseudomap@npm:^1.0.2": version: 1.0.2 resolution: "pseudomap@npm:1.0.2" @@ -15591,6 +16301,24 @@ __metadata: languageName: node linkType: hard +"qs@npm:6.11.0": + version: 6.11.0 + resolution: "qs@npm:6.11.0" + dependencies: + side-channel: "npm:^1.0.4" + checksum: 10/5a3bfea3e2f359ede1bfa5d2f0dbe54001aa55e40e27dc3e60fab814362d83a9b30758db057c2011b6f53a2d4e4e5150194b5bac45372652aecb3e3c0d4b256e + languageName: node + linkType: hard + +"qs@npm:6.13.0": + version: 6.13.0 + resolution: "qs@npm:6.13.0" + dependencies: + side-channel: "npm:^1.0.6" + checksum: 10/f548b376e685553d12e461409f0d6e5c59ec7c7d76f308e2a888fd9db3e0c5e89902bedd0754db3a9038eda5f27da2331a6f019c8517dc5e0a16b3c9a6e9cef8 + languageName: node + linkType: hard + "querystringify@npm:^2.1.1": version: 2.2.0 resolution: "querystringify@npm:2.2.0" @@ -15646,6 +16374,25 @@ __metadata: languageName: node linkType: hard +"range-parser@npm:~1.2.1": + version: 1.2.1 + resolution: "range-parser@npm:1.2.1" + checksum: 10/ce21ef2a2dd40506893157970dc76e835c78cf56437e26e19189c48d5291e7279314477b06ac38abd6a401b661a6840f7b03bd0b1249da9b691deeaa15872c26 + languageName: node + linkType: hard + +"raw-body@npm:2.5.2": + version: 2.5.2 + resolution: "raw-body@npm:2.5.2" + dependencies: + bytes: "npm:3.1.2" + http-errors: "npm:2.0.0" + iconv-lite: "npm:0.4.24" + unpipe: "npm:1.0.0" + checksum: 10/863b5171e140546a4d99f349b720abac4410338e23df5e409cfcc3752538c9caf947ce382c89129ba976f71894bd38b5806c774edac35ebf168d02aa1ac11a95 + languageName: node + linkType: hard + "rc4@npm:~0.1.5": version: 0.1.5 resolution: "rc4@npm:0.1.5" @@ -16196,6 +16943,19 @@ __metadata: languageName: node linkType: hard +"readable-stream@npm:^4.5.2": + version: 4.5.2 + resolution: "readable-stream@npm:4.5.2" + dependencies: + abort-controller: "npm:^3.0.0" + buffer: "npm:^6.0.3" + events: "npm:^3.3.0" + process: "npm:^0.11.10" + string_decoder: "npm:^1.3.0" + checksum: 10/01b128a559c5fd76a898495f858cf0a8839f135e6a69e3409f986e88460134791657eb46a2ff16826f331682a3c4d0c5a75cef5e52ef259711021ba52b1c2e82 + languageName: node + linkType: hard + "readable-stream@npm:~1.0.31": version: 1.0.34 resolution: "readable-stream@npm:1.0.34" @@ -16814,6 +17574,13 @@ __metadata: languageName: node linkType: hard +"safe-stable-stringify@npm:^2.3.1": + version: 2.5.0 + resolution: "safe-stable-stringify@npm:2.5.0" + checksum: 10/2697fa186c17c38c3ca5309637b4ac6de2f1c3d282da27cd5e1e3c88eca0fb1f9aea568a6aabdf284111592c8782b94ee07176f17126031be72ab1313ed46c5c + languageName: node + linkType: hard + "safer-buffer@npm:>= 2.1.2 < 3, safer-buffer@npm:>= 2.1.2 < 3.0.0": version: 2.1.2 resolution: "safer-buffer@npm:2.1.2" @@ -16925,6 +17692,48 @@ __metadata: languageName: node linkType: hard +"send@npm:0.18.0": + version: 0.18.0 + resolution: "send@npm:0.18.0" + dependencies: + debug: "npm:2.6.9" + depd: "npm:2.0.0" + destroy: "npm:1.2.0" + encodeurl: "npm:~1.0.2" + escape-html: "npm:~1.0.3" + etag: "npm:~1.8.1" + fresh: "npm:0.5.2" + http-errors: "npm:2.0.0" + mime: "npm:1.6.0" + ms: "npm:2.1.3" + on-finished: "npm:2.4.1" + range-parser: "npm:~1.2.1" + statuses: "npm:2.0.1" + checksum: 10/ec66c0ad109680ad8141d507677cfd8b4e40b9559de23191871803ed241718e99026faa46c398dcfb9250676076573bd6bfe5d0ec347f88f4b7b8533d1d391cb + languageName: node + linkType: hard + +"send@npm:0.19.0": + version: 0.19.0 + resolution: "send@npm:0.19.0" + dependencies: + debug: "npm:2.6.9" + depd: "npm:2.0.0" + destroy: "npm:1.2.0" + encodeurl: "npm:~1.0.2" + escape-html: "npm:~1.0.3" + etag: "npm:~1.8.1" + fresh: "npm:0.5.2" + http-errors: "npm:2.0.0" + mime: "npm:1.6.0" + ms: "npm:2.1.3" + on-finished: "npm:2.4.1" + range-parser: "npm:~1.2.1" + statuses: "npm:2.0.1" + checksum: 10/1f6064dea0ae4cbe4878437aedc9270c33f2a6650a77b56a16b62d057527f2766d96ee282997dd53ec0339082f2aad935bc7d989b46b48c82fc610800dc3a1d0 + languageName: node + linkType: hard + "serialize-error@npm:^7.0.1": version: 7.0.1 resolution: "serialize-error@npm:7.0.1" @@ -16943,6 +17752,18 @@ __metadata: languageName: node linkType: hard +"serve-static@npm:1.16.0": + version: 1.16.0 + resolution: "serve-static@npm:1.16.0" + dependencies: + encodeurl: "npm:~1.0.2" + escape-html: "npm:~1.0.3" + parseurl: "npm:~1.3.3" + send: "npm:0.18.0" + checksum: 10/29a01f67e8c64a359d49dd0c46bc95bb4aa99781f97845dccbf0c8cd0284c5fd79ad7fb9433a36fac4b6c58b577d3eab314a379142412413b8b5cd73be3cd551 + languageName: node + linkType: hard + "set-blocking@npm:^2.0.0": version: 2.0.0 resolution: "set-blocking@npm:2.0.0" @@ -16983,6 +17804,13 @@ __metadata: languageName: node linkType: hard +"setprototypeof@npm:1.2.0": + version: 1.2.0 + resolution: "setprototypeof@npm:1.2.0" + checksum: 10/fde1630422502fbbc19e6844346778f99d449986b2f9cdcceb8326730d2f3d9964dbcb03c02aaadaefffecd0f2c063315ebea8b3ad895914bf1afc1747fc172e + languageName: node + linkType: hard + "shallow-clone@npm:^3.0.0": version: 3.0.1 resolution: "shallow-clone@npm:3.0.1" @@ -17082,6 +17910,15 @@ __metadata: languageName: node linkType: hard +"simple-swizzle@npm:^0.2.2": + version: 0.2.2 + resolution: "simple-swizzle@npm:0.2.2" + dependencies: + is-arrayish: "npm:^0.3.1" + checksum: 10/c6dffff17aaa383dae7e5c056fbf10cf9855a9f79949f20ee225c04f06ddde56323600e0f3d6797e82d08d006e93761122527438ee9531620031c08c9e0d73cc + languageName: node + linkType: hard + "simple-update-notifier@npm:2.0.0": version: 2.0.0 resolution: "simple-update-notifier@npm:2.0.0" @@ -17154,6 +17991,15 @@ __metadata: languageName: node linkType: hard +"slug@npm:^8.2.2": + version: 8.2.3 + resolution: "slug@npm:8.2.3" + bin: + slug: cli.js + checksum: 10/341e87b07fd89e2947cb7016e02fdf7a2280536b88715ba5318aa18dc4bfb18052037da867c512999d8cd7b14a50743c78f6604ae2d4aa9d3b0f289043ed5c3c + languageName: node + linkType: hard + "smart-buffer@npm:^4.0.2, smart-buffer@npm:^4.2.0": version: 4.2.0 resolution: "smart-buffer@npm:4.2.0" @@ -17366,6 +18212,13 @@ __metadata: languageName: node linkType: hard +"stack-trace@npm:0.0.x": + version: 0.0.10 + resolution: "stack-trace@npm:0.0.10" + checksum: 10/7bd633f0e9ac46e81a0b0fe6538482c1d77031959cf94478228731709db4672fbbed59176f5b9a9fd89fec656b5dae03d084ef2d1b0c4c2f5683e05f2dbb1405 + languageName: node + linkType: hard + "stack-utils@npm:^2.0.3": version: 2.0.6 resolution: "stack-utils@npm:2.0.6" @@ -17389,6 +18242,13 @@ __metadata: languageName: node linkType: hard +"statuses@npm:2.0.1": + version: 2.0.1 + resolution: "statuses@npm:2.0.1" + checksum: 10/18c7623fdb8f646fb213ca4051be4df7efb3484d4ab662937ca6fbef7ced9b9e12842709872eb3020cc3504b93bde88935c9f6417489627a7786f24f8031cbcb + languageName: node + linkType: hard + "std-env@npm:^3.5.0": version: 3.6.0 resolution: "std-env@npm:3.6.0" @@ -17605,7 +18465,7 @@ __metadata: languageName: node linkType: hard -"string_decoder@npm:^1.1.1": +"string_decoder@npm:^1.1.1, string_decoder@npm:^1.3.0": version: 1.3.0 resolution: "string_decoder@npm:1.3.0" dependencies: @@ -18073,6 +18933,13 @@ __metadata: languageName: node linkType: hard +"text-hex@npm:1.0.x": + version: 1.0.0 + resolution: "text-hex@npm:1.0.0" + checksum: 10/1138f68adc97bf4381a302a24e2352f04992b7b1316c5003767e9b0d3367ffd0dc73d65001ea02b07cd0ecc2a9d186de0cf02f3c2d880b8a522d4ccb9342244a + languageName: node + linkType: hard + "text-table@npm:^0.2.0": version: 0.2.0 resolution: "text-table@npm:0.2.0" @@ -18203,6 +19070,13 @@ __metadata: languageName: node linkType: hard +"toidentifier@npm:1.0.1": + version: 1.0.1 + resolution: "toidentifier@npm:1.0.1" + checksum: 10/952c29e2a85d7123239b5cfdd889a0dde47ab0497f0913d70588f19c53f7e0b5327c95f4651e413c74b785147f9637b17410ac8c846d5d4a20a5a33eb6dc3a45 + languageName: node + linkType: hard + "totalist@npm:^3.0.0": version: 3.0.1 resolution: "totalist@npm:3.0.1" @@ -18270,6 +19144,13 @@ __metadata: languageName: node linkType: hard +"triple-beam@npm:^1.3.0": + version: 1.4.1 + resolution: "triple-beam@npm:1.4.1" + checksum: 10/2e881a3e8e076b6f2b85b9ec9dd4a900d3f5016e6d21183ed98e78f9abcc0149e7d54d79a3f432b23afde46b0885bdcdcbff789f39bc75de796316961ec07f61 + languageName: node + linkType: hard + "trough@npm:^2.0.0": version: 2.1.0 resolution: "trough@npm:2.1.0" @@ -18464,6 +19345,16 @@ __metadata: languageName: node linkType: hard +"type-is@npm:~1.6.18": + version: 1.6.18 + resolution: "type-is@npm:1.6.18" + dependencies: + media-typer: "npm:0.3.0" + mime-types: "npm:~2.1.24" + checksum: 10/0bd9eeae5efd27d98fd63519f999908c009e148039d8e7179a074f105362d4fcc214c38b24f6cda79c87e563cbd12083a4691381ed28559220d4a10c2047bed4 + languageName: node + linkType: hard + "typed-array-buffer@npm:^1.0.2": version: 1.0.2 resolution: "typed-array-buffer@npm:1.0.2" @@ -18854,6 +19745,13 @@ __metadata: languageName: node linkType: hard +"unpipe@npm:1.0.0, unpipe@npm:~1.0.0": + version: 1.0.0 + resolution: "unpipe@npm:1.0.0" + checksum: 10/4fa18d8d8d977c55cb09715385c203197105e10a6d220087ec819f50cb68870f02942244f1017565484237f1f8c5d3cd413631b1ae104d3096f24fdfde1b4aa2 + languageName: node + linkType: hard + "untildify@npm:^4.0.0": version: 4.0.0 resolution: "untildify@npm:4.0.0" @@ -18964,6 +19862,13 @@ __metadata: languageName: node linkType: hard +"utils-merge@npm:1.0.1": + version: 1.0.1 + resolution: "utils-merge@npm:1.0.1" + checksum: 10/5d6949693d58cb2e636a84f3ee1c6e7b2f9c16cb1d42d0ecb386d8c025c69e327205aa1c69e2868cc06a01e5e20681fbba55a4e0ed0cce913d60334024eae798 + languageName: node + linkType: hard + "uuid@npm:^3.0.1, uuid@npm:^3.3.2": version: 3.4.0 resolution: "uuid@npm:3.4.0" @@ -18973,7 +19878,7 @@ __metadata: languageName: node linkType: hard -"uuid@npm:^9.0.1": +"uuid@npm:^9.0.0, uuid@npm:^9.0.1": version: 9.0.1 resolution: "uuid@npm:9.0.1" bin: @@ -19031,6 +19936,13 @@ __metadata: languageName: node linkType: hard +"vary@npm:^1, vary@npm:~1.1.2": + version: 1.1.2 + resolution: "vary@npm:1.1.2" + checksum: 10/31389debef15a480849b8331b220782230b9815a8e0dbb7b9a8369559aed2e9a7800cd904d4371ea74f4c3527db456dc8e7ac5befce5f0d289014dbdf47b2242 + languageName: node + linkType: hard + "verror@npm:^1.10.0": version: 1.10.1 resolution: "verror@npm:1.10.1" @@ -19659,7 +20571,7 @@ __metadata: languageName: node linkType: hard -"wide-align@npm:^1.1.5": +"wide-align@npm:^1.1.2, wide-align@npm:^1.1.5": version: 1.1.5 resolution: "wide-align@npm:1.1.5" dependencies: @@ -19675,6 +20587,36 @@ __metadata: languageName: node linkType: hard +"winston-transport@npm:^4.7.0": + version: 4.8.0 + resolution: "winston-transport@npm:4.8.0" + dependencies: + logform: "npm:^2.6.1" + readable-stream: "npm:^4.5.2" + triple-beam: "npm:^1.3.0" + checksum: 10/930bdc0ec689d5c4f07a262721da80440336f64739d0ce33db801c7142b4fca5be8ef71b725b670bac609de8b6bce405e5c5f84d355f5176a611209b476cee18 + languageName: node + linkType: hard + +"winston@npm:^3.14.2": + version: 3.15.0 + resolution: "winston@npm:3.15.0" + dependencies: + "@colors/colors": "npm:^1.6.0" + "@dabh/diagnostics": "npm:^2.0.2" + async: "npm:^3.2.3" + is-stream: "npm:^2.0.0" + logform: "npm:^2.6.0" + one-time: "npm:^1.0.0" + readable-stream: "npm:^3.4.0" + safe-stable-stringify: "npm:^2.3.1" + stack-trace: "npm:0.0.x" + triple-beam: "npm:^1.3.0" + winston-transport: "npm:^4.7.0" + checksum: 10/60e55eb3621e4de1a764a4e43ee1d242c71957d3e0eb359cb8f16fe2b9d9543fd4c31a8d3baf96fa7e43ef5df383c43c1a98aff4bd714ea0082303504b0e3cdc + languageName: node + linkType: hard + "word-wrap@npm:~1.2.3": version: 1.2.3 resolution: "word-wrap@npm:1.2.3" From 7782f2dd6bb074efb4ed69002b9ebcdfafb3811f Mon Sep 17 00:00:00 2001 From: Mike Clark Date: Sat, 12 Oct 2024 13:11:49 +0100 Subject: [PATCH 07/55] ensuring deps are built before running server --- bin/package-electron | 1 + package.json | 1 + 2 files changed, 2 insertions(+) diff --git a/bin/package-electron b/bin/package-electron index b827d0015a0..9f603b94967 100755 --- a/bin/package-electron +++ b/bin/package-electron @@ -37,6 +37,7 @@ fi yarn workspace loot-core build:node yarn workspace @actual-app/web build --mode=desktop +yarn workspace @actual-app/crdt build --mode=desktop yarn workspace desktop-electron update-client diff --git a/package.json b/package.json index 7a673cd67cf..cc58c2d61f9 100644 --- a/package.json +++ b/package.json @@ -21,6 +21,7 @@ "start": "yarn start:browser", "start:desktop": "yarn rebuild-electron && npm-run-all --parallel 'start:desktop-*'", "start:desktop-node": "yarn workspace loot-core watch:node", + "start:desktop-server-deps": "yarn workspace @actual-app/crdt build", "start:desktop-client": "yarn workspace @actual-app/web watch", "start:desktop-electron": "yarn workspace desktop-electron watch", "start:electron": "yarn start:desktop", From 55e91139c8e52b9dd13459149eaf853482d7e9d4 Mon Sep 17 00:00:00 2001 From: Mike Clark Date: Sat, 12 Oct 2024 13:39:32 +0100 Subject: [PATCH 08/55] unneeded mode --- bin/package-electron | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/bin/package-electron b/bin/package-electron index 9f603b94967..e3934ea84d0 100755 --- a/bin/package-electron +++ b/bin/package-electron @@ -37,7 +37,7 @@ fi yarn workspace loot-core build:node yarn workspace @actual-app/web build --mode=desktop -yarn workspace @actual-app/crdt build --mode=desktop +yarn workspace @actual-app/crdt build yarn workspace desktop-electron update-client From 32663197626f010b4f0a5dc2f42b78ab66b54ae3 Mon Sep 17 00:00:00 2001 From: Mike Clark Date: Sun, 13 Oct 2024 22:19:09 +0100 Subject: [PATCH 09/55] exposing sync server via ngrok --- .../src/browser-preload.browser.js | 1 + .../src/components/manager/BudgetList.tsx | 16 +++++- packages/desktop-electron/index.ts | 44 ++++++++++++++-- packages/desktop-electron/package.json | 1 + packages/desktop-electron/preload.ts | 5 ++ packages/loot-core/typings/window.d.ts | 4 ++ yarn.lock | 52 +++++++++++++++++++ 7 files changed, 119 insertions(+), 4 deletions(-) diff --git a/packages/desktop-client/src/browser-preload.browser.js b/packages/desktop-client/src/browser-preload.browser.js index 43a3037cc92..43f4f0c30e1 100644 --- a/packages/desktop-client/src/browser-preload.browser.js +++ b/packages/desktop-client/src/browser-preload.browser.js @@ -141,6 +141,7 @@ global.Actual = { }, downloadActualServer: () => {}, startActualServer: () => {}, + exposeActualServer: () => {}, onEventFromMain: () => {}, applyAppUpdate: () => {}, updateAppMenu: () => {}, diff --git a/packages/desktop-client/src/components/manager/BudgetList.tsx b/packages/desktop-client/src/components/manager/BudgetList.tsx index 2afadfe3c7f..528f5ba11b6 100644 --- a/packages/desktop-client/src/components/manager/BudgetList.tsx +++ b/packages/desktop-client/src/components/manager/BudgetList.tsx @@ -445,6 +445,10 @@ export function BudgetList({ showHeader = true, quickSwitchMode = false }) { await globalThis.Actual.startActualServer('v24.10.1'); }; + const exposeActualServer = async () => { + await globalThis.Actual.exposeActualServer(); + }; + return ( - Start Electron Server + Start Server + + )} diff --git a/packages/desktop-electron/index.ts b/packages/desktop-electron/index.ts index 6b0f81403b1..47ccf1b53c5 100644 --- a/packages/desktop-electron/index.ts +++ b/packages/desktop-electron/index.ts @@ -17,6 +17,7 @@ import { SaveDialogOptions, } from 'electron'; import { copy, exists, remove } from 'fs-extra'; +import ngrok from 'ngrok'; import promiseRetry from 'promise-retry'; import { getMenu } from './menu'; @@ -405,9 +406,9 @@ ipcMain.handle( ipcMain.handle( 'start-actual-server', async (_event, payload: { releaseVersion: string }) => { - const serverReleaseDir = path.resolve( - `${process.env.ACTUAL_DATA_DIR}/actual-server/${payload.releaseVersion}`, - ); + // const serverReleaseDir = path.resolve( + // `${process.env.ACTUAL_DATA_DIR}/actual-server/${payload.releaseVersion}`, + // ); // actualServerProcess = utilityProcess.fork( // serverReleaseDir + '/app.js', // if bundling it ourselves and manually including it @@ -443,6 +444,43 @@ ipcMain.handle( clientWin?.webContents.executeJavaScript(` console.error('Server Log:', ${JSON.stringify(chunk.toString('utf8'))})`); }); + + const url = ngrok.connect({ + proto: 'http', + addr: 5006, + region: 'us', + }); + + return url; + }, +); + +export type ExposeActualServerPayload = { + region: 'us' | 'eu' | 'au' | 'ap' | 'sa' | 'jp' | 'in'; + port: number; +}; + +ipcMain.handle( + 'expose-actual-server', + async ( + _event, + payload: ExposeActualServerPayload = { + region: 'eu', + port: 5006, + }, + ) => { + try { + const url = await ngrok.connect({ + scheme: 'https', + addr: payload.port, + region: payload.region, + }); + + console.info(`Exposing actual server on url: ${url}`); + return url; + } catch (error) { + console.error('Unable to run ngrok', error); + } }, ); diff --git a/packages/desktop-electron/package.json b/packages/desktop-electron/package.json index 6eaeb555318..9622945adc7 100644 --- a/packages/desktop-electron/package.json +++ b/packages/desktop-electron/package.json @@ -79,6 +79,7 @@ "adm-zip": "^0.5.10", "better-sqlite3": "^9.6.0", "fs-extra": "^11.2.0", + "ngrok": "^5.0.0-beta.2", "node-fetch": "^2.7.0", "promise-retry": "^2.0.1" }, diff --git a/packages/desktop-electron/preload.ts b/packages/desktop-electron/preload.ts index 2c65ddff64e..8b53ed31002 100644 --- a/packages/desktop-electron/preload.ts +++ b/packages/desktop-electron/preload.ts @@ -1,6 +1,7 @@ import { ipcRenderer, contextBridge, IpcRenderer } from 'electron'; import { + ExposeActualServerPayload, GetBootstrapDataPayload, OpenFileDialogPayload, SaveFileDialogPayload, @@ -70,6 +71,10 @@ contextBridge.exposeInMainWorld('Actual', { }); }, + exposeActualServer: (settings: ExposeActualServerPayload) => { + return ipcRenderer.invoke('expose-actual-server', settings); + }, + onEventFromMain: (type: string, handler: (...args: unknown[]) => void) => { ipcRenderer.on(type, handler); }, diff --git a/packages/loot-core/typings/window.d.ts b/packages/loot-core/typings/window.d.ts index 805db112ba6..9d95204f18b 100644 --- a/packages/loot-core/typings/window.d.ts +++ b/packages/loot-core/typings/window.d.ts @@ -8,6 +8,10 @@ declare global { openURLInBrowser: (url: string) => void; downloadActualServer: (releaseVersion: string) => Promise; startActualServer: (releaseVersion: string) => Promise; + exposeActualServer: (settings: { + region: 'us' | 'eu' | 'au' | 'ap' | 'sa' | 'jp' | 'in'; + port: number; + }) => Promise; saveFile: ( contents: string | Buffer, filename: string, diff --git a/yarn.lock b/yarn.lock index 439310d4fe2..087745d4df7 100644 --- a/yarn.lock +++ b/yarn.lock @@ -8683,6 +8683,7 @@ __metadata: electron: "npm:30.0.6" electron-builder: "npm:24.13.3" fs-extra: "npm:^11.2.0" + ngrok: "npm:^5.0.0-beta.2" node-fetch: "npm:^2.7.0" promise-retry: "npm:^2.0.1" typescript: "npm:^5.5.4" @@ -11346,6 +11347,13 @@ __metadata: languageName: node linkType: hard +"hpagent@npm:^0.1.2": + version: 0.1.2 + resolution: "hpagent@npm:0.1.2" + checksum: 10/bd033b3700bb523edc9a805f8683c71fddd622df901e73842b5e3357136ce062c2ddb2ab5e9f5b3d84e0977bfe439f5cdc51d755a11e99376eb95e4624312f0a + languageName: node + linkType: hard + "html-encoding-sniffer@npm:^2.0.1": version: 2.0.1 resolution: "html-encoding-sniffer@npm:2.0.1" @@ -13593,6 +13601,13 @@ __metadata: languageName: node linkType: hard +"lodash.clonedeep@npm:^4.5.0": + version: 4.5.0 + resolution: "lodash.clonedeep@npm:4.5.0" + checksum: 10/957ed243f84ba6791d4992d5c222ffffca339a3b79dbe81d2eaf0c90504160b500641c5a0f56e27630030b18b8e971ea10b44f928a977d5ced3c8948841b555f + languageName: node + linkType: hard + "lodash.debounce@npm:^4.0.8": version: 4.0.8 resolution: "lodash.debounce@npm:4.0.8" @@ -14945,6 +14960,25 @@ __metadata: languageName: node linkType: hard +"ngrok@npm:^5.0.0-beta.2": + version: 5.0.0-beta.2 + resolution: "ngrok@npm:5.0.0-beta.2" + dependencies: + extract-zip: "npm:^2.0.1" + got: "npm:^11.8.5" + hpagent: "npm:^0.1.2" + lodash.clonedeep: "npm:^4.5.0" + uuid: "npm:^7.0.0 || ^8.0.0" + yaml: "npm:^2.2.2" + dependenciesMeta: + hpagent: + optional: true + bin: + ngrok: bin/ngrok + checksum: 10/46712d4e59cc8bc9d3bbfa8c327ed12f5ea218991c26fddf22d9c9d4eea59d2caeb6463790a8daceaafe76223c07ecd27297ce7fc31cd88d9cea788945f315b7 + languageName: node + linkType: hard + "nice-try@npm:^1.0.4": version: 1.0.5 resolution: "nice-try@npm:1.0.5" @@ -19878,6 +19912,15 @@ __metadata: languageName: node linkType: hard +"uuid@npm:^7.0.0 || ^8.0.0": + version: 8.3.2 + resolution: "uuid@npm:8.3.2" + bin: + uuid: dist/bin/uuid + checksum: 10/9a5f7aa1d6f56dd1e8d5f2478f855f25c645e64e26e347a98e98d95781d5ed20062d6cca2eecb58ba7c84bc3910be95c0451ef4161906abaab44f9cb68ffbdd1 + languageName: node + linkType: hard + "uuid@npm:^9.0.0, uuid@npm:^9.0.1": version: 9.0.1 resolution: "uuid@npm:9.0.1" @@ -20988,6 +21031,15 @@ __metadata: languageName: node linkType: hard +"yaml@npm:^2.2.2": + version: 2.6.0 + resolution: "yaml@npm:2.6.0" + bin: + yaml: bin.mjs + checksum: 10/f4369f667c7626c216ea81b5840fe9b530cdae4cff2d84d166ec1239e54bf332dbfac4a71bf60d121f8e85e175364a4e280a520292269b6cf9d074368309adf9 + languageName: node + linkType: hard + "yaml@npm:~2.5.0": version: 2.5.0 resolution: "yaml@npm:2.5.0" From b3e6d28136ddfe1e4df1268e3284b1165dddd3a0 Mon Sep 17 00:00:00 2001 From: Mike Clark Date: Sun, 13 Oct 2024 23:07:00 +0100 Subject: [PATCH 10/55] log msg for dev --- packages/desktop-client/src/components/manager/BudgetList.tsx | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/packages/desktop-client/src/components/manager/BudgetList.tsx b/packages/desktop-client/src/components/manager/BudgetList.tsx index 528f5ba11b6..f2759360877 100644 --- a/packages/desktop-client/src/components/manager/BudgetList.tsx +++ b/packages/desktop-client/src/components/manager/BudgetList.tsx @@ -446,7 +446,8 @@ export function BudgetList({ showHeader = true, quickSwitchMode = false }) { }; const exposeActualServer = async () => { - await globalThis.Actual.exposeActualServer(); + const url = await globalThis.Actual.exposeActualServer(); + console.info('exposting actual at: ' + url); }; return ( From ce5225e593d51eccda55c30905238aeca95e37ad Mon Sep 17 00:00:00 2001 From: Mike Clark Date: Mon, 14 Oct 2024 14:13:57 +0100 Subject: [PATCH 11/55] using official ngrok package for smaller size --- .../src/components/manager/BudgetList.tsx | 7 +- packages/desktop-electron/index.ts | 26 ++- packages/desktop-electron/package.json | 2 +- packages/loot-core/src/server/main.ts | 6 + packages/loot-core/src/types/prefs.d.ts | 1 + packages/loot-core/typings/window.d.ts | 2 +- yarn.lock | 192 +++++++++++++----- 7 files changed, 167 insertions(+), 69 deletions(-) diff --git a/packages/desktop-client/src/components/manager/BudgetList.tsx b/packages/desktop-client/src/components/manager/BudgetList.tsx index f2759360877..49707a1936a 100644 --- a/packages/desktop-client/src/components/manager/BudgetList.tsx +++ b/packages/desktop-client/src/components/manager/BudgetList.tsx @@ -23,6 +23,7 @@ import { type SyncedLocalFile, } from 'loot-core/types/file'; +import { useGlobalPref } from '../../hooks/useGlobalPref'; import { useInitialMount } from '../../hooks/useInitialMount'; import { useMetadataPref } from '../../hooks/useMetadataPref'; import { AnimatedLoading } from '../../icons/AnimatedLoading'; @@ -445,8 +446,12 @@ export function BudgetList({ showHeader = true, quickSwitchMode = false }) { await globalThis.Actual.startActualServer('v24.10.1'); }; + const [ngrokAuthToken] = useGlobalPref('ngrokAuthToken'); const exposeActualServer = async () => { - const url = await globalThis.Actual.exposeActualServer(); + const url = await globalThis.Actual.exposeActualServer({ + authToken: ngrokAuthToken, + port: 5006, + }); console.info('exposting actual at: ' + url); }; diff --git a/packages/desktop-electron/index.ts b/packages/desktop-electron/index.ts index 47ccf1b53c5..da824d9615e 100644 --- a/packages/desktop-electron/index.ts +++ b/packages/desktop-electron/index.ts @@ -1,6 +1,7 @@ import fs from 'fs'; import path from 'path'; +import ngrok from '@ngrok/ngrok'; import { net, app, @@ -17,7 +18,6 @@ import { SaveDialogOptions, } from 'electron'; import { copy, exists, remove } from 'fs-extra'; -import ngrok from 'ngrok'; import promiseRetry from 'promise-retry'; import { getMenu } from './menu'; @@ -456,28 +456,26 @@ ipcMain.handle( ); export type ExposeActualServerPayload = { - region: 'us' | 'eu' | 'au' | 'ap' | 'sa' | 'jp' | 'in'; + authToken: string; port: number; }; ipcMain.handle( 'expose-actual-server', - async ( - _event, - payload: ExposeActualServerPayload = { - region: 'eu', - port: 5006, - }, - ) => { + async (_event, payload: ExposeActualServerPayload) => { try { - const url = await ngrok.connect({ - scheme: 'https', + const listener = await ngrok.forward({ + schemes: ['https'], // change this to https and bind certificate - may need to generate cert and store in user-data addr: payload.port, - region: payload.region, + host_header: `localhost:${payload.port}`, + authtoken: payload.authToken, + domain: 'creative-genuinely-meerkat.ngrok-free.app', // should come from settings? + // crt: fs.readFileSync("crt.pem", "utf8"), + // key: fs.readFileSync("key.pem", "utf8"), }); - console.info(`Exposing actual server on url: ${url}`); - return url; + console.info(`Exposing actual server on url: ${listener.url()}`); + return listener.url(); } catch (error) { console.error('Unable to run ngrok', error); } diff --git a/packages/desktop-electron/package.json b/packages/desktop-electron/package.json index 9622945adc7..584cc16a4de 100644 --- a/packages/desktop-electron/package.json +++ b/packages/desktop-electron/package.json @@ -75,11 +75,11 @@ "npmRebuild": false }, "dependencies": { + "@ngrok/ngrok": "^1.4.1", "actual-sync": "file:../../../actual-server", "adm-zip": "^0.5.10", "better-sqlite3": "^9.6.0", "fs-extra": "^11.2.0", - "ngrok": "^5.0.0-beta.2", "node-fetch": "^2.7.0", "promise-retry": "^2.0.1" }, diff --git a/packages/loot-core/src/server/main.ts b/packages/loot-core/src/server/main.ts index a2a547173af..4d8fe958a82 100644 --- a/packages/loot-core/src/server/main.ts +++ b/packages/loot-core/src/server/main.ts @@ -1262,6 +1262,9 @@ handlers['save-global-prefs'] = async function (prefs) { prefs.serverSelfSignedCert, ); } + if ('ngrokAuthToken' in prefs) { + await asyncStorage.setItem('ngrokAuthToken', prefs.ngrokAuthToken); + } return 'ok'; }; @@ -1274,6 +1277,7 @@ handlers['load-global-prefs'] = async function () { [, theme], [, preferredDarkTheme], [, serverSelfSignedCert], + [, ngrokAuthToken], ] = await asyncStorage.multiGet([ 'floating-sidebar', 'max-months', @@ -1282,6 +1286,7 @@ handlers['load-global-prefs'] = async function () { 'theme', 'preferred-dark-theme', 'server-self-signed-cert', + 'ngrokAuthToken', ]); return { floatingSidebar: floatingSidebar === 'true' ? true : false, @@ -1301,6 +1306,7 @@ handlers['load-global-prefs'] = async function () { ? preferredDarkTheme : 'dark', serverSelfSignedCert: serverSelfSignedCert || undefined, + ngrokAuthToken: ngrokAuthToken || undefined, }; }; diff --git a/packages/loot-core/src/types/prefs.d.ts b/packages/loot-core/src/types/prefs.d.ts index 036d609f2d4..05240fc2ca7 100644 --- a/packages/loot-core/src/types/prefs.d.ts +++ b/packages/loot-core/src/types/prefs.d.ts @@ -78,4 +78,5 @@ export type GlobalPrefs = Partial<{ preferredDarkTheme: DarkTheme; documentDir: string; // Electron only serverSelfSignedCert: string; // Electron only + ngrokAuthToken: string; // Electron only }>; diff --git a/packages/loot-core/typings/window.d.ts b/packages/loot-core/typings/window.d.ts index 9d95204f18b..9f039c1972b 100644 --- a/packages/loot-core/typings/window.d.ts +++ b/packages/loot-core/typings/window.d.ts @@ -9,7 +9,7 @@ declare global { downloadActualServer: (releaseVersion: string) => Promise; startActualServer: (releaseVersion: string) => Promise; exposeActualServer: (settings: { - region: 'us' | 'eu' | 'au' | 'ap' | 'sa' | 'jp' | 'in'; + authToken: string; port: number; }) => Promise; saveFile: ( diff --git a/yarn.lock b/yarn.lock index 087745d4df7..79baabcfe72 100644 --- a/yarn.lock +++ b/yarn.lock @@ -2472,6 +2472,145 @@ __metadata: languageName: node linkType: hard +"@ngrok/ngrok-android-arm-eabi@npm:1.4.1": + version: 1.4.1 + resolution: "@ngrok/ngrok-android-arm-eabi@npm:1.4.1" + conditions: os=android & cpu=arm + languageName: node + linkType: hard + +"@ngrok/ngrok-android-arm64@npm:1.4.1": + version: 1.4.1 + resolution: "@ngrok/ngrok-android-arm64@npm:1.4.1" + conditions: os=android & cpu=arm64 + languageName: node + linkType: hard + +"@ngrok/ngrok-darwin-arm64@npm:1.4.1": + version: 1.4.1 + resolution: "@ngrok/ngrok-darwin-arm64@npm:1.4.1" + conditions: os=darwin & cpu=arm64 + languageName: node + linkType: hard + +"@ngrok/ngrok-darwin-universal@npm:1.4.1": + version: 1.4.1 + resolution: "@ngrok/ngrok-darwin-universal@npm:1.4.1" + conditions: os=darwin + languageName: node + linkType: hard + +"@ngrok/ngrok-darwin-x64@npm:1.4.1": + version: 1.4.1 + resolution: "@ngrok/ngrok-darwin-x64@npm:1.4.1" + conditions: os=darwin & cpu=x64 + languageName: node + linkType: hard + +"@ngrok/ngrok-freebsd-x64@npm:1.4.1": + version: 1.4.1 + resolution: "@ngrok/ngrok-freebsd-x64@npm:1.4.1" + conditions: os=freebsd & cpu=x64 + languageName: node + linkType: hard + +"@ngrok/ngrok-linux-arm-gnueabihf@npm:1.4.1": + version: 1.4.1 + resolution: "@ngrok/ngrok-linux-arm-gnueabihf@npm:1.4.1" + conditions: os=linux & cpu=arm + languageName: node + linkType: hard + +"@ngrok/ngrok-linux-arm64-gnu@npm:1.4.1": + version: 1.4.1 + resolution: "@ngrok/ngrok-linux-arm64-gnu@npm:1.4.1" + conditions: os=linux & cpu=arm64 & libc=glibc + languageName: node + linkType: hard + +"@ngrok/ngrok-linux-arm64-musl@npm:1.4.1": + version: 1.4.1 + resolution: "@ngrok/ngrok-linux-arm64-musl@npm:1.4.1" + conditions: os=linux & cpu=arm64 & libc=musl + languageName: node + linkType: hard + +"@ngrok/ngrok-linux-x64-gnu@npm:1.4.1": + version: 1.4.1 + resolution: "@ngrok/ngrok-linux-x64-gnu@npm:1.4.1" + conditions: os=linux & cpu=x64 & libc=glibc + languageName: node + linkType: hard + +"@ngrok/ngrok-linux-x64-musl@npm:1.4.1": + version: 1.4.1 + resolution: "@ngrok/ngrok-linux-x64-musl@npm:1.4.1" + conditions: os=linux & cpu=x64 & libc=musl + languageName: node + linkType: hard + +"@ngrok/ngrok-win32-ia32-msvc@npm:1.4.1": + version: 1.4.1 + resolution: "@ngrok/ngrok-win32-ia32-msvc@npm:1.4.1" + conditions: os=win32 & cpu=ia32 + languageName: node + linkType: hard + +"@ngrok/ngrok-win32-x64-msvc@npm:1.4.1": + version: 1.4.1 + resolution: "@ngrok/ngrok-win32-x64-msvc@npm:1.4.1" + conditions: os=win32 & cpu=x64 + languageName: node + linkType: hard + +"@ngrok/ngrok@npm:^1.4.1": + version: 1.4.1 + resolution: "@ngrok/ngrok@npm:1.4.1" + dependencies: + "@ngrok/ngrok-android-arm-eabi": "npm:1.4.1" + "@ngrok/ngrok-android-arm64": "npm:1.4.1" + "@ngrok/ngrok-darwin-arm64": "npm:1.4.1" + "@ngrok/ngrok-darwin-universal": "npm:1.4.1" + "@ngrok/ngrok-darwin-x64": "npm:1.4.1" + "@ngrok/ngrok-freebsd-x64": "npm:1.4.1" + "@ngrok/ngrok-linux-arm-gnueabihf": "npm:1.4.1" + "@ngrok/ngrok-linux-arm64-gnu": "npm:1.4.1" + "@ngrok/ngrok-linux-arm64-musl": "npm:1.4.1" + "@ngrok/ngrok-linux-x64-gnu": "npm:1.4.1" + "@ngrok/ngrok-linux-x64-musl": "npm:1.4.1" + "@ngrok/ngrok-win32-ia32-msvc": "npm:1.4.1" + "@ngrok/ngrok-win32-x64-msvc": "npm:1.4.1" + dependenciesMeta: + "@ngrok/ngrok-android-arm-eabi": + optional: true + "@ngrok/ngrok-android-arm64": + optional: true + "@ngrok/ngrok-darwin-arm64": + optional: true + "@ngrok/ngrok-darwin-universal": + optional: true + "@ngrok/ngrok-darwin-x64": + optional: true + "@ngrok/ngrok-freebsd-x64": + optional: true + "@ngrok/ngrok-linux-arm-gnueabihf": + optional: true + "@ngrok/ngrok-linux-arm64-gnu": + optional: true + "@ngrok/ngrok-linux-arm64-musl": + optional: true + "@ngrok/ngrok-linux-x64-gnu": + optional: true + "@ngrok/ngrok-linux-x64-musl": + optional: true + "@ngrok/ngrok-win32-ia32-msvc": + optional: true + "@ngrok/ngrok-win32-x64-msvc": + optional: true + checksum: 10/c86956756af6eb9f2cc47aba19ac14f59a0d031defc52ee6845b17363b051213abd29c984f11501d88a482f343fbadb0e7bf855803068bceb96bf5fb16dfb2cb + languageName: node + linkType: hard + "@nodelib/fs.scandir@npm:2.1.5": version: 2.1.5 resolution: "@nodelib/fs.scandir@npm:2.1.5" @@ -8673,6 +8812,7 @@ __metadata: dependencies: "@electron/notarize": "npm:2.4.0" "@electron/rebuild": "npm:3.6.0" + "@ngrok/ngrok": "npm:^1.4.1" "@types/copyfiles": "npm:^2" "@types/fs-extra": "npm:^11" actual-sync: "file:../../../actual-server" @@ -8683,7 +8823,6 @@ __metadata: electron: "npm:30.0.6" electron-builder: "npm:24.13.3" fs-extra: "npm:^11.2.0" - ngrok: "npm:^5.0.0-beta.2" node-fetch: "npm:^2.7.0" promise-retry: "npm:^2.0.1" typescript: "npm:^5.5.4" @@ -11347,13 +11486,6 @@ __metadata: languageName: node linkType: hard -"hpagent@npm:^0.1.2": - version: 0.1.2 - resolution: "hpagent@npm:0.1.2" - checksum: 10/bd033b3700bb523edc9a805f8683c71fddd622df901e73842b5e3357136ce062c2ddb2ab5e9f5b3d84e0977bfe439f5cdc51d755a11e99376eb95e4624312f0a - languageName: node - linkType: hard - "html-encoding-sniffer@npm:^2.0.1": version: 2.0.1 resolution: "html-encoding-sniffer@npm:2.0.1" @@ -13601,13 +13733,6 @@ __metadata: languageName: node linkType: hard -"lodash.clonedeep@npm:^4.5.0": - version: 4.5.0 - resolution: "lodash.clonedeep@npm:4.5.0" - checksum: 10/957ed243f84ba6791d4992d5c222ffffca339a3b79dbe81d2eaf0c90504160b500641c5a0f56e27630030b18b8e971ea10b44f928a977d5ced3c8948841b555f - languageName: node - linkType: hard - "lodash.debounce@npm:^4.0.8": version: 4.0.8 resolution: "lodash.debounce@npm:4.0.8" @@ -14960,25 +15085,6 @@ __metadata: languageName: node linkType: hard -"ngrok@npm:^5.0.0-beta.2": - version: 5.0.0-beta.2 - resolution: "ngrok@npm:5.0.0-beta.2" - dependencies: - extract-zip: "npm:^2.0.1" - got: "npm:^11.8.5" - hpagent: "npm:^0.1.2" - lodash.clonedeep: "npm:^4.5.0" - uuid: "npm:^7.0.0 || ^8.0.0" - yaml: "npm:^2.2.2" - dependenciesMeta: - hpagent: - optional: true - bin: - ngrok: bin/ngrok - checksum: 10/46712d4e59cc8bc9d3bbfa8c327ed12f5ea218991c26fddf22d9c9d4eea59d2caeb6463790a8daceaafe76223c07ecd27297ce7fc31cd88d9cea788945f315b7 - languageName: node - linkType: hard - "nice-try@npm:^1.0.4": version: 1.0.5 resolution: "nice-try@npm:1.0.5" @@ -19912,15 +20018,6 @@ __metadata: languageName: node linkType: hard -"uuid@npm:^7.0.0 || ^8.0.0": - version: 8.3.2 - resolution: "uuid@npm:8.3.2" - bin: - uuid: dist/bin/uuid - checksum: 10/9a5f7aa1d6f56dd1e8d5f2478f855f25c645e64e26e347a98e98d95781d5ed20062d6cca2eecb58ba7c84bc3910be95c0451ef4161906abaab44f9cb68ffbdd1 - languageName: node - linkType: hard - "uuid@npm:^9.0.0, uuid@npm:^9.0.1": version: 9.0.1 resolution: "uuid@npm:9.0.1" @@ -21031,15 +21128,6 @@ __metadata: languageName: node linkType: hard -"yaml@npm:^2.2.2": - version: 2.6.0 - resolution: "yaml@npm:2.6.0" - bin: - yaml: bin.mjs - checksum: 10/f4369f667c7626c216ea81b5840fe9b530cdae4cff2d84d166ec1239e54bf332dbfac4a71bf60d121f8e85e175364a4e280a520292269b6cf9d074368309adf9 - languageName: node - linkType: hard - "yaml@npm:~2.5.0": version: 2.5.0 resolution: "yaml@npm:2.5.0" From 07e7730aed87a4c52016f66f84ce2ad3b6b69ff0 Mon Sep 17 00:00:00 2001 From: Mike Clark Date: Mon, 14 Oct 2024 14:17:25 +0100 Subject: [PATCH 12/55] comment --- packages/desktop-electron/index.ts | 1 + 1 file changed, 1 insertion(+) diff --git a/packages/desktop-electron/index.ts b/packages/desktop-electron/index.ts index da824d9615e..33ea0e857ed 100644 --- a/packages/desktop-electron/index.ts +++ b/packages/desktop-electron/index.ts @@ -464,6 +464,7 @@ ipcMain.handle( 'expose-actual-server', async (_event, payload: ExposeActualServerPayload) => { try { + // TODO: check global setting for ServerRunning, if true, autostart the server and expose it - probably done inside of initApp (main.ts) const listener = await ngrok.forward({ schemes: ['https'], // change this to https and bind certificate - may need to generate cert and store in user-data addr: payload.port, From b9c14dc82424a8f56b120a1eccbd545fc03a05c9 Mon Sep 17 00:00:00 2001 From: Mike Clark Date: Mon, 14 Oct 2024 20:39:03 +0100 Subject: [PATCH 13/55] ngrok bits --- .../src/components/manager/BudgetList.tsx | 9 +- packages/desktop-electron/index.ts | 189 ++++++++---------- packages/desktop-electron/package.json | 1 - packages/desktop-electron/preload.ts | 11 +- .../platform/server/asyncStorage/index.d.ts | 4 +- packages/loot-core/src/server/main.ts | 10 +- packages/loot-core/src/types/prefs.d.ts | 8 +- packages/loot-core/typings/window.d.ts | 9 +- 8 files changed, 113 insertions(+), 128 deletions(-) diff --git a/packages/desktop-client/src/components/manager/BudgetList.tsx b/packages/desktop-client/src/components/manager/BudgetList.tsx index 49707a1936a..f4307bc944d 100644 --- a/packages/desktop-client/src/components/manager/BudgetList.tsx +++ b/packages/desktop-client/src/components/manager/BudgetList.tsx @@ -446,13 +446,14 @@ export function BudgetList({ showHeader = true, quickSwitchMode = false }) { await globalThis.Actual.startActualServer('v24.10.1'); }; - const [ngrokAuthToken] = useGlobalPref('ngrokAuthToken'); + const [ngrokConfig] = useGlobalPref('ngrokConfig'); const exposeActualServer = async () => { const url = await globalThis.Actual.exposeActualServer({ - authToken: ngrokAuthToken, - port: 5006, + authToken: ngrokConfig.authToken, + port: ngrokConfig.port, + domain: ngrokConfig.domain, }); - console.info('exposting actual at: ' + url); + console.info('exposing actual at: ' + url); }; return ( diff --git a/packages/desktop-electron/index.ts b/packages/desktop-electron/index.ts index 33ea0e857ed..8afd951a96e 100644 --- a/packages/desktop-electron/index.ts +++ b/packages/desktop-electron/index.ts @@ -20,6 +20,8 @@ import { import { copy, exists, remove } from 'fs-extra'; import promiseRetry from 'promise-retry'; +import { GlobalPrefs } from 'loot-core/types/prefs'; + import { getMenu } from './menu'; import { get as getWindowState, @@ -27,7 +29,6 @@ import { } from './window-state'; import './security'; -import AdmZip from 'adm-zip'; const isDev = !app.isPackaged; // dev mode if not packaged @@ -54,11 +55,28 @@ if (!isDev || !process.env.ACTUAL_DATA_DIR) { let clientWin: BrowserWindow | null; let serverProcess: UtilityProcess | null; let actualServerProcess: UtilityProcess | null; +let globalPrefs: Partial | null; if (isDev) { process.traceProcessWarnings = true; } +async function loadGlobalPrefs() { + let state: GlobalPrefs | undefined = undefined; + try { + state = JSON.parse( + fs.readFileSync( + path.join(process.env.ACTUAL_DATA_DIR, 'global-store.json'), + 'utf8', + ), + ); + } catch (e) { + console.log('Could not load global state'); + } + + return state; +} + function createBackgroundProcess() { serverProcess = utilityProcess.fork( __dirname + '/server.js', @@ -96,6 +114,62 @@ function createBackgroundProcess() { }); } +function startSyncServer() { + const serverPath = path.resolve( + __dirname, + '../../../node_modules/actual-sync/app.js', // if letting electron-builder bundle it (needs to be in our workspace) + ); + + // NOTE: config.json parameters will be relative to THIS directory at the moment - may need a fix? + // Or we can override the config.json location when starting the process + try { + actualServerProcess = utilityProcess.fork( + serverPath, // This requires actual-server depencies (crdt) to be built before running electron - they need to be manually specified because actual-server doesn't get bundled + [], + isDev ? { execArgv: ['--inspect'], stdio: 'pipe' } : { stdio: 'pipe' }, + ); + } catch (error) { + console.error(error); + } + + actualServerProcess.stdout?.on('data', (chunk: Buffer) => { + // Send the Server console.log messages to the main browser window + clientWin?.webContents.executeJavaScript(` + console.info('Server Log:', ${JSON.stringify(chunk.toString('utf8'))})`); + }); + + actualServerProcess.stderr?.on('data', (chunk: Buffer) => { + // Send the Server console.error messages out to the main browser window + clientWin?.webContents.executeJavaScript(` + console.error('Server Log:', ${JSON.stringify(chunk.toString('utf8'))})`); + }); +} + +async function exposeSyncServer(settings: GlobalPrefs['ngrokConfig']) { + if (settings?.authToken && settings?.domain && settings?.port) { + console.error('Cannot expose sync server: missing ngrok settings'); + return { error: 'Missing ngrok settings' }; + } + + try { + const listener = await ngrok.forward({ + schemes: ['https'], // change this to https and bind certificate - may need to generate cert and store in user-data + addr: settings.port, + host_header: `localhost:${settings.port}`, + authtoken: settings.authToken, + domain: settings.domain, + // crt: fs.readFileSync("crt.pem", "utf8"), + // key: fs.readFileSync("key.pem", "utf8"), + }); + + console.info(`Exposing actual server on url: ${listener.url()}`); + return { url: listener.url() }; + } catch (error) { + console.error('Unable to run ngrok', error); + return { error: `Unable to run ngrok. ${error}` }; + } +} + async function createWindow() { const windowState = await getWindowState(); @@ -226,6 +300,14 @@ app.on('ready', async () => { // Install an `app://` protocol that always returns the base HTML // file no matter what URL it is. This allows us to use react-router // on the frontend + + globalPrefs = await loadGlobalPrefs(); // load global prefs + + if (globalPrefs.ngrokConfig.autoStart) { + startSyncServer(); + exposeSyncServer(globalPrefs.ngrokConfig); + } + protocol.handle('app', request => { if (request.method !== 'GET') { return new Response(null, { @@ -376,111 +458,12 @@ ipcMain.handle('open-external-url', (event, url) => { shell.openExternal(url); }); -// NOTE: We could just bundle it in the package, but it would be a large download - consider it though... -ipcMain.handle( - 'download-actual-server', - async (_event, payload: { releaseVersion: string }) => { - console.info({ payload }); - const downloadUrl = `https://github.com/MikesGlitch/actual-server/releases/download/${payload.releaseVersion}/${payload.releaseVersion}-server-sync-dist.zip`; - - try { - const res = await fetch(downloadUrl); - const arrBuffer = await res.arrayBuffer(); - const zipped = new AdmZip(Buffer.from(arrBuffer)); - console.info( - 'actual-server will be installed here:', - process.env.ACTUAL_DATA_DIR, - ); - zipped.extractAllTo( - process.env.ACTUAL_DATA_DIR + '/actual-server-releases', - true, - false, - ); - } catch (error) { - console.error('Error retrieving actual-server:', error); - throw error; - } - }, -); - -ipcMain.handle( - 'start-actual-server', - async (_event, payload: { releaseVersion: string }) => { - // const serverReleaseDir = path.resolve( - // `${process.env.ACTUAL_DATA_DIR}/actual-server/${payload.releaseVersion}`, - // ); - - // actualServerProcess = utilityProcess.fork( - // serverReleaseDir + '/app.js', // if bundling it ourselves and manually including it - // ['--subprocess'], - // isDev ? { execArgv: ['--inspect'], stdio: 'pipe' } : { stdio: 'pipe' }, - // ); - - const serverPath = path.resolve( - __dirname, - '../../../node_modules/actual-sync/app.js', // if letting electron-builder bundle it (needs to be in our workspace) - ); - - // NOTE: config.json parameters will be relative to THIS directory at the moment - may need a fix? - // Or we can override the config.json location when starting the process - try { - actualServerProcess = utilityProcess.fork( - serverPath, // This requires actual-server depencies (crdt) to be built before running electron - they need to be manually specified because actual-server doesn't get bundled - [], - isDev ? { execArgv: ['--inspect'], stdio: 'pipe' } : { stdio: 'pipe' }, - ); - } catch (error) { - console.error(error); - } - - actualServerProcess.stdout?.on('data', (chunk: Buffer) => { - // Send the Server console.log messages to the main browser window - clientWin?.webContents.executeJavaScript(` - console.info('Server Log:', ${JSON.stringify(chunk.toString('utf8'))})`); - }); - - actualServerProcess.stderr?.on('data', (chunk: Buffer) => { - // Send the Server console.error messages out to the main browser window - clientWin?.webContents.executeJavaScript(` - console.error('Server Log:', ${JSON.stringify(chunk.toString('utf8'))})`); - }); - - const url = ngrok.connect({ - proto: 'http', - addr: 5006, - region: 'us', - }); - - return url; - }, -); - -export type ExposeActualServerPayload = { - authToken: string; - port: number; -}; +ipcMain.handle('start-actual-server', async () => startSyncServer()); ipcMain.handle( 'expose-actual-server', - async (_event, payload: ExposeActualServerPayload) => { - try { - // TODO: check global setting for ServerRunning, if true, autostart the server and expose it - probably done inside of initApp (main.ts) - const listener = await ngrok.forward({ - schemes: ['https'], // change this to https and bind certificate - may need to generate cert and store in user-data - addr: payload.port, - host_header: `localhost:${payload.port}`, - authtoken: payload.authToken, - domain: 'creative-genuinely-meerkat.ngrok-free.app', // should come from settings? - // crt: fs.readFileSync("crt.pem", "utf8"), - // key: fs.readFileSync("key.pem", "utf8"), - }); - - console.info(`Exposing actual server on url: ${listener.url()}`); - return listener.url(); - } catch (error) { - console.error('Unable to run ngrok', error); - } - }, + async (_event, payload: GlobalPrefs['ngrokConfig']) => + exposeSyncServer(payload), ); ipcMain.on('message', (_event, msg) => { diff --git a/packages/desktop-electron/package.json b/packages/desktop-electron/package.json index 584cc16a4de..bd98e77ba5e 100644 --- a/packages/desktop-electron/package.json +++ b/packages/desktop-electron/package.json @@ -77,7 +77,6 @@ "dependencies": { "@ngrok/ngrok": "^1.4.1", "actual-sync": "file:../../../actual-server", - "adm-zip": "^0.5.10", "better-sqlite3": "^9.6.0", "fs-extra": "^11.2.0", "node-fetch": "^2.7.0", diff --git a/packages/desktop-electron/preload.ts b/packages/desktop-electron/preload.ts index 8b53ed31002..30316bbdce7 100644 --- a/packages/desktop-electron/preload.ts +++ b/packages/desktop-electron/preload.ts @@ -1,7 +1,8 @@ import { ipcRenderer, contextBridge, IpcRenderer } from 'electron'; +import { GlobalPrefs } from 'loot-core/types/prefs'; + import { - ExposeActualServerPayload, GetBootstrapDataPayload, OpenFileDialogPayload, SaveFileDialogPayload, @@ -59,19 +60,13 @@ contextBridge.exposeInMainWorld('Actual', { ipcRenderer.invoke('open-external-url', url); }, - downloadActualServer: (releaseVersion: string) => { - return ipcRenderer.invoke('download-actual-server', { - releaseVersion, - }); - }, - startActualServer: (releaseVersion: string) => { return ipcRenderer.invoke('start-actual-server', { releaseVersion, }); }, - exposeActualServer: (settings: ExposeActualServerPayload) => { + exposeActualServer: (settings: GlobalPrefs['ngrokConfig']) => { return ipcRenderer.invoke('expose-actual-server', settings); }, diff --git a/packages/loot-core/src/platform/server/asyncStorage/index.d.ts b/packages/loot-core/src/platform/server/asyncStorage/index.d.ts index 4a189f3a86e..6cd9ac22aa2 100644 --- a/packages/loot-core/src/platform/server/asyncStorage/index.d.ts +++ b/packages/loot-core/src/platform/server/asyncStorage/index.d.ts @@ -1,7 +1,7 @@ export function init(opts?: { persist?: boolean }): void; export type Init = typeof init; -export function getItem(key: string): Promise; +export function getItem(key: string): Promise; export type GetItem = typeof getItem; export function setItem(key: string, value: unknown): void; @@ -10,7 +10,7 @@ export type SetItem = typeof setItem; export function removeItem(key: string): void; export type RemoveItem = typeof removeItem; -export function multiGet(keys: string[]): Promise<[string, string][]>; +export function multiGet(keys: string[]): Promise<[string, unknown][]>; export type MultiGet = typeof multiGet; export function multiSet(keyValues: [string, unknown][]): void; diff --git a/packages/loot-core/src/server/main.ts b/packages/loot-core/src/server/main.ts index 4d8fe958a82..958e9d5cc03 100644 --- a/packages/loot-core/src/server/main.ts +++ b/packages/loot-core/src/server/main.ts @@ -1262,8 +1262,8 @@ handlers['save-global-prefs'] = async function (prefs) { prefs.serverSelfSignedCert, ); } - if ('ngrokAuthToken' in prefs) { - await asyncStorage.setItem('ngrokAuthToken', prefs.ngrokAuthToken); + if ('ngrokConfig' in prefs) { + await asyncStorage.setItem('ngrokConfig', prefs.ngrokConfig); } return 'ok'; }; @@ -1277,7 +1277,7 @@ handlers['load-global-prefs'] = async function () { [, theme], [, preferredDarkTheme], [, serverSelfSignedCert], - [, ngrokAuthToken], + [, ngrokConfig], ] = await asyncStorage.multiGet([ 'floating-sidebar', 'max-months', @@ -1286,7 +1286,7 @@ handlers['load-global-prefs'] = async function () { 'theme', 'preferred-dark-theme', 'server-self-signed-cert', - 'ngrokAuthToken', + 'ngrokConfig', ]); return { floatingSidebar: floatingSidebar === 'true' ? true : false, @@ -1306,7 +1306,7 @@ handlers['load-global-prefs'] = async function () { ? preferredDarkTheme : 'dark', serverSelfSignedCert: serverSelfSignedCert || undefined, - ngrokAuthToken: ngrokAuthToken || undefined, + ngrokConfig: ngrokConfig || undefined, }; }; diff --git a/packages/loot-core/src/types/prefs.d.ts b/packages/loot-core/src/types/prefs.d.ts index 05240fc2ca7..01dc865718f 100644 --- a/packages/loot-core/src/types/prefs.d.ts +++ b/packages/loot-core/src/types/prefs.d.ts @@ -78,5 +78,11 @@ export type GlobalPrefs = Partial<{ preferredDarkTheme: DarkTheme; documentDir: string; // Electron only serverSelfSignedCert: string; // Electron only - ngrokAuthToken: string; // Electron only + ngrokConfig?: { + // Electron only + autoStart?: boolean; + authToken?: string; + port?: number; + domain?: string; + }; }>; diff --git a/packages/loot-core/typings/window.d.ts b/packages/loot-core/typings/window.d.ts index 9f039c1972b..a11819ca8a2 100644 --- a/packages/loot-core/typings/window.d.ts +++ b/packages/loot-core/typings/window.d.ts @@ -1,3 +1,5 @@ +import type { GlobalPrefs } from 'loot-core/types/prefs'; + export {}; declare global { @@ -8,10 +10,9 @@ declare global { openURLInBrowser: (url: string) => void; downloadActualServer: (releaseVersion: string) => Promise; startActualServer: (releaseVersion: string) => Promise; - exposeActualServer: (settings: { - authToken: string; - port: number; - }) => Promise; + exposeActualServer: ( + settings: GlobalPrefs['ngrokConfig'], + ) => Promise<{ url?: string; error?: string } | undefined>; saveFile: ( contents: string | Buffer, filename: string, From c6d2f59d0d9f97705eaaf257040d3a736ce51aac Mon Sep 17 00:00:00 2001 From: Mike Clark Date: Mon, 14 Oct 2024 20:46:12 +0100 Subject: [PATCH 14/55] correcting ngrok conditional logic --- packages/desktop-electron/index.ts | 15 +++++++++------ 1 file changed, 9 insertions(+), 6 deletions(-) diff --git a/packages/desktop-electron/index.ts b/packages/desktop-electron/index.ts index 8afd951a96e..af456e5ba29 100644 --- a/packages/desktop-electron/index.ts +++ b/packages/desktop-electron/index.ts @@ -145,8 +145,11 @@ function startSyncServer() { }); } -async function exposeSyncServer(settings: GlobalPrefs['ngrokConfig']) { - if (settings?.authToken && settings?.domain && settings?.port) { +async function exposeSyncServer(ngrokConfig: GlobalPrefs['ngrokConfig']) { + const hasRequiredConfig = + ngrokConfig?.authToken && ngrokConfig?.domain && ngrokConfig?.port; + + if (!hasRequiredConfig) { console.error('Cannot expose sync server: missing ngrok settings'); return { error: 'Missing ngrok settings' }; } @@ -154,10 +157,10 @@ async function exposeSyncServer(settings: GlobalPrefs['ngrokConfig']) { try { const listener = await ngrok.forward({ schemes: ['https'], // change this to https and bind certificate - may need to generate cert and store in user-data - addr: settings.port, - host_header: `localhost:${settings.port}`, - authtoken: settings.authToken, - domain: settings.domain, + addr: ngrokConfig.port, + host_header: `localhost:${ngrokConfig.port}`, + authtoken: ngrokConfig.authToken, + domain: ngrokConfig.domain, // crt: fs.readFileSync("crt.pem", "utf8"), // key: fs.readFileSync("key.pem", "utf8"), }); From 8eb145ffae4bfe1ed20572c27fef03afcdae684b Mon Sep 17 00:00:00 2001 From: Mike Clark Date: Mon, 14 Oct 2024 20:54:44 +0100 Subject: [PATCH 15/55] ngrok settings optional --- packages/desktop-electron/index.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/desktop-electron/index.ts b/packages/desktop-electron/index.ts index af456e5ba29..077b7c17051 100644 --- a/packages/desktop-electron/index.ts +++ b/packages/desktop-electron/index.ts @@ -306,7 +306,7 @@ app.on('ready', async () => { globalPrefs = await loadGlobalPrefs(); // load global prefs - if (globalPrefs.ngrokConfig.autoStart) { + if (globalPrefs.ngrokConfig?.autoStart) { startSyncServer(); exposeSyncServer(globalPrefs.ngrokConfig); } From 23fdf04cde1c52ed24247ef13b09c27799347b1e Mon Sep 17 00:00:00 2001 From: Mike Clark Date: Mon, 14 Oct 2024 21:30:24 +0100 Subject: [PATCH 16/55] bits --- .../src/components/manager/BudgetList.tsx | 19 +++++++++++++------ packages/desktop-electron/index.ts | 4 +++- 2 files changed, 16 insertions(+), 7 deletions(-) diff --git a/packages/desktop-client/src/components/manager/BudgetList.tsx b/packages/desktop-client/src/components/manager/BudgetList.tsx index f4307bc944d..eb335c3f652 100644 --- a/packages/desktop-client/src/components/manager/BudgetList.tsx +++ b/packages/desktop-client/src/components/manager/BudgetList.tsx @@ -448,12 +448,19 @@ export function BudgetList({ showHeader = true, quickSwitchMode = false }) { const [ngrokConfig] = useGlobalPref('ngrokConfig'); const exposeActualServer = async () => { - const url = await globalThis.Actual.exposeActualServer({ - authToken: ngrokConfig.authToken, - port: ngrokConfig.port, - domain: ngrokConfig.domain, - }); - console.info('exposing actual at: ' + url); + const hasRequiredNgrokSettings = + ngrokConfig?.authToken && ngrokConfig?.port && ngrokConfig?.domain; + if (hasRequiredNgrokSettings) { + const url = await globalThis.Actual.exposeActualServer({ + authToken: ngrokConfig.authToken, + port: ngrokConfig.port, + domain: ngrokConfig.domain, + }); + + console.info('exposing actual at: ' + url); + } else { + console.info('ngrok settings not set'); + } }; return ( diff --git a/packages/desktop-electron/index.ts b/packages/desktop-electron/index.ts index 077b7c17051..c5594cbc70a 100644 --- a/packages/desktop-electron/index.ts +++ b/packages/desktop-electron/index.ts @@ -117,7 +117,9 @@ function createBackgroundProcess() { function startSyncServer() { const serverPath = path.resolve( __dirname, - '../../../node_modules/actual-sync/app.js', // if letting electron-builder bundle it (needs to be in our workspace) + isDev + ? '../../../node_modules/actual-sync/app.js' + : '../node_modules/actual-sync/app.js', // Temporary - required because actual-server is in the other repo ); // NOTE: config.json parameters will be relative to THIS directory at the moment - may need a fix? From 58aa35041ea93e57e2a77bde8e3956cfc036e1be Mon Sep 17 00:00:00 2001 From: Mike Clark Date: Tue, 15 Oct 2024 14:58:54 +0100 Subject: [PATCH 17/55] moving server folders --- packages/desktop-electron/index.ts | 89 +++++++++++++++++++++++++----- yarn.lock | 5 +- 2 files changed, 77 insertions(+), 17 deletions(-) diff --git a/packages/desktop-electron/index.ts b/packages/desktop-electron/index.ts index c5594cbc70a..181115d05b9 100644 --- a/packages/desktop-electron/index.ts +++ b/packages/desktop-electron/index.ts @@ -16,8 +16,10 @@ import { UtilityProcess, OpenDialogSyncOptions, SaveDialogOptions, + Env, + ForkOptions, } from 'electron'; -import { copy, exists, remove } from 'fs-extra'; +import { copy, exists, mkdir, remove } from 'fs-extra'; import promiseRetry from 'promise-retry'; import { GlobalPrefs } from 'loot-core/types/prefs'; @@ -115,6 +117,34 @@ function createBackgroundProcess() { } function startSyncServer() { + const syncServerConfig = { + port: 5006, // actual-server seems to only run on 5006 - I can't get it to work on anything else... + ACTUAL_SERVER_DATA_DIR: path.resolve( + process.env.ACTUAL_DATA_DIR, + 'actual-server', + ), + ACTUAL_SERVER_FILES: path.resolve( + process.env.ACTUAL_DATA_DIR, + 'actual-server', + 'server-files', + ), + ACTUAL_USER_FILES: path.resolve( + process.env.ACTUAL_DATA_DIR, + 'actual-server', + 'user-files', + ), + defaultDataDir: path.resolve( + // TODO: There's no env variable for this - may need to add one to sync server + process.env.ACTUAL_DATA_DIR, + 'actual-server', + 'data', + ), + https: { + key: '', + cert: '', + }, + }; + const serverPath = path.resolve( __dirname, isDev @@ -125,26 +155,57 @@ function startSyncServer() { // NOTE: config.json parameters will be relative to THIS directory at the moment - may need a fix? // Or we can override the config.json location when starting the process try { + const envVariables: Env = { + ...process.env, // required + ACTUAL_PORT: `${syncServerConfig.port}`, + ACTUAL_SERVER_FILES: `${syncServerConfig.ACTUAL_SERVER_FILES}`, + ACTUAL_USER_FILES: `${syncServerConfig.ACTUAL_USER_FILES}`, + ACTUAL_DATA_DIR: `${syncServerConfig.ACTUAL_SERVER_DATA_DIR}`, + }; + + if (!fs.existsSync(syncServerConfig.ACTUAL_SERVER_FILES)) { + // create directories if they do not exit - actual-sync doesn't do it for us... + mkdir(syncServerConfig.ACTUAL_SERVER_FILES, { recursive: true }); + } + + if (!fs.existsSync(syncServerConfig.ACTUAL_USER_FILES)) { + // create directories if they do not exit - actual-sync doesn't do it for us... + mkdir(syncServerConfig.ACTUAL_USER_FILES, { recursive: true }); + } + + // TODO: make sure .migrate file is also in user-directory under actual-server + + let forkOptions: ForkOptions = { + stdio: 'pipe', + env: envVariables, + }; + + if (isDev) { + forkOptions = { ...forkOptions, execArgv: ['--inspect'] }; + } + + console.info('server-sync options', { forkOptions }); + actualServerProcess = utilityProcess.fork( serverPath, // This requires actual-server depencies (crdt) to be built before running electron - they need to be manually specified because actual-server doesn't get bundled [], - isDev ? { execArgv: ['--inspect'], stdio: 'pipe' } : { stdio: 'pipe' }, + forkOptions, ); + + actualServerProcess.stdout?.on('data', (chunk: Buffer) => { + // Send the Server console.log messages to the main browser window + clientWin?.webContents.executeJavaScript(` + console.info('Server Log:', ${JSON.stringify(chunk.toString('utf8'))})`); + }); + + actualServerProcess.stderr?.on('data', (chunk: Buffer) => { + // Send the Server console.error messages out to the main browser window + clientWin?.webContents.executeJavaScript(` + console.error('Server Log:', ${JSON.stringify(chunk.toString('utf8'))})`); + }); } catch (error) { console.error(error); } - - actualServerProcess.stdout?.on('data', (chunk: Buffer) => { - // Send the Server console.log messages to the main browser window - clientWin?.webContents.executeJavaScript(` - console.info('Server Log:', ${JSON.stringify(chunk.toString('utf8'))})`); - }); - - actualServerProcess.stderr?.on('data', (chunk: Buffer) => { - // Send the Server console.error messages out to the main browser window - clientWin?.webContents.executeJavaScript(` - console.error('Server Log:', ${JSON.stringify(chunk.toString('utf8'))})`); - }); } async function exposeSyncServer(ngrokConfig: GlobalPrefs['ngrokConfig']) { diff --git a/yarn.lock b/yarn.lock index 79baabcfe72..42d02114764 100644 --- a/yarn.lock +++ b/yarn.lock @@ -6254,7 +6254,7 @@ __metadata: "actual-sync@file:../../../actual-server::locator=desktop-electron%40workspace%3Apackages%2Fdesktop-electron": version: 24.10.0 - resolution: "actual-sync@file:../../../actual-server#../../../actual-server::hash=bcb84c&locator=desktop-electron%40workspace%3Apackages%2Fdesktop-electron" + resolution: "actual-sync@file:../../../actual-server#../../../actual-server::hash=384dfd&locator=desktop-electron%40workspace%3Apackages%2Fdesktop-electron" dependencies: "@actual-app/crdt": "npm:2.1.0" "@actual-app/web": "npm:24.10.0" @@ -6274,7 +6274,7 @@ __metadata: nordigen-node: "npm:^1.4.0" uuid: "npm:^9.0.0" winston: "npm:^3.14.2" - checksum: 10/eb84130ce8e62e8292658b12f48b5cd36d4647a37c22277842781dc1425f98475f47b24cb5c77c0c1695ee33b3ba05e842bacb7fb0ea511a8ee1f5f5a83760ac + checksum: 10/91dd1a3f845d020287f850a3a61882e65018f238d6159d6810900fdd991661b14afde96b762b8f9572aaa2907f0f1e19c98391dc4eb59d2a362adaf588f24dbd languageName: node linkType: hard @@ -8816,7 +8816,6 @@ __metadata: "@types/copyfiles": "npm:^2" "@types/fs-extra": "npm:^11" actual-sync: "file:../../../actual-server" - adm-zip: "npm:^0.5.10" better-sqlite3: "npm:^9.6.0" copyfiles: "npm:^2.4.1" cross-env: "npm:^7.0.3" From 2dc34de7277bac9395215fe3ece343cf1fc8f1c1 Mon Sep 17 00:00:00 2001 From: Mike Clark Date: Tue, 15 Oct 2024 17:15:32 +0100 Subject: [PATCH 18/55] package process --- bin/package-electron | 3 ++- packages/desktop-client/.gitignore | 1 + packages/desktop-client/vite.config.mts | 6 +++--- packages/desktop-electron/bin/update-client | 4 ++-- packages/desktop-electron/index.ts | 8 ++++++++ .../loot-core/webpack/webpack.desktop.config.js | 2 +- yarn.lock | 17 +++++------------ 7 files changed, 22 insertions(+), 19 deletions(-) diff --git a/bin/package-electron b/bin/package-electron index e3934ea84d0..f4cdf780bf8 100755 --- a/bin/package-electron +++ b/bin/package-electron @@ -36,7 +36,8 @@ fi yarn workspace loot-core build:node -yarn workspace @actual-app/web build --mode=desktop +yarn workspace @actual-app/web build:browser # required for web server (exposed via ngrok) +yarn workspace @actual-app/web build --mode=desktop # required for desktop app yarn workspace @actual-app/crdt build yarn workspace desktop-electron update-client diff --git a/packages/desktop-client/.gitignore b/packages/desktop-client/.gitignore index f635ae3caaa..d00b9758da4 100644 --- a/packages/desktop-client/.gitignore +++ b/packages/desktop-client/.gitignore @@ -10,6 +10,7 @@ playwright-report # production build +build-electron build-stats stats.json diff --git a/packages/desktop-client/vite.config.mts b/packages/desktop-client/vite.config.mts index ca7a93a61e7..44e986dfda0 100644 --- a/packages/desktop-client/vite.config.mts +++ b/packages/desktop-client/vite.config.mts @@ -15,7 +15,7 @@ const addWatchers = (): Plugin => ({ configureServer(server) { server.watcher .add([ - path.resolve('../loot-core/lib-dist/*.js'), + path.resolve('../loot-core/lib-dist/electron/*.js'), path.resolve('../loot-core/lib-dist/browser/*.js'), ]) .on('all', function () { @@ -109,7 +109,7 @@ export default defineConfig(async ({ mode }) => { build: { target: 'es2022', sourcemap: true, - outDir: 'build', + outDir: mode === 'desktop' ? 'build-electron' : 'build', assetsDir: 'static', manifest: true, assetsInlineLimit: 0, @@ -148,7 +148,7 @@ export default defineConfig(async ({ mode }) => { extensions: resolveExtensions, }, plugins: [ - // Macos electron (desktop) builds do not support PWA + // electron (desktop) builds do not support PWA mode === 'desktop' ? undefined : VitePWA({ diff --git a/packages/desktop-electron/bin/update-client b/packages/desktop-electron/bin/update-client index 24da05f6fcd..fa70190a32a 100755 --- a/packages/desktop-electron/bin/update-client +++ b/packages/desktop-electron/bin/update-client @@ -4,7 +4,7 @@ ROOT=`dirname $0`/.. rm -rf ${ROOT}/build mkdir -p ${ROOT}/build -cp -r ${ROOT}/../desktop-client/build ${ROOT}/build/client-build +cp -r ${ROOT}/../desktop-client/build-electron ${ROOT}/build/client-build # Remove the embedded backend for the browser version. Will improve # this process @@ -16,7 +16,7 @@ rm -rf ${ROOT}/build/client-build/*.map # Copy loot-core into build directory mkdir -p ${ROOT}/build/loot-core/lib-dist ls ${ROOT}/build/loot-core/lib-dist -cp ${ROOT}/../loot-core/lib-dist/bundle.desktop.js ${ROOT}/build/loot-core/lib-dist/bundle.desktop.js +cp ${ROOT}/../loot-core/lib-dist/electron/bundle.desktop.js ${ROOT}/build/loot-core/lib-dist/bundle.desktop.js cp ${ROOT}/../loot-core/default-db.sqlite ${ROOT}/build/loot-core/default-db.sqlite cp -r ${ROOT}/../loot-core/migrations ${ROOT}/build/loot-core/migrations diff --git a/packages/desktop-electron/index.ts b/packages/desktop-electron/index.ts index 181115d05b9..89f39305c1d 100644 --- a/packages/desktop-electron/index.ts +++ b/packages/desktop-electron/index.ts @@ -152,6 +152,13 @@ function startSyncServer() { : '../node_modules/actual-sync/app.js', // Temporary - required because actual-server is in the other repo ); + const webRoot = path.resolve( + __dirname, + isDev + ? undefined // default is fine in dev + : '../node_modules/@actual-app/web/build/', // this the web build output + ); + // NOTE: config.json parameters will be relative to THIS directory at the moment - may need a fix? // Or we can override the config.json location when starting the process try { @@ -161,6 +168,7 @@ function startSyncServer() { ACTUAL_SERVER_FILES: `${syncServerConfig.ACTUAL_SERVER_FILES}`, ACTUAL_USER_FILES: `${syncServerConfig.ACTUAL_USER_FILES}`, ACTUAL_DATA_DIR: `${syncServerConfig.ACTUAL_SERVER_DATA_DIR}`, + ACTUAL_WEB_ROOT: webRoot, }; if (!fs.existsSync(syncServerConfig.ACTUAL_SERVER_FILES)) { diff --git a/packages/loot-core/webpack/webpack.desktop.config.js b/packages/loot-core/webpack/webpack.desktop.config.js index 50460f5597c..20c72c54a43 100644 --- a/packages/loot-core/webpack/webpack.desktop.config.js +++ b/packages/loot-core/webpack/webpack.desktop.config.js @@ -11,7 +11,7 @@ module.exports = { target: 'node', devtool: 'source-map', output: { - path: path.resolve(path.join(__dirname, '/../lib-dist')), + path: path.resolve(path.join(__dirname, '/../lib-dist/electron')), filename: 'bundle.desktop.js', sourceMapFilename: 'bundle.desktop.js.map', libraryTarget: 'commonjs2', diff --git a/yarn.lock b/yarn.lock index 42d02114764..4e88104d7ae 100644 --- a/yarn.lock +++ b/yarn.lock @@ -55,14 +55,7 @@ __metadata: languageName: unknown linkType: soft -"@actual-app/web@npm:24.10.0": - version: 24.10.0 - resolution: "@actual-app/web@npm:24.10.0" - checksum: 10/e713d98f69d7a8598f863c0fe559c2a87d56f0adc31e3bea62338bf9f9e8cfefc1fc41597e8ef0e3cdc1c51a611e330d023c9cde92e1d7b2b0a3f90e8a690254 - languageName: node - linkType: hard - -"@actual-app/web@workspace:packages/desktop-client": +"@actual-app/web@npm:24.10.1, @actual-app/web@workspace:packages/desktop-client": version: 0.0.0-use.local resolution: "@actual-app/web@workspace:packages/desktop-client" dependencies: @@ -6253,11 +6246,11 @@ __metadata: linkType: hard "actual-sync@file:../../../actual-server::locator=desktop-electron%40workspace%3Apackages%2Fdesktop-electron": - version: 24.10.0 - resolution: "actual-sync@file:../../../actual-server#../../../actual-server::hash=384dfd&locator=desktop-electron%40workspace%3Apackages%2Fdesktop-electron" + version: 24.10.1 + resolution: "actual-sync@file:../../../actual-server#../../../actual-server::hash=b4db45&locator=desktop-electron%40workspace%3Apackages%2Fdesktop-electron" dependencies: "@actual-app/crdt": "npm:2.1.0" - "@actual-app/web": "npm:24.10.0" + "@actual-app/web": "npm:24.10.1" bcrypt: "npm:^5.1.1" better-sqlite3: "npm:^9.6.0" body-parser: "npm:^1.20.3" @@ -6274,7 +6267,7 @@ __metadata: nordigen-node: "npm:^1.4.0" uuid: "npm:^9.0.0" winston: "npm:^3.14.2" - checksum: 10/91dd1a3f845d020287f850a3a61882e65018f238d6159d6810900fdd991661b14afde96b762b8f9572aaa2907f0f1e19c98391dc4eb59d2a362adaf588f24dbd + checksum: 10/b8b9bab1dc179669a1ee628d2546cf27dab6cb216e7b161a06e06d255edcf92727187616dd05d9011c14f9fc8d605617fedd17352691ca0058a1c3f71c53cf8f languageName: node linkType: hard From 5d01674f6d5387765fa6e160af79921e9303e14f Mon Sep 17 00:00:00 2001 From: Mike Clark Date: Tue, 15 Oct 2024 19:43:49 +0100 Subject: [PATCH 19/55] what an ordeal --- bin/package-electron | 12 +++++++++--- 1 file changed, 9 insertions(+), 3 deletions(-) diff --git a/bin/package-electron b/bin/package-electron index f4cdf780bf8..ac29060e879 100755 --- a/bin/package-electron +++ b/bin/package-electron @@ -34,11 +34,17 @@ if [ "$OSTYPE" == "msys" ]; then fi fi -yarn workspace loot-core build:node +# TODO: should do these async if possible -yarn workspace @actual-app/web build:browser # required for web server (exposed via ngrok) -yarn workspace @actual-app/web build --mode=desktop # required for desktop app +# required for when running in electron yarn workspace @actual-app/crdt build +yarn workspace loot-core build:node +yarn workspace @actual-app/web build --mode=desktop # electron specific build + +# required for when running in web server (exposed via ngrok) +yarn workspace loot-core build:browser +yarn workspace @actual-app/web build:browser + yarn workspace desktop-electron update-client From 8eca6437be7a641c50f7514bf77b07257b0b0598 Mon Sep 17 00:00:00 2001 From: Mike Clark Date: Thu, 17 Oct 2024 22:20:45 +0100 Subject: [PATCH 20/55] fixes --- packages/desktop-electron/bin/update-client | 5 ++--- packages/desktop-electron/index.ts | 20 +++++++++---------- packages/desktop-electron/package.json | 2 +- packages/loot-core/init-node.js | 2 +- .../src/platform/server/fs/index.electron.ts | 2 +- packages/loot-core/src/server/post.ts | 6 ++++++ yarn.lock | 4 ++-- 7 files changed, 22 insertions(+), 19 deletions(-) diff --git a/packages/desktop-electron/bin/update-client b/packages/desktop-electron/bin/update-client index fa70190a32a..d705152cc6c 100755 --- a/packages/desktop-electron/bin/update-client +++ b/packages/desktop-electron/bin/update-client @@ -14,9 +14,8 @@ rm -rf ${ROOT}/build/client-build/*.wasm rm -rf ${ROOT}/build/client-build/*.map # Copy loot-core into build directory -mkdir -p ${ROOT}/build/loot-core/lib-dist -ls ${ROOT}/build/loot-core/lib-dist -cp ${ROOT}/../loot-core/lib-dist/electron/bundle.desktop.js ${ROOT}/build/loot-core/lib-dist/bundle.desktop.js +mkdir -p ${ROOT}/build/loot-core/lib-dist/electron +cp ${ROOT}/../loot-core/lib-dist/electron/bundle.desktop.js ${ROOT}/build/loot-core/lib-dist/electron/bundle.desktop.js cp ${ROOT}/../loot-core/default-db.sqlite ${ROOT}/build/loot-core/default-db.sqlite cp -r ${ROOT}/../loot-core/migrations ${ROOT}/build/loot-core/migrations diff --git a/packages/desktop-electron/index.ts b/packages/desktop-electron/index.ts index 89f39305c1d..fd18201245a 100644 --- a/packages/desktop-electron/index.ts +++ b/packages/desktop-electron/index.ts @@ -35,8 +35,8 @@ import './security'; const isDev = !app.isPackaged; // dev mode if not packaged process.env.lootCoreScript = isDev - ? 'loot-core/lib-dist/bundle.desktop.js' // serve from local output in development (provides hot-reloading) - : path.resolve(__dirname, 'loot-core/lib-dist/bundle.desktop.js'); // serve from build in production + ? 'loot-core/lib-dist/electron/bundle.desktop.js' // serve from local output in development (provides hot-reloading) + : path.resolve(__dirname, 'loot-core/lib-dist/electron/bundle.desktop.js'); // serve from build in production // This allows relative URLs to be resolved to app:// which makes // local assets load correctly @@ -152,12 +152,12 @@ function startSyncServer() { : '../node_modules/actual-sync/app.js', // Temporary - required because actual-server is in the other repo ); - const webRoot = path.resolve( - __dirname, - isDev - ? undefined // default is fine in dev - : '../node_modules/@actual-app/web/build/', // this the web build output - ); + const webRoot = isDev + ? undefined + : path.resolve( + __dirname, + '../node_modules/@actual-app/web/build/', // this the web build output + ); // NOTE: config.json parameters will be relative to THIS directory at the moment - may need a fix? // Or we can override the config.json location when starting the process @@ -192,8 +192,6 @@ function startSyncServer() { forkOptions = { ...forkOptions, execArgv: ['--inspect'] }; } - console.info('server-sync options', { forkOptions }); - actualServerProcess = utilityProcess.fork( serverPath, // This requires actual-server depencies (crdt) to be built before running electron - they need to be manually specified because actual-server doesn't get bundled [], @@ -378,7 +376,7 @@ app.on('ready', async () => { globalPrefs = await loadGlobalPrefs(); // load global prefs if (globalPrefs.ngrokConfig?.autoStart) { - startSyncServer(); + // startSyncServer(); exposeSyncServer(globalPrefs.ngrokConfig); } diff --git a/packages/desktop-electron/package.json b/packages/desktop-electron/package.json index bd98e77ba5e..b9a36df375b 100644 --- a/packages/desktop-electron/package.json +++ b/packages/desktop-electron/package.json @@ -21,7 +21,7 @@ "!**/*.js.map", "!**/stats.json", "!build/client-build/sql-wasm.wasm", - "!build/loot-core/lib-dist/{browser,bundle.mobile*}" + "!build/loot-core/lib-dist/electron/{browser,bundle.mobile*}" ], "beforePack": "./build/beforePackHook.js", "mac": { diff --git a/packages/loot-core/init-node.js b/packages/loot-core/init-node.js index 3470eccd0e3..13b4df0f5c4 100644 --- a/packages/loot-core/init-node.js +++ b/packages/loot-core/init-node.js @@ -4,7 +4,7 @@ import fetch from 'node-fetch'; import 'source-map-support/register'; // eslint-disable-next-line import/extensions -import bundle from './lib-dist/bundle.desktop.js'; +import bundle from './lib-dist/electron/bundle.desktop.js'; global.fetch = fetch; diff --git a/packages/loot-core/src/platform/server/fs/index.electron.ts b/packages/loot-core/src/platform/server/fs/index.electron.ts index 9d78de4bcb5..fc165ab6383 100644 --- a/packages/loot-core/src/platform/server/fs/index.electron.ts +++ b/packages/loot-core/src/platform/server/fs/index.electron.ts @@ -13,7 +13,7 @@ let rootPath = path.join(__dirname, '..', '..', '..', '..'); if (__filename.match('bundle')) { // The file name is not our filename and indicates that we're in the // bundled form. Because of this, the root path is different. - rootPath = path.join(__dirname, '..'); + rootPath = path.join(__dirname, '..', '..'); } export const init = () => { diff --git a/packages/loot-core/src/server/post.ts b/packages/loot-core/src/server/post.ts index 8b49c359d41..9761a397431 100644 --- a/packages/loot-core/src/server/post.ts +++ b/packages/loot-core/src/server/post.ts @@ -15,6 +15,12 @@ function throwIfNot200(res, text) { const json = JSON.parse(text); throw new PostError(json.reason); } + + if (res.headers.get('ngrok-error-code')) { + // When server is hosted behind ngrok and response code is not 200 we have experienced a network error + throw new PostError('network-failure'); + } + throw new PostError(text); } } diff --git a/yarn.lock b/yarn.lock index 50ce34384b0..d96859af24f 100644 --- a/yarn.lock +++ b/yarn.lock @@ -6388,7 +6388,7 @@ __metadata: "actual-sync@file:../../../actual-server::locator=desktop-electron%40workspace%3Apackages%2Fdesktop-electron": version: 24.10.1 - resolution: "actual-sync@file:../../../actual-server#../../../actual-server::hash=b4db45&locator=desktop-electron%40workspace%3Apackages%2Fdesktop-electron" + resolution: "actual-sync@file:../../../actual-server#../../../actual-server::hash=4569dd&locator=desktop-electron%40workspace%3Apackages%2Fdesktop-electron" dependencies: "@actual-app/crdt": "npm:2.1.0" "@actual-app/web": "npm:24.10.1" @@ -6408,7 +6408,7 @@ __metadata: nordigen-node: "npm:^1.4.0" uuid: "npm:^9.0.0" winston: "npm:^3.14.2" - checksum: 10/b8b9bab1dc179669a1ee628d2546cf27dab6cb216e7b161a06e06d255edcf92727187616dd05d9011c14f9fc8d605617fedd17352691ca0058a1c3f71c53cf8f + checksum: 10/3c38b1628ec665ee4129184f8b44ebdc51ced8561d4b144578f500401b32faf49a590bf4303f3429ffeca179dcb4b67d37da7ddc4e1890f22f76a159b4976b20 languageName: node linkType: hard From b83a3c6e446221c75758969d5773cb196bd08fe5 Mon Sep 17 00:00:00 2001 From: Mike Clark Date: Thu, 17 Oct 2024 22:21:43 +0100 Subject: [PATCH 21/55] reenable sync server now tests have passed --- packages/desktop-electron/index.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/desktop-electron/index.ts b/packages/desktop-electron/index.ts index fd18201245a..9258bca2ce5 100644 --- a/packages/desktop-electron/index.ts +++ b/packages/desktop-electron/index.ts @@ -376,7 +376,7 @@ app.on('ready', async () => { globalPrefs = await loadGlobalPrefs(); // load global prefs if (globalPrefs.ngrokConfig?.autoStart) { - // startSyncServer(); + startSyncServer(); exposeSyncServer(globalPrefs.ngrokConfig); } From 39d060bd0b824ec0caa057a7b9ee7e271a87d194 Mon Sep 17 00:00:00 2001 From: Mike Clark Date: Fri, 18 Oct 2024 21:42:33 +0100 Subject: [PATCH 22/55] splitting up server management to allow config of desktop app server and external serperately --- .../manager/ConfigExternalSyncServer.tsx | 273 ++++++++++++++++++ .../manager/ConfigInternalSyncServer.tsx | 40 +++ .../src/components/manager/ConfigServer.tsx | 258 ++--------------- .../src/components/manager/ManagementApp.jsx | 10 + packages/desktop-electron/index.ts | 19 +- 5 files changed, 357 insertions(+), 243 deletions(-) create mode 100644 packages/desktop-client/src/components/manager/ConfigExternalSyncServer.tsx create mode 100644 packages/desktop-client/src/components/manager/ConfigInternalSyncServer.tsx diff --git a/packages/desktop-client/src/components/manager/ConfigExternalSyncServer.tsx b/packages/desktop-client/src/components/manager/ConfigExternalSyncServer.tsx new file mode 100644 index 00000000000..dc3eed32a71 --- /dev/null +++ b/packages/desktop-client/src/components/manager/ConfigExternalSyncServer.tsx @@ -0,0 +1,273 @@ +// @ts-strict-ignore +import React, { useState, useEffect, useCallback } from 'react'; +import { Trans, useTranslation } from 'react-i18next'; + +import { + isNonProductionEnvironment, + isElectron, +} from 'loot-core/src/shared/environment'; + +import { useActions } from '../../hooks/useActions'; +import { useGlobalPref } from '../../hooks/useGlobalPref'; +import { useNavigate } from '../../hooks/useNavigate'; +import { theme } from '../../style'; +import { Button, ButtonWithLoading } from '../common/Button2'; +import { BigInput } from '../common/Input'; +import { Link } from '../common/Link'; +import { Text } from '../common/Text'; +import { View } from '../common/View'; +import { useServerURL, useSetServerURL } from '../ServerContext'; + +import { Title } from './subscribe/common'; + +export function ConfigExternalSyncServer() { + const { t } = useTranslation(); + const { createBudget, signOut, loggedIn } = useActions(); + const navigate = useNavigate(); + const [url, setUrl] = useState(''); + const currentUrl = useServerURL(); + const setServerUrl = useSetServerURL(); + useEffect(() => { + setUrl(currentUrl); + }, [currentUrl]); + const [loading, setLoading] = useState(false); + const [error, setError] = useState(null); + + const restartElectronServer = useCallback(() => { + globalThis.window.Actual.restartElectronServer(); + setError(null); + }, []); + + const [_serverSelfSignedCert, setServerSelfSignedCert] = useGlobalPref( + 'serverSelfSignedCert', + restartElectronServer, + ); + + function getErrorMessage(error: string) { + switch (error) { + case 'network-failure': + return t( + 'Server is not running at this URL. Make sure you have HTTPS set up properly.', + ); + default: + return t( + 'Server does not look like an Actual server. Is it set up correctly?', + ); + } + } + + async function onSubmit() { + if (url === '' || loading) { + return; + } + + setError(null); + setLoading(true); + const { error } = await setServerUrl(url); + + if ( + ['network-failure', 'get-server-failure'].includes(error) && + !url.startsWith('http://') && + !url.startsWith('https://') + ) { + const { error } = await setServerUrl('https://' + url); + if (error) { + setUrl('https://' + url); + setError(error); + } else { + await signOut(); + navigate('/'); + } + setLoading(false); + } else if (error) { + setLoading(false); + setError(error); + } else { + setLoading(false); + await signOut(); + navigate('/'); + } + } + + function onSameDomain() { + setUrl(window.location.origin); + } + + async function onSelectSelfSignedCertificate() { + const selfSignedCertificateLocation = await window.Actual?.openFileDialog({ + properties: ['openFile'], + filters: [ + { + name: 'Self Signed Certificate', + extensions: ['crt', 'pem'], + }, + ], + }); + + if (selfSignedCertificateLocation) { + setServerSelfSignedCert(selfSignedCertificateLocation[0]); + } + } + + async function onSkip() { + await setServerUrl(null); + await loggedIn(); + navigate('/'); + } + + async function onCreateTestFile() { + await setServerUrl(null); + await createBudget({ testMode: true }); + window.__navigate('/'); + } + + if (isElectron()) { + } + + return ( + + + + <Text + style={{ + fontSize: 16, + color: theme.tableRowHeaderText, + lineHeight: 1.5, + }} + > + {currentUrl ? ( + <Trans> + Existing sessions will be logged out and you will log in to this + server. We will validate that Actual is running at this URL. + </Trans> + ) : ( + <Trans> + There is no server configured. After running the server, specify the + URL here to use the app. You can always change this later. We will + validate that Actual is running at this URL. + </Trans> + )} + </Text> + + {error && ( + <> + <Text + style={{ + marginTop: 20, + color: theme.errorText, + borderRadius: 4, + fontSize: 15, + }} + > + {getErrorMessage(error)} + </Text> + {isElectron() && ( + <View + style={{ display: 'flex', flexDirection: 'row', marginTop: 20 }} + > + <Text + style={{ + color: theme.errorText, + borderRadius: 4, + fontSize: 15, + }} + > + <Trans> + If the server is using a self-signed certificate{' '} + <Link + variant="text" + style={{ fontSize: 15 }} + onClick={onSelectSelfSignedCertificate} + > + select it here + </Link> + . + </Trans> + </Text> + </View> + )} + </> + )} + + <View style={{ display: 'flex', flexDirection: 'row', marginTop: 30 }}> + <BigInput + autoFocus={true} + placeholder={t('https://example.com')} + value={url || ''} + onChangeValue={setUrl} + style={{ flex: 1, marginRight: 10 }} + onEnter={onSubmit} + /> + <ButtonWithLoading + variant="primary" + isLoading={loading} + style={{ fontSize: 15 }} + onPress={onSubmit} + > + {t('OK')} + </ButtonWithLoading> + {currentUrl && ( + <Button + variant="bare" + style={{ fontSize: 15, marginLeft: 10 }} + onPress={() => navigate(-1)} + > + {t('Cancel')} + </Button> + )} + </View> + + <View + style={{ + flexDirection: 'row', + flexFlow: 'row wrap', + justifyContent: 'center', + marginTop: 15, + }} + > + {currentUrl ? ( + <Button + variant="bare" + style={{ color: theme.pageTextLight }} + onPress={onSkip} + > + {t('Stop using a server')} + </Button> + ) : ( + <> + {!isElectron() && ( + <Button + variant="bare" + style={{ + color: theme.pageTextLight, + margin: 5, + marginRight: 15, + }} + onPress={onSameDomain} + > + {t('Use current domain')} + </Button> + )} + <Button + variant="bare" + style={{ color: theme.pageTextLight, margin: 5 }} + onPress={onSkip} + > + {t('Don’t use a server')} + </Button> + + {isNonProductionEnvironment() && ( + <Button + variant="primary" + style={{ marginLeft: 15 }} + onPress={onCreateTestFile} + > + {t('Create test file')} + </Button> + )} + </> + )} + </View> + </View> + ); +} diff --git a/packages/desktop-client/src/components/manager/ConfigInternalSyncServer.tsx b/packages/desktop-client/src/components/manager/ConfigInternalSyncServer.tsx new file mode 100644 index 00000000000..07595c0cf4a --- /dev/null +++ b/packages/desktop-client/src/components/manager/ConfigInternalSyncServer.tsx @@ -0,0 +1,40 @@ +// @ts-strict-ignore +import React, { useState, useEffect, useCallback } from 'react'; +import { Trans, useTranslation } from 'react-i18next'; + +import { useNavigate } from '../../hooks/useNavigate'; +import { theme } from '../../style'; +import { Button, ButtonWithLoading } from '../common/Button2'; +import { Text } from '../common/Text'; +import { View } from '../common/View'; +import { useServerURL, useSetServerURL } from '../ServerContext'; + +import { Title } from './subscribe/common'; + +export function ConfigInternalSyncServer() { + const { t } = useTranslation(); + const navigate = useNavigate(); + + return ( + <View style={{ maxWidth: 500, marginTop: -30 }}> + <Title text={t('Server configuration')} /> + + <Text + style={{ + fontSize: 16, + color: theme.pageText, + lineHeight: 1.5, + }} + > + <Trans> + Actual can setup a server for you to sync your data across devices. It + can either run on your computer or on a server. If you want to run it + on your computer, you can use the button below to start the server. If + you want to run it on a server, you can enter the URL of the server + below. + </Trans> + </Text> + <Button onPress={() => navigate(-1)}>Back</Button> + </View> + ); +} diff --git a/packages/desktop-client/src/components/manager/ConfigServer.tsx b/packages/desktop-client/src/components/manager/ConfigServer.tsx index d8c1727e042..7712b39af5f 100644 --- a/packages/desktop-client/src/components/manager/ConfigServer.tsx +++ b/packages/desktop-client/src/components/manager/ConfigServer.tsx @@ -1,270 +1,60 @@ // @ts-strict-ignore -import React, { useState, useEffect, useCallback } from 'react'; +import React from 'react'; import { Trans, useTranslation } from 'react-i18next'; -import { - isNonProductionEnvironment, - isElectron, -} from 'loot-core/src/shared/environment'; +import { isElectron } from 'loot-core/src/shared/environment'; -import { useActions } from '../../hooks/useActions'; -import { useGlobalPref } from '../../hooks/useGlobalPref'; import { useNavigate } from '../../hooks/useNavigate'; import { theme } from '../../style'; -import { Button, ButtonWithLoading } from '../common/Button2'; -import { BigInput } from '../common/Input'; -import { Link } from '../common/Link'; +import { Button } from '../common/Button2'; import { Text } from '../common/Text'; import { View } from '../common/View'; -import { useServerURL, useSetServerURL } from '../ServerContext'; import { Title } from './subscribe/common'; export function ConfigServer() { const { t } = useTranslation(); - const { createBudget, signOut, loggedIn } = useActions(); const navigate = useNavigate(); - const [url, setUrl] = useState(''); - const currentUrl = useServerURL(); - const setServerUrl = useSetServerURL(); - useEffect(() => { - setUrl(currentUrl); - }, [currentUrl]); - const [loading, setLoading] = useState(false); - const [error, setError] = useState<string | null>(null); - - const restartElectronServer = useCallback(() => { - globalThis.window.Actual.restartElectronServer(); - setError(null); - }, []); - - const [_serverSelfSignedCert, setServerSelfSignedCert] = useGlobalPref( - 'serverSelfSignedCert', - restartElectronServer, - ); - - function getErrorMessage(error: string) { - switch (error) { - case 'network-failure': - return t( - 'Server is not running at this URL. Make sure you have HTTPS set up properly.', - ); - default: - return t( - 'Server does not look like an Actual server. Is it set up correctly?', - ); - } - } - - async function onSubmit() { - if (url === '' || loading) { - return; - } - - setError(null); - setLoading(true); - const { error } = await setServerUrl(url); - - if ( - ['network-failure', 'get-server-failure'].includes(error) && - !url.startsWith('http://') && - !url.startsWith('https://') - ) { - const { error } = await setServerUrl('https://' + url); - if (error) { - setUrl('https://' + url); - setError(error); - } else { - await signOut(); - navigate('/'); - } - setLoading(false); - } else if (error) { - setLoading(false); - setError(error); - } else { - setLoading(false); - await signOut(); - navigate('/'); - } - } - - function onSameDomain() { - setUrl(window.location.origin); - } - - async function onSelectSelfSignedCertificate() { - const selfSignedCertificateLocation = await window.Actual?.openFileDialog({ - properties: ['openFile'], - filters: [ - { - name: 'Self Signed Certificate', - extensions: ['crt', 'pem'], - }, - ], - }); - - if (selfSignedCertificateLocation) { - setServerSelfSignedCert(selfSignedCertificateLocation[0]); - } - } - - async function onSkip() { - await setServerUrl(null); - await loggedIn(); - navigate('/'); - } - - async function onCreateTestFile() { - await setServerUrl(null); - await createBudget({ testMode: true }); - window.__navigate('/'); - } return ( <View style={{ maxWidth: 500, marginTop: -30 }}> - <Title text={t('Where’s the server?')} /> + <Title text={t('Let’s set up your server!')} /> <Text style={{ fontSize: 16, - color: theme.tableRowHeaderText, + color: theme.pageText, lineHeight: 1.5, }} > - {currentUrl ? ( - <Trans> - Existing sessions will be logged out and you will log in to this - server. We will validate that Actual is running at this URL. - </Trans> - ) : ( - <Trans> - There is no server configured. After running the server, specify the - URL here to use the app. You can always change this later. We will - validate that Actual is running at this URL. - </Trans> - )} + <Trans> + If you like, Actual can setup a server for you to sync your data + across devices. + </Trans> </Text> - - {error && ( + {isElectron() && ( <> <Text style={{ - marginTop: 20, - color: theme.errorText, - borderRadius: 4, - fontSize: 15, + fontSize: 16, + color: theme.pageText, + lineHeight: 1.5, }} > - {getErrorMessage(error)} + <Trans> + Would you like to host the server on your computer or connect to + an external server? + </Trans> </Text> - {isElectron() && ( - <View - style={{ display: 'flex', flexDirection: 'row', marginTop: 20 }} - > - <Text - style={{ - color: theme.errorText, - borderRadius: 4, - fontSize: 15, - }} - > - <Trans> - If the server is using a self-signed certificate{' '} - <Link - variant="text" - style={{ fontSize: 15 }} - onClick={onSelectSelfSignedCertificate} - > - select it here - </Link> - . - </Trans> - </Text> - </View> - )} - </> - )} - - <View style={{ display: 'flex', flexDirection: 'row', marginTop: 30 }}> - <BigInput - autoFocus={true} - placeholder={t('https://example.com')} - value={url || ''} - onChangeValue={setUrl} - style={{ flex: 1, marginRight: 10 }} - onEnter={onSubmit} - /> - <ButtonWithLoading - variant="primary" - isLoading={loading} - style={{ fontSize: 15 }} - onPress={onSubmit} - > - {t('OK')} - </ButtonWithLoading> - {currentUrl && ( - <Button - variant="bare" - style={{ fontSize: 15, marginLeft: 10 }} - onPress={() => navigate(-1)} - > - {t('Cancel')} + <Button onPress={() => navigate('/config-server/internal')}> + Host on this computer </Button> - )} - </View> - - <View - style={{ - flexDirection: 'row', - flexFlow: 'row wrap', - justifyContent: 'center', - marginTop: 15, - }} - > - {currentUrl ? ( - <Button - variant="bare" - style={{ color: theme.pageTextLight }} - onPress={onSkip} - > - {t('Stop using a server')} + <Button onPress={() => navigate('/config-server/external')}> + Connect to an external server </Button> - ) : ( - <> - {!isElectron() && ( - <Button - variant="bare" - style={{ - color: theme.pageTextLight, - margin: 5, - marginRight: 15, - }} - onPress={onSameDomain} - > - {t('Use current domain')} - </Button> - )} - <Button - variant="bare" - style={{ color: theme.pageTextLight, margin: 5 }} - onPress={onSkip} - > - {t('Don’t use a server')} - </Button> - - {isNonProductionEnvironment() && ( - <Button - variant="primary" - style={{ marginLeft: 15 }} - onPress={onCreateTestFile} - > - {t('Create test file')} - </Button> - )} - </> - )} - </View> + </> + )} + <Button onPress={() => navigate('/')}>I don’t want a server</Button> </View> ); } diff --git a/packages/desktop-client/src/components/manager/ManagementApp.jsx b/packages/desktop-client/src/components/manager/ManagementApp.jsx index b9550088c6c..6e6a64d96c5 100644 --- a/packages/desktop-client/src/components/manager/ManagementApp.jsx +++ b/packages/desktop-client/src/components/manager/ManagementApp.jsx @@ -20,6 +20,8 @@ import { Notifications } from '../Notifications'; import { useServerVersion } from '../ServerContext'; import { BudgetList } from './BudgetList'; +import { ConfigExternalSyncServer } from './ConfigExternalSyncServer'; +import { ConfigInternalSyncServer } from './ConfigInternalSyncServer'; import { ConfigServer } from './ConfigServer'; import { ServerURL } from './ServerURL'; import { Bootstrap } from './subscribe/Bootstrap'; @@ -126,6 +128,14 @@ export function ManagementApp() { <> <Routes> <Route path="/config-server" element={<ConfigServer />} /> + <Route + path="/config-server/external" + element={<ConfigExternalSyncServer />} + /> + <Route + path="/config-server/internal" + element={<ConfigInternalSyncServer />} + /> <Route path="/change-password" element={<ChangePassword />} /> {files && files.length > 0 ? ( diff --git a/packages/desktop-electron/index.ts b/packages/desktop-electron/index.ts index 9258bca2ce5..dfb4374d874 100644 --- a/packages/desktop-electron/index.ts +++ b/packages/desktop-electron/index.ts @@ -152,25 +152,26 @@ function startSyncServer() { : '../node_modules/actual-sync/app.js', // Temporary - required because actual-server is in the other repo ); - const webRoot = isDev - ? undefined - : path.resolve( - __dirname, - '../node_modules/@actual-app/web/build/', // this the web build output - ); - // NOTE: config.json parameters will be relative to THIS directory at the moment - may need a fix? // Or we can override the config.json location when starting the process try { - const envVariables: Env = { + let envVariables: Env = { ...process.env, // required ACTUAL_PORT: `${syncServerConfig.port}`, ACTUAL_SERVER_FILES: `${syncServerConfig.ACTUAL_SERVER_FILES}`, ACTUAL_USER_FILES: `${syncServerConfig.ACTUAL_USER_FILES}`, ACTUAL_DATA_DIR: `${syncServerConfig.ACTUAL_SERVER_DATA_DIR}`, - ACTUAL_WEB_ROOT: webRoot, }; + if (!isDev) { + const webRoot = path.resolve( + __dirname, + '../node_modules/@actual-app/web/build/', // this the web build output + ); + + envVariables = { ...envVariables, ACTUAL_WEB_ROOT: webRoot }; + } + if (!fs.existsSync(syncServerConfig.ACTUAL_SERVER_FILES)) { // create directories if they do not exit - actual-sync doesn't do it for us... mkdir(syncServerConfig.ACTUAL_SERVER_FILES, { recursive: true }); From 45d9fae206a86e610517ec607d87cb47d8824d5c Mon Sep 17 00:00:00 2001 From: Mike Clark <mclarkgb@gmail.com> Date: Sun, 20 Oct 2024 10:30:36 +0100 Subject: [PATCH 23/55] fix the port thing --- package.json | 2 +- packages/desktop-electron/index.ts | 16 ++++++++-------- 2 files changed, 9 insertions(+), 9 deletions(-) diff --git a/package.json b/package.json index cc58c2d61f9..36628540203 100644 --- a/package.json +++ b/package.json @@ -21,7 +21,7 @@ "start": "yarn start:browser", "start:desktop": "yarn rebuild-electron && npm-run-all --parallel 'start:desktop-*'", "start:desktop-node": "yarn workspace loot-core watch:node", - "start:desktop-server-deps": "yarn workspace @actual-app/crdt build", + "start:desktop-server-dependencies": "yarn workspace @actual-app/crdt build", "start:desktop-client": "yarn workspace @actual-app/web watch", "start:desktop-electron": "yarn workspace desktop-electron watch", "start:electron": "yarn start:desktop", diff --git a/packages/desktop-electron/index.ts b/packages/desktop-electron/index.ts index dfb4374d874..6721a6c25dd 100644 --- a/packages/desktop-electron/index.ts +++ b/packages/desktop-electron/index.ts @@ -118,7 +118,7 @@ function createBackgroundProcess() { function startSyncServer() { const syncServerConfig = { - port: 5006, // actual-server seems to only run on 5006 - I can't get it to work on anything else... + port: 5007, ACTUAL_SERVER_DATA_DIR: path.resolve( process.env.ACTUAL_DATA_DIR, 'actual-server', @@ -163,14 +163,14 @@ function startSyncServer() { ACTUAL_DATA_DIR: `${syncServerConfig.ACTUAL_SERVER_DATA_DIR}`, }; - if (!isDev) { - const webRoot = path.resolve( - __dirname, - '../node_modules/@actual-app/web/build/', // this the web build output - ); + const webRoot = path.resolve( + __dirname, + isDev + ? '../../../node_modules/@actual-app/web/build/' // workspace node_modules + : '../node_modules/@actual-app/web/build/', // location of packaged module + ); - envVariables = { ...envVariables, ACTUAL_WEB_ROOT: webRoot }; - } + envVariables = { ...envVariables, ACTUAL_WEB_ROOT: webRoot }; if (!fs.existsSync(syncServerConfig.ACTUAL_SERVER_FILES)) { // create directories if they do not exit - actual-sync doesn't do it for us... From 0a333ed3967c14a6c523bf56ae9eb2a4a10aa6b1 Mon Sep 17 00:00:00 2001 From: Mike Clark <mclarkgb@gmail.com> Date: Sun, 20 Oct 2024 12:35:58 +0100 Subject: [PATCH 24/55] tunnel erorrs cleanup --- packages/desktop-electron/index.ts | 2 +- packages/loot-core/src/server/post.ts | 13 +++++++++++-- 2 files changed, 12 insertions(+), 3 deletions(-) diff --git a/packages/desktop-electron/index.ts b/packages/desktop-electron/index.ts index 6721a6c25dd..bd5101851d7 100644 --- a/packages/desktop-electron/index.ts +++ b/packages/desktop-electron/index.ts @@ -377,7 +377,7 @@ app.on('ready', async () => { globalPrefs = await loadGlobalPrefs(); // load global prefs if (globalPrefs.ngrokConfig?.autoStart) { - startSyncServer(); + // startSyncServer(); exposeSyncServer(globalPrefs.ngrokConfig); } diff --git a/packages/loot-core/src/server/post.ts b/packages/loot-core/src/server/post.ts index 9761a397431..50d4e1679c3 100644 --- a/packages/loot-core/src/server/post.ts +++ b/packages/loot-core/src/server/post.ts @@ -16,8 +16,17 @@ function throwIfNot200(res, text) { throw new PostError(json.reason); } - if (res.headers.get('ngrok-error-code')) { - // When server is hosted behind ngrok and response code is not 200 we have experienced a network error + // Actual Sync Server may be exposed via a tunnel (e.g. ngrok). Tunnel errors should be treated as network errors. + const tunnelErrorHeaders = ['ngrok-error-code']; + + const headerKeys = res.headers.keys().toArray(); + const tunnelError = tunnelErrorHeaders.some(tunnelErrorHeader => + headerKeys.includes(tunnelErrorHeader), + ); + + if (tunnelError) { + // Tunnel errors are present when the tunnel is active and the server is not reachable e.g. server is offline + // When we experience a tunnel error we treat it as a network failure throw new PostError('network-failure'); } From a78ad8824b7a742d791c259888636ec1e9f9ed0e Mon Sep 17 00:00:00 2001 From: Mike Clark <mclarkgb@gmail.com> Date: Sun, 20 Oct 2024 15:30:21 +0100 Subject: [PATCH 25/55] trickle in --- packages/desktop-electron/index.ts | 2 +- packages/loot-core/src/server/post.ts | 6 ++---- yarn.lock | 4 ++-- 3 files changed, 5 insertions(+), 7 deletions(-) diff --git a/packages/desktop-electron/index.ts b/packages/desktop-electron/index.ts index bd5101851d7..6721a6c25dd 100644 --- a/packages/desktop-electron/index.ts +++ b/packages/desktop-electron/index.ts @@ -377,7 +377,7 @@ app.on('ready', async () => { globalPrefs = await loadGlobalPrefs(); // load global prefs if (globalPrefs.ngrokConfig?.autoStart) { - // startSyncServer(); + startSyncServer(); exposeSyncServer(globalPrefs.ngrokConfig); } diff --git a/packages/loot-core/src/server/post.ts b/packages/loot-core/src/server/post.ts index 50d4e1679c3..1fa28cd22a0 100644 --- a/packages/loot-core/src/server/post.ts +++ b/packages/loot-core/src/server/post.ts @@ -18,10 +18,8 @@ function throwIfNot200(res, text) { // Actual Sync Server may be exposed via a tunnel (e.g. ngrok). Tunnel errors should be treated as network errors. const tunnelErrorHeaders = ['ngrok-error-code']; - - const headerKeys = res.headers.keys().toArray(); - const tunnelError = tunnelErrorHeaders.some(tunnelErrorHeader => - headerKeys.includes(tunnelErrorHeader), + const tunnelError = tunnelErrorHeaders.some(header => + res.headers.has(header), ); if (tunnelError) { diff --git a/yarn.lock b/yarn.lock index d96859af24f..73bc00d88f4 100644 --- a/yarn.lock +++ b/yarn.lock @@ -6388,7 +6388,7 @@ __metadata: "actual-sync@file:../../../actual-server::locator=desktop-electron%40workspace%3Apackages%2Fdesktop-electron": version: 24.10.1 - resolution: "actual-sync@file:../../../actual-server#../../../actual-server::hash=4569dd&locator=desktop-electron%40workspace%3Apackages%2Fdesktop-electron" + resolution: "actual-sync@file:../../../actual-server#../../../actual-server::hash=462a94&locator=desktop-electron%40workspace%3Apackages%2Fdesktop-electron" dependencies: "@actual-app/crdt": "npm:2.1.0" "@actual-app/web": "npm:24.10.1" @@ -6408,7 +6408,7 @@ __metadata: nordigen-node: "npm:^1.4.0" uuid: "npm:^9.0.0" winston: "npm:^3.14.2" - checksum: 10/3c38b1628ec665ee4129184f8b44ebdc51ced8561d4b144578f500401b32faf49a590bf4303f3429ffeca179dcb4b67d37da7ddc4e1890f22f76a159b4976b20 + checksum: 10/6106458ead5b8b7e2a948fee74a9b46633ebfd8096af1cc2c3e3946ab9ff571d60018bbf805948b4611a0f5cd93da5ca216290ec3a8ef68a367a44fe9cb8d429 languageName: node linkType: hard From 804425fd06879d19c594ca9760e61194263e606b Mon Sep 17 00:00:00 2001 From: Mike Clark <mclarkgb@gmail.com> Date: Mon, 21 Oct 2024 19:14:22 +0100 Subject: [PATCH 26/55] resetting state if not available to avoid hard crashes --- packages/desktop-electron/index.ts | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/packages/desktop-electron/index.ts b/packages/desktop-electron/index.ts index 6721a6c25dd..44bbc65f65a 100644 --- a/packages/desktop-electron/index.ts +++ b/packages/desktop-electron/index.ts @@ -73,7 +73,8 @@ async function loadGlobalPrefs() { ), ); } catch (e) { - console.log('Could not load global state'); + console.error('Could not load global state - resetting', e); + state = {}; } return state; From a1de454f602475ed41bb321441e2563e388fbe3a Mon Sep 17 00:00:00 2001 From: Mike Clark <mclarkgb@gmail.com> Date: Mon, 21 Oct 2024 21:15:28 +0100 Subject: [PATCH 27/55] small updates until I figure out what the issue is --- .../src/components/manager/ConfigServer.tsx | 12 +++++++++++- packages/desktop-electron/index.ts | 2 +- 2 files changed, 12 insertions(+), 2 deletions(-) diff --git a/packages/desktop-client/src/components/manager/ConfigServer.tsx b/packages/desktop-client/src/components/manager/ConfigServer.tsx index 7712b39af5f..5754929156d 100644 --- a/packages/desktop-client/src/components/manager/ConfigServer.tsx +++ b/packages/desktop-client/src/components/manager/ConfigServer.tsx @@ -4,17 +4,27 @@ import { Trans, useTranslation } from 'react-i18next'; import { isElectron } from 'loot-core/src/shared/environment'; +import { useActions } from '../../hooks/useActions'; import { useNavigate } from '../../hooks/useNavigate'; import { theme } from '../../style'; import { Button } from '../common/Button2'; import { Text } from '../common/Text'; import { View } from '../common/View'; +import { useSetServerURL } from '../ServerContext'; import { Title } from './subscribe/common'; export function ConfigServer() { const { t } = useTranslation(); const navigate = useNavigate(); + const setServerUrl = useSetServerURL(); + const { loggedIn } = useActions(); + + async function onSkip() { + await setServerUrl(null); + await loggedIn(); + navigate('/'); + } return ( <View style={{ maxWidth: 500, marginTop: -30 }}> @@ -54,7 +64,7 @@ export function ConfigServer() { </Button> </> )} - <Button onPress={() => navigate('/')}>I don’t want a server</Button> + <Button onPress={onSkip}>I don’t want a server</Button> </View> ); } diff --git a/packages/desktop-electron/index.ts b/packages/desktop-electron/index.ts index 44bbc65f65a..eec6214d012 100644 --- a/packages/desktop-electron/index.ts +++ b/packages/desktop-electron/index.ts @@ -73,7 +73,7 @@ async function loadGlobalPrefs() { ), ); } catch (e) { - console.error('Could not load global state - resetting', e); + console.info('Could not load global state - using defaults'); // This could be the first time running the app - no global-store.json state = {}; } From 6d0363f7c743d0510f16a75483f0513290f1d4e8 Mon Sep 17 00:00:00 2001 From: Mike Clark <mclarkgb@gmail.com> Date: Sun, 27 Oct 2024 18:25:58 +0000 Subject: [PATCH 28/55] settings --- .../src/components/manager/BudgetList.tsx | 42 ----- .../manager/ConfigInternalSyncServer.tsx | 146 +++++++++++++++--- yarn.lock | 4 +- 3 files changed, 130 insertions(+), 62 deletions(-) diff --git a/packages/desktop-client/src/components/manager/BudgetList.tsx b/packages/desktop-client/src/components/manager/BudgetList.tsx index eb335c3f652..1cd0f5e3c96 100644 --- a/packages/desktop-client/src/components/manager/BudgetList.tsx +++ b/packages/desktop-client/src/components/manager/BudgetList.tsx @@ -23,7 +23,6 @@ import { type SyncedLocalFile, } from 'loot-core/types/file'; -import { useGlobalPref } from '../../hooks/useGlobalPref'; import { useInitialMount } from '../../hooks/useInitialMount'; import { useMetadataPref } from '../../hooks/useMetadataPref'; import { AnimatedLoading } from '../../icons/AnimatedLoading'; @@ -442,27 +441,6 @@ export function BudgetList({ showHeader = true, quickSwitchMode = false }) { } }; - const startActualServer = async () => { - await globalThis.Actual.startActualServer('v24.10.1'); - }; - - const [ngrokConfig] = useGlobalPref('ngrokConfig'); - const exposeActualServer = async () => { - const hasRequiredNgrokSettings = - ngrokConfig?.authToken && ngrokConfig?.port && ngrokConfig?.domain; - if (hasRequiredNgrokSettings) { - const url = await globalThis.Actual.exposeActualServer({ - authToken: ngrokConfig.authToken, - port: ngrokConfig.port, - domain: ngrokConfig.domain, - }); - - console.info('exposing actual at: ' + url); - } else { - console.info('ngrok settings not set'); - } - }; - return ( <View style={{ @@ -537,26 +515,6 @@ export function BudgetList({ showHeader = true, quickSwitchMode = false }) { <Trans>Create test file</Trans> </Button> )} - <Button - variant="primary" - onPress={startActualServer} - style={{ - ...narrowButtonStyle, - marginLeft: 10, - }} - > - Start Server - </Button> - <Button - variant="primary" - onPress={exposeActualServer} - style={{ - ...narrowButtonStyle, - marginLeft: 10, - }} - > - Expose Server - </Button> </View> )} </View> diff --git a/packages/desktop-client/src/components/manager/ConfigInternalSyncServer.tsx b/packages/desktop-client/src/components/manager/ConfigInternalSyncServer.tsx index 07595c0cf4a..7e0be3ddf98 100644 --- a/packages/desktop-client/src/components/manager/ConfigInternalSyncServer.tsx +++ b/packages/desktop-client/src/components/manager/ConfigInternalSyncServer.tsx @@ -1,40 +1,150 @@ // @ts-strict-ignore import React, { useState, useEffect, useCallback } from 'react'; +import { Group, NumberField } from 'react-aria-components'; import { Trans, useTranslation } from 'react-i18next'; +import { useGlobalPref } from '../../hooks/useGlobalPref'; import { useNavigate } from '../../hooks/useNavigate'; -import { theme } from '../../style'; +import { useResponsive } from '../../ResponsiveProvider'; +import { styles, theme } from '../../style'; import { Button, ButtonWithLoading } from '../common/Button2'; +import { Input } from '../common/Input'; +import { Label } from '../common/Label'; import { Text } from '../common/Text'; import { View } from '../common/View'; -import { useServerURL, useSetServerURL } from '../ServerContext'; import { Title } from './subscribe/common'; export function ConfigInternalSyncServer() { const { t } = useTranslation(); const navigate = useNavigate(); + const [serverConfig, setServerConfig] = useState({ + port: 5007, + certificatePath: undefined, // merge with current global.json pref + ngrokDomain: undefined, + ngrokAuthToken: undefined, + }); + + const startActualServer = async () => { + await globalThis.Actual.startActualServer('v24.10.1'); + }; + + const { isNarrowWidth } = useResponsive(); + const narrowButtonStyle = isNarrowWidth + ? { + height: styles.mobileMinHeight, + } + : {}; + + const [ngrokConfig] = useGlobalPref('ngrokConfig'); + const exposeActualServer = async () => { + const hasRequiredNgrokSettings = + ngrokConfig?.authToken && ngrokConfig?.port && ngrokConfig?.domain; + if (hasRequiredNgrokSettings) { + const url = await globalThis.Actual.exposeActualServer({ + authToken: ngrokConfig.authToken, + port: ngrokConfig.port, + domain: ngrokConfig.domain, + }); + + console.info('exposing actual at: ' + url); + } else { + console.info('ngrok settings not set'); + } + }; + + const handleChange = (name: keyof typeof serverConfig, event) => { + console.info(event); + const { value } = event.target; + setServerConfig({ + ...serverConfig, + [name]: value, + }); + }; return ( <View style={{ maxWidth: 500, marginTop: -30 }}> <Title text={t('Server configuration')} /> - <Text - style={{ - fontSize: 16, - color: theme.pageText, - lineHeight: 1.5, - }} - > - <Trans> - Actual can setup a server for you to sync your data across devices. It - can either run on your computer or on a server. If you want to run it - on your computer, you can use the button below to start the server. If - you want to run it on a server, you can enter the URL of the server - below. - </Trans> - </Text> - <Button onPress={() => navigate(-1)}>Back</Button> + <View> + <Text + style={{ + fontSize: 16, + color: theme.pageText, + lineHeight: 1.5, + }} + > + <Trans> + Actual can setup a server for you to sync your data across devices. + It can either run on your computer or on a server. If you want to + run it on your computer, you can use the button below to start the + server. If you want to run it on a server, you can enter the URL of + the server below. + </Trans> + </Text> + + <View> + <Label title={t('Port')} /> + <Input + type="number" + value={serverConfig.port} + name={t('Port')} + onChange={event => handleChange('port', event)} + /> + </View> + + <View> + <Label title={t('SSL Certificate (optional)')} /> + <Input + type="file" + value={serverConfig.certificatePath} + name={t('Certificate')} + onChange={event => handleChange('certificatePath', event)} + /> + </View> + + <View> + <Label title={t('Ngrok custom domain (optional)')} /> + <Input + type="text" + value={serverConfig.ngrokDomain} + name={t('Ngrok Custom Domain')} + onChange={event => handleChange('ngrokDomain', event)} + /> + </View> + <View> + <Label title={t('Ngrok auth token (optional)')} /> + <Input + type="text" + value={serverConfig.ngrokAuthToken} + name={t('Ngrok Auth Token')} + onChange={event => handleChange('ngrokAuthToken', event)} + /> + </View> + </View> + <View> + <Button onPress={() => navigate(-1)}>Back</Button> + <Button + variant="primary" + onPress={startActualServer} + style={{ + ...narrowButtonStyle, + marginLeft: 10, + }} + > + Start Server + </Button> + <Button + variant="primary" + onPress={exposeActualServer} + style={{ + ...narrowButtonStyle, + marginLeft: 10, + }} + > + Expose Server + </Button> + </View> </View> ); } diff --git a/yarn.lock b/yarn.lock index aa5c12d80ba..9ec2d694a7e 100644 --- a/yarn.lock +++ b/yarn.lock @@ -6603,7 +6603,7 @@ __metadata: "actual-sync@file:../../../actual-server::locator=desktop-electron%40workspace%3Apackages%2Fdesktop-electron": version: 24.10.1 - resolution: "actual-sync@file:../../../actual-server#../../../actual-server::hash=d86144&locator=desktop-electron%40workspace%3Apackages%2Fdesktop-electron" + resolution: "actual-sync@file:../../../actual-server#../../../actual-server::hash=fefd95&locator=desktop-electron%40workspace%3Apackages%2Fdesktop-electron" dependencies: "@actual-app/crdt": "npm:2.1.0" "@actual-app/web": "npm:24.10.1" @@ -6623,7 +6623,7 @@ __metadata: nordigen-node: "npm:^1.4.0" uuid: "npm:^9.0.0" winston: "npm:^3.14.2" - checksum: 10/ad0fd9e40428f3914ccc4ebed056c0f6cfe77676b53becea09d6e528a6c0dce564cd4f6c1324a441f426545853a0a0140ef3a3583606a50ba2cc1427dc679834 + checksum: 10/ed0b8268d4433477f654900b0e78a0f95eccd286c7abbce88f5d6865a9f16c3377dc5811d52d67745fdbf5ad4c84ece443d73008017e6713258d4fbca024bad4 languageName: node linkType: hard From f3c2743ce3583b3c7a3492ee253f5b1627fb5a3d Mon Sep 17 00:00:00 2001 From: Mike Clark <mclarkgb@gmail.com> Date: Sun, 27 Oct 2024 20:28:54 +0000 Subject: [PATCH 29/55] remove it --- packages/desktop-electron/index.ts | 1 - 1 file changed, 1 deletion(-) diff --git a/packages/desktop-electron/index.ts b/packages/desktop-electron/index.ts index eec6214d012..3b8abd93e21 100644 --- a/packages/desktop-electron/index.ts +++ b/packages/desktop-electron/index.ts @@ -229,7 +229,6 @@ async function exposeSyncServer(ngrokConfig: GlobalPrefs['ngrokConfig']) { const listener = await ngrok.forward({ schemes: ['https'], // change this to https and bind certificate - may need to generate cert and store in user-data addr: ngrokConfig.port, - host_header: `localhost:${ngrokConfig.port}`, authtoken: ngrokConfig.authToken, domain: ngrokConfig.domain, // crt: fs.readFileSync("crt.pem", "utf8"), From 76b2918ae65bb6909976d059d7373bb9df966ca9 Mon Sep 17 00:00:00 2001 From: Mike Clark <mclarkgb@gmail.com> Date: Sun, 27 Oct 2024 22:53:08 +0000 Subject: [PATCH 30/55] thoughts --- packages/desktop-electron/index.ts | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/packages/desktop-electron/index.ts b/packages/desktop-electron/index.ts index 3b8abd93e21..01b40bb1235 100644 --- a/packages/desktop-electron/index.ts +++ b/packages/desktop-electron/index.ts @@ -173,6 +173,15 @@ function startSyncServer() { envVariables = { ...envVariables, ACTUAL_WEB_ROOT: webRoot }; + // const customDomain = globalPrefs?.ngrokConfig?.domain; + + // if (customDomain) { + // // If we expose on a custom domain via ngrok we need to tell the server to allow it to work as a proxy + // // I'm not sure about this. It needs a CIDR block. I'm not sure what to put here or if it is needed. + // // It's possible this setting will prevent the annoying auth issue where I have to login every day + // envVariables = { ...envVariables, ACTUAL_TRUSTED_PROXIES: customDomain }; + // } + if (!fs.existsSync(syncServerConfig.ACTUAL_SERVER_FILES)) { // create directories if they do not exit - actual-sync doesn't do it for us... mkdir(syncServerConfig.ACTUAL_SERVER_FILES, { recursive: true }); From 9ff112c31ee6b22197d8589dc4291c694aa69b6e Mon Sep 17 00:00:00 2001 From: Mike Clark <mclarkgb@gmail.com> Date: Mon, 28 Oct 2024 16:47:35 +0000 Subject: [PATCH 31/55] waiting on sync server before starting --- packages/desktop-electron/index.ts | 111 +++++++++++++++++------------ 1 file changed, 65 insertions(+), 46 deletions(-) diff --git a/packages/desktop-electron/index.ts b/packages/desktop-electron/index.ts index 01b40bb1235..4acb096eff3 100644 --- a/packages/desktop-electron/index.ts +++ b/packages/desktop-electron/index.ts @@ -155,54 +155,64 @@ function startSyncServer() { // NOTE: config.json parameters will be relative to THIS directory at the moment - may need a fix? // Or we can override the config.json location when starting the process - try { - let envVariables: Env = { - ...process.env, // required - ACTUAL_PORT: `${syncServerConfig.port}`, - ACTUAL_SERVER_FILES: `${syncServerConfig.ACTUAL_SERVER_FILES}`, - ACTUAL_USER_FILES: `${syncServerConfig.ACTUAL_USER_FILES}`, - ACTUAL_DATA_DIR: `${syncServerConfig.ACTUAL_SERVER_DATA_DIR}`, - }; - - const webRoot = path.resolve( - __dirname, - isDev - ? '../../../node_modules/@actual-app/web/build/' // workspace node_modules - : '../node_modules/@actual-app/web/build/', // location of packaged module - ); + let envVariables: Env = { + ...process.env, // required + ACTUAL_PORT: `${syncServerConfig.port}`, + ACTUAL_SERVER_FILES: `${syncServerConfig.ACTUAL_SERVER_FILES}`, + ACTUAL_USER_FILES: `${syncServerConfig.ACTUAL_USER_FILES}`, + ACTUAL_DATA_DIR: `${syncServerConfig.ACTUAL_SERVER_DATA_DIR}`, + }; - envVariables = { ...envVariables, ACTUAL_WEB_ROOT: webRoot }; + const webRoot = path.resolve( + __dirname, + isDev + ? '../../../node_modules/@actual-app/web/build/' // workspace node_modules + : '../node_modules/@actual-app/web/build/', // location of packaged module + ); - // const customDomain = globalPrefs?.ngrokConfig?.domain; + envVariables = { ...envVariables, ACTUAL_WEB_ROOT: webRoot }; - // if (customDomain) { - // // If we expose on a custom domain via ngrok we need to tell the server to allow it to work as a proxy - // // I'm not sure about this. It needs a CIDR block. I'm not sure what to put here or if it is needed. - // // It's possible this setting will prevent the annoying auth issue where I have to login every day - // envVariables = { ...envVariables, ACTUAL_TRUSTED_PROXIES: customDomain }; - // } + // const customDomain = globalPrefs?.ngrokConfig?.domain; - if (!fs.existsSync(syncServerConfig.ACTUAL_SERVER_FILES)) { - // create directories if they do not exit - actual-sync doesn't do it for us... - mkdir(syncServerConfig.ACTUAL_SERVER_FILES, { recursive: true }); - } + // if (customDomain) { + // // If we expose on a custom domain via ngrok we need to tell the server to allow it to work as a proxy + // // I'm not sure about this. It needs a CIDR block. I'm not sure what to put here or if it is needed. + // // It's possible this setting will prevent the annoying auth issue where I have to login every day + // envVariables = { ...envVariables, ACTUAL_TRUSTED_PROXIES: customDomain }; + // } - if (!fs.existsSync(syncServerConfig.ACTUAL_USER_FILES)) { - // create directories if they do not exit - actual-sync doesn't do it for us... - mkdir(syncServerConfig.ACTUAL_USER_FILES, { recursive: true }); - } + if (!fs.existsSync(syncServerConfig.ACTUAL_SERVER_FILES)) { + // create directories if they do not exit - actual-sync doesn't do it for us... + mkdir(syncServerConfig.ACTUAL_SERVER_FILES, { recursive: true }); + } - // TODO: make sure .migrate file is also in user-directory under actual-server + if (!fs.existsSync(syncServerConfig.ACTUAL_USER_FILES)) { + // create directories if they do not exit - actual-sync doesn't do it for us... + mkdir(syncServerConfig.ACTUAL_USER_FILES, { recursive: true }); + } - let forkOptions: ForkOptions = { - stdio: 'pipe', - env: envVariables, - }; + // TODO: make sure .migrate file is also in user-directory under actual-server - if (isDev) { - forkOptions = { ...forkOptions, execArgv: ['--inspect'] }; - } + let forkOptions: ForkOptions = { + stdio: 'pipe', + env: envVariables, + }; + if (isDev) { + forkOptions = { ...forkOptions, execArgv: ['--inspect'] }; + } + + const SYNC_SERVER_WAIT_TIMEOUT = 10000; // wait 10 seconds for the server to start - if it doesn't, throw an error + + const syncServerTimeout = new Promise<void>((_, reject) => { + setTimeout(() => { + const errorMessage = `Sync server failed to start within ${SYNC_SERVER_WAIT_TIMEOUT / 1000} seconds. Something is wrong. Please raise a github issue.`; + console.error(errorMessage); + reject(new Error(errorMessage)); + }, SYNC_SERVER_WAIT_TIMEOUT); + }); + + const syncServerPromise = new Promise<void>(async resolve => { actualServerProcess = utilityProcess.fork( serverPath, // This requires actual-server depencies (crdt) to be built before running electron - they need to be manually specified because actual-server doesn't get bundled [], @@ -211,18 +221,24 @@ function startSyncServer() { actualServerProcess.stdout?.on('data', (chunk: Buffer) => { // Send the Server console.log messages to the main browser window + const chunkValue = JSON.stringify(chunk.toString('utf8')); + if (chunkValue.includes('Listening on')) { + console.info('Actual Sync Server has started!'); + resolve(); // The server is running - resolve + } + clientWin?.webContents.executeJavaScript(` - console.info('Server Log:', ${JSON.stringify(chunk.toString('utf8'))})`); + console.info('Actual Sync Server Log:', ${chunkValue})`); }); actualServerProcess.stderr?.on('data', (chunk: Buffer) => { // Send the Server console.error messages out to the main browser window clientWin?.webContents.executeJavaScript(` - console.error('Server Log:', ${JSON.stringify(chunk.toString('utf8'))})`); + console.error('Actual Sync Server Log:', ${JSON.stringify(chunk.toString('utf8'))})`); }); - } catch (error) { - console.error(error); - } + }); + + return Promise.race([syncServerPromise, syncServerTimeout]); // Either the server has started or the timeout is reached } async function exposeSyncServer(ngrokConfig: GlobalPrefs['ngrokConfig']) { @@ -386,8 +402,11 @@ app.on('ready', async () => { globalPrefs = await loadGlobalPrefs(); // load global prefs if (globalPrefs.ngrokConfig?.autoStart) { - startSyncServer(); - exposeSyncServer(globalPrefs.ngrokConfig); + // wait for both server and ngrok to start before starting the Actual client to ensure server is available + await Promise.allSettled([ + startSyncServer(), + exposeSyncServer(globalPrefs.ngrokConfig), + ]); } protocol.handle('app', request => { From 312e85e3dd52f5d750eaa34e83c40d0e77560ac3 Mon Sep 17 00:00:00 2001 From: Mike Clark <mclarkgb@gmail.com> Date: Wed, 30 Oct 2024 11:35:12 +0000 Subject: [PATCH 32/55] fix timeout message --- packages/desktop-electron/index.ts | 20 +++++++++------ packages/loot-core/init-node.js | 39 ------------------------------ 2 files changed, 13 insertions(+), 46 deletions(-) delete mode 100644 packages/loot-core/init-node.js diff --git a/packages/desktop-electron/index.ts b/packages/desktop-electron/index.ts index 4acb096eff3..84ef85fd40e 100644 --- a/packages/desktop-electron/index.ts +++ b/packages/desktop-electron/index.ts @@ -204,13 +204,7 @@ function startSyncServer() { const SYNC_SERVER_WAIT_TIMEOUT = 10000; // wait 10 seconds for the server to start - if it doesn't, throw an error - const syncServerTimeout = new Promise<void>((_, reject) => { - setTimeout(() => { - const errorMessage = `Sync server failed to start within ${SYNC_SERVER_WAIT_TIMEOUT / 1000} seconds. Something is wrong. Please raise a github issue.`; - console.error(errorMessage); - reject(new Error(errorMessage)); - }, SYNC_SERVER_WAIT_TIMEOUT); - }); + let syncServerStarted = false; const syncServerPromise = new Promise<void>(async resolve => { actualServerProcess = utilityProcess.fork( @@ -224,6 +218,7 @@ function startSyncServer() { const chunkValue = JSON.stringify(chunk.toString('utf8')); if (chunkValue.includes('Listening on')) { console.info('Actual Sync Server has started!'); + syncServerStarted = true; resolve(); // The server is running - resolve } @@ -238,6 +233,17 @@ function startSyncServer() { }); }); + const syncServerTimeout = new Promise<void>((_, reject) => { + setTimeout(() => { + if (!syncServerStarted) { + const errorMessage = `Sync server failed to start within ${SYNC_SERVER_WAIT_TIMEOUT / 1000} seconds. Something is wrong. Please raise a github issue.`; + console.error(errorMessage); + reject(new Error(errorMessage)); + } + }, SYNC_SERVER_WAIT_TIMEOUT); + }); + + // This aint working... return Promise.race([syncServerPromise, syncServerTimeout]); // Either the server has started or the timeout is reached } diff --git a/packages/loot-core/init-node.js b/packages/loot-core/init-node.js deleted file mode 100644 index 13b4df0f5c4..00000000000 --- a/packages/loot-core/init-node.js +++ /dev/null @@ -1,39 +0,0 @@ -import { dirname, basename } from 'path'; - -import fetch from 'node-fetch'; -import 'source-map-support/register'; - -// eslint-disable-next-line import/extensions -import bundle from './lib-dist/electron/bundle.desktop.js'; - -global.fetch = fetch; - -async function init(budgetPath) { - const dir = dirname(budgetPath); - const budgetId = basename(budgetPath); - await bundle.initEmbedded('0.0.147', true, dir); - await bundle.lib.send('load-budget', { id: budgetId }); - - return bundle.lib; -} - -async function run() { - const { send } = await init('/tmp/_test-budget'); - const accounts = await send('accounts-get'); - - await send('transaction-add', { - date: '2022-03-20', - account: accounts[0].id, - amount: 1000, - }); - - await new Promise(resolve => { - setTimeout(() => { - resolve(); - }, 5000); - }); - - await send('close-budget'); -} - -run(); From 4f667d07eb4766b6b456f7f3c083adec4af244dc Mon Sep 17 00:00:00 2001 From: Mike Clark <mclarkgb@gmail.com> Date: Wed, 30 Oct 2024 14:02:51 +0000 Subject: [PATCH 33/55] note --- packages/desktop-electron/index.ts | 3 ++- yarn.lock | 4 ++-- 2 files changed, 4 insertions(+), 3 deletions(-) diff --git a/packages/desktop-electron/index.ts b/packages/desktop-electron/index.ts index 84ef85fd40e..402ecbe4f0d 100644 --- a/packages/desktop-electron/index.ts +++ b/packages/desktop-electron/index.ts @@ -217,9 +217,10 @@ function startSyncServer() { // Send the Server console.log messages to the main browser window const chunkValue = JSON.stringify(chunk.toString('utf8')); if (chunkValue.includes('Listening on')) { + // can we send a signal from the server instead of doing this? console.info('Actual Sync Server has started!'); syncServerStarted = true; - resolve(); // The server is running - resolve + resolve(); } clientWin?.webContents.executeJavaScript(` diff --git a/yarn.lock b/yarn.lock index 9ec2d694a7e..4a6706e2fc8 100644 --- a/yarn.lock +++ b/yarn.lock @@ -6603,7 +6603,7 @@ __metadata: "actual-sync@file:../../../actual-server::locator=desktop-electron%40workspace%3Apackages%2Fdesktop-electron": version: 24.10.1 - resolution: "actual-sync@file:../../../actual-server#../../../actual-server::hash=fefd95&locator=desktop-electron%40workspace%3Apackages%2Fdesktop-electron" + resolution: "actual-sync@file:../../../actual-server#../../../actual-server::hash=f2a23f&locator=desktop-electron%40workspace%3Apackages%2Fdesktop-electron" dependencies: "@actual-app/crdt": "npm:2.1.0" "@actual-app/web": "npm:24.10.1" @@ -6623,7 +6623,7 @@ __metadata: nordigen-node: "npm:^1.4.0" uuid: "npm:^9.0.0" winston: "npm:^3.14.2" - checksum: 10/ed0b8268d4433477f654900b0e78a0f95eccd286c7abbce88f5d6865a9f16c3377dc5811d52d67745fdbf5ad4c84ece443d73008017e6713258d4fbca024bad4 + checksum: 10/c546a8d3b4f83e511558f676c9735e39827c4440de6cf49cadc05072b324d9f36c938c4134bebbf256297e79c78b355225e23890f22482f4d2153e6496c469fe languageName: node linkType: hard From 79f648f48b15dade314c123413492b9eff2339c2 Mon Sep 17 00:00:00 2001 From: Mike Clark <mclarkgb@gmail.com> Date: Mon, 4 Nov 2024 11:26:58 +0000 Subject: [PATCH 34/55] use env variable instead of https agent for default fetch --- packages/desktop-electron/index.ts | 30 ++++++++++++++++++++++----- packages/loot-core/src/server/main.ts | 17 --------------- yarn.lock | 13 +++++++++--- 3 files changed, 35 insertions(+), 25 deletions(-) diff --git a/packages/desktop-electron/index.ts b/packages/desktop-electron/index.ts index 402ecbe4f0d..93781fbfdb0 100644 --- a/packages/desktop-electron/index.ts +++ b/packages/desktop-electron/index.ts @@ -57,7 +57,6 @@ if (!isDev || !process.env.ACTUAL_DATA_DIR) { let clientWin: BrowserWindow | null; let serverProcess: UtilityProcess | null; let actualServerProcess: UtilityProcess | null; -let globalPrefs: Partial<GlobalPrefs> | null; if (isDev) { process.traceProcessWarnings = true; @@ -80,11 +79,32 @@ async function loadGlobalPrefs() { return state; } -function createBackgroundProcess() { +async function createBackgroundProcess() { + const globalPrefs = await loadGlobalPrefs(); // ensures we have the latest settings even when restarting the server + let envVariables: Env = { + ...process.env, // required + }; + + if (globalPrefs?.['server-self-signed-cert']) { + envVariables = { + ...envVariables, + NODE_EXTRA_CA_CERTS: globalPrefs?.['server-self-signed-cert'], // add self signed cert to env variable - fetch can pick that up + }; + } + + let forkOptions: ForkOptions = { + stdio: 'pipe', + env: envVariables, + }; + + if (isDev) { + forkOptions = { ...forkOptions, execArgv: ['--inspect'] }; + } + serverProcess = utilityProcess.fork( __dirname + '/server.js', ['--subprocess', app.getVersion()], - isDev ? { execArgv: ['--inspect'], stdio: 'pipe' } : { stdio: 'pipe' }, + forkOptions, ); serverProcess.stdout?.on('data', (chunk: Buffer) => { @@ -406,7 +426,7 @@ app.on('ready', async () => { // file no matter what URL it is. This allows us to use react-router // on the frontend - globalPrefs = await loadGlobalPrefs(); // load global prefs + const globalPrefs = await loadGlobalPrefs(); // load global prefs if (globalPrefs.ngrokConfig?.autoStart) { // wait for both server and ngrok to start before starting the Actual client to ensure server is available @@ -471,7 +491,7 @@ app.on('ready', async () => { console.log('Suspending', new Date()); }); - createBackgroundProcess(); + await createBackgroundProcess(); }); app.on('window-all-closed', () => { diff --git a/packages/loot-core/src/server/main.ts b/packages/loot-core/src/server/main.ts index d695e95de3b..819fc45991c 100644 --- a/packages/loot-core/src/server/main.ts +++ b/packages/loot-core/src/server/main.ts @@ -2155,23 +2155,6 @@ export async function initApp(isDev, socketName) { } } - const selfSignedCertPath = await asyncStorage.getItem( - 'server-self-signed-cert', - ); - - if (selfSignedCertPath) { - try { - const selfSignedCert = await fs.readFile(selfSignedCertPath); - https.globalAgent.options.ca = [...tls.rootCertificates, selfSignedCert]; - } catch (error) { - console.error( - 'Unable to add the self signed certificate, removing its reference', - error, - ); - await asyncStorage.removeItem('server-self-signed-cert'); - } - } - const url = await asyncStorage.getItem('server-url'); if (!url) { diff --git a/yarn.lock b/yarn.lock index 4a6706e2fc8..a727260886a 100644 --- a/yarn.lock +++ b/yarn.lock @@ -55,7 +55,14 @@ __metadata: languageName: unknown linkType: soft -"@actual-app/web@npm:24.10.1, @actual-app/web@workspace:packages/desktop-client": +"@actual-app/web@npm:24.10.1": + version: 24.10.1 + resolution: "@actual-app/web@npm:24.10.1" + checksum: 10/ef5a3c4aa17a4274a7b3faecf7bee9d0845adcdf42a4fab4680224be8ddbf9a2b9e63624a71be5282deba926795b210e84be7fe5185aefeec7ee0c26829b04ea + languageName: node + linkType: hard + +"@actual-app/web@workspace:packages/desktop-client": version: 0.0.0-use.local resolution: "@actual-app/web@workspace:packages/desktop-client" dependencies: @@ -6603,7 +6610,7 @@ __metadata: "actual-sync@file:../../../actual-server::locator=desktop-electron%40workspace%3Apackages%2Fdesktop-electron": version: 24.10.1 - resolution: "actual-sync@file:../../../actual-server#../../../actual-server::hash=f2a23f&locator=desktop-electron%40workspace%3Apackages%2Fdesktop-electron" + resolution: "actual-sync@file:../../../actual-server#../../../actual-server::hash=00708e&locator=desktop-electron%40workspace%3Apackages%2Fdesktop-electron" dependencies: "@actual-app/crdt": "npm:2.1.0" "@actual-app/web": "npm:24.10.1" @@ -6623,7 +6630,7 @@ __metadata: nordigen-node: "npm:^1.4.0" uuid: "npm:^9.0.0" winston: "npm:^3.14.2" - checksum: 10/c546a8d3b4f83e511558f676c9735e39827c4440de6cf49cadc05072b324d9f36c938c4134bebbf256297e79c78b355225e23890f22482f4d2153e6496c469fe + checksum: 10/aa1f647a8258674bd5d47e881987578e291da2f7bafbd70c3f717d8fcc6f7ed9810e9f2b53cb1975e13505aaeb5768c1eea12d6a2a368643aeca12215f7fc4a7 languageName: node linkType: hard From 22651649eb0d8b6abaa253a29f5d74865d7e3f2b Mon Sep 17 00:00:00 2001 From: Mike Clark <mclarkgb@gmail.com> Date: Mon, 4 Nov 2024 11:35:23 +0000 Subject: [PATCH 35/55] updating root ca impl to use node env variable for more support --- packages/desktop-electron/index.ts | 45 +++++++++++++++++++++++++-- packages/loot-core/src/server/main.ts | 17 ---------- 2 files changed, 43 insertions(+), 19 deletions(-) diff --git a/packages/desktop-electron/index.ts b/packages/desktop-electron/index.ts index 92649a96c1f..43e1c95101a 100644 --- a/packages/desktop-electron/index.ts +++ b/packages/desktop-electron/index.ts @@ -15,6 +15,8 @@ import { UtilityProcess, OpenDialogSyncOptions, SaveDialogOptions, + Env, + ForkOptions, } from 'electron'; import { copy, exists, remove } from 'fs-extra'; import promiseRetry from 'promise-retry'; @@ -26,6 +28,7 @@ import { } from './window-state'; import './security'; +import { GlobalPrefs } from 'loot-core/types/prefs'; const isDev = !app.isPackaged; // dev mode if not packaged @@ -56,11 +59,49 @@ if (isDev) { process.traceProcessWarnings = true; } -function createBackgroundProcess() { +async function loadGlobalPrefs() { + let state: GlobalPrefs | undefined = undefined; + try { + state = JSON.parse( + fs.readFileSync( + path.join(process.env.ACTUAL_DATA_DIR, 'global-store.json'), + 'utf8', + ), + ); + } catch (e) { + console.info('Could not load global state - using defaults'); // This could be the first time running the app - no global-store.json + state = {}; + } + + return state; +} + +async function createBackgroundProcess() { + const globalPrefs = await loadGlobalPrefs(); // ensures we have the latest settings - even when restarting the server + let envVariables: Env = { + ...process.env, // required + }; + + if (globalPrefs?.['server-self-signed-cert']) { + envVariables = { + ...envVariables, + NODE_EXTRA_CA_CERTS: globalPrefs?.['server-self-signed-cert'], // add self signed cert to env - fetch can pick it up + }; + } + + let forkOptions: ForkOptions = { + stdio: 'pipe', + env: envVariables, + }; + + if (isDev) { + forkOptions = { ...forkOptions, execArgv: ['--inspect'] }; + } + serverProcess = utilityProcess.fork( __dirname + '/server.js', ['--subprocess', app.getVersion()], - isDev ? { execArgv: ['--inspect'], stdio: 'pipe' } : { stdio: 'pipe' }, + forkOptions, ); serverProcess.stdout?.on('data', (chunk: Buffer) => { diff --git a/packages/loot-core/src/server/main.ts b/packages/loot-core/src/server/main.ts index a7979f03781..1e8019ee897 100644 --- a/packages/loot-core/src/server/main.ts +++ b/packages/loot-core/src/server/main.ts @@ -2149,23 +2149,6 @@ export async function initApp(isDev, socketName) { } } - const selfSignedCertPath = await asyncStorage.getItem( - 'server-self-signed-cert', - ); - - if (selfSignedCertPath) { - try { - const selfSignedCert = await fs.readFile(selfSignedCertPath); - https.globalAgent.options.ca = [...tls.rootCertificates, selfSignedCert]; - } catch (error) { - console.error( - 'Unable to add the self signed certificate, removing its reference', - error, - ); - await asyncStorage.removeItem('server-self-signed-cert'); - } - } - const url = await asyncStorage.getItem('server-url'); if (!url) { From 4400ff11d9d57040257e28b94655e7d8c0ed86f2 Mon Sep 17 00:00:00 2001 From: Mike Clark <mclarkgb@gmail.com> Date: Mon, 4 Nov 2024 11:54:57 +0000 Subject: [PATCH 36/55] release notes --- packages/desktop-electron/index.ts | 7 ++++--- packages/loot-core/src/server/main.ts | 2 -- upcoming-release-notes/3782.md | 6 ++++++ 3 files changed, 10 insertions(+), 5 deletions(-) create mode 100644 upcoming-release-notes/3782.md diff --git a/packages/desktop-electron/index.ts b/packages/desktop-electron/index.ts index 43e1c95101a..10fd2d95122 100644 --- a/packages/desktop-electron/index.ts +++ b/packages/desktop-electron/index.ts @@ -21,6 +21,8 @@ import { import { copy, exists, remove } from 'fs-extra'; import promiseRetry from 'promise-retry'; +import { GlobalPrefs } from 'loot-core/types/prefs'; + import { getMenu } from './menu'; import { get as getWindowState, @@ -28,7 +30,6 @@ import { } from './window-state'; import './security'; -import { GlobalPrefs } from 'loot-core/types/prefs'; const isDev = !app.isPackaged; // dev mode if not packaged @@ -60,11 +61,11 @@ if (isDev) { } async function loadGlobalPrefs() { - let state: GlobalPrefs | undefined = undefined; + let state: { [key: string]: unknown } | undefined = undefined; try { state = JSON.parse( fs.readFileSync( - path.join(process.env.ACTUAL_DATA_DIR, 'global-store.json'), + path.join(process.env.ACTUAL_DATA_DIR!, 'global-store.json'), 'utf8', ), ); diff --git a/packages/loot-core/src/server/main.ts b/packages/loot-core/src/server/main.ts index 1e8019ee897..ca1b5cb5bc9 100644 --- a/packages/loot-core/src/server/main.ts +++ b/packages/loot-core/src/server/main.ts @@ -1,7 +1,5 @@ // @ts-strict-ignore import './polyfills'; -import https from 'https'; -import tls from 'tls'; import * as injectAPI from '@actual-app/api/injected'; import * as CRDT from '@actual-app/crdt'; diff --git a/upcoming-release-notes/3782.md b/upcoming-release-notes/3782.md new file mode 100644 index 00000000000..f9084f5d32a --- /dev/null +++ b/upcoming-release-notes/3782.md @@ -0,0 +1,6 @@ +--- +category: Maintenance +authors: [MikesGlitch] +--- + +Updating the Desktop app self signed certificate implementation for greater support From 98174bb0bd987d028c5baeb83b6a34f6c727b606 Mon Sep 17 00:00:00 2001 From: Mike Clark <mclarkgb@gmail.com> Date: Mon, 4 Nov 2024 12:10:53 +0000 Subject: [PATCH 37/55] removing node-fetch --- packages/desktop-electron/package.json | 1 - packages/desktop-electron/server.ts | 5 --- packages/loot-core/package.json | 1 - .../platform/server/fetch/index.electron.ts | 5 +-- .../webpack/webpack.desktop.config.js | 2 +- yarn.lock | 40 ------------------- 6 files changed, 2 insertions(+), 52 deletions(-) diff --git a/packages/desktop-electron/package.json b/packages/desktop-electron/package.json index 7cf9e403cd3..4002e9220ac 100644 --- a/packages/desktop-electron/package.json +++ b/packages/desktop-electron/package.json @@ -87,7 +87,6 @@ "dependencies": { "better-sqlite3": "^9.6.0", "fs-extra": "^11.2.0", - "node-fetch": "^2.7.0", "promise-retry": "^2.0.1" }, "devDependencies": { diff --git a/packages/desktop-electron/server.ts b/packages/desktop-electron/server.ts index 1e6f8366787..d84e94a69d5 100644 --- a/packages/desktop-electron/server.ts +++ b/packages/desktop-electron/server.ts @@ -1,8 +1,3 @@ -// @ts-strict-ignore -import fetch from 'node-fetch'; - -global.fetch = fetch; - const lazyLoadBackend = async (isDev: boolean) => { try { const bundle = await import(process.env.lootCoreScript); diff --git a/packages/loot-core/package.json b/packages/loot-core/package.json index 8b971e62860..f92b6e6cb44 100644 --- a/packages/loot-core/package.json +++ b/packages/loot-core/package.json @@ -33,7 +33,6 @@ "md5": "^2.3.0", "memoize-one": "^6.0.0", "mitt": "^3.0.1", - "node-fetch": "^2.7.0", "reselect": "^4.1.8", "slash": "3.0.0", "throttleit": "^1.0.1", diff --git a/packages/loot-core/src/platform/server/fetch/index.electron.ts b/packages/loot-core/src/platform/server/fetch/index.electron.ts index 05a320bc0fe..2271fef0957 100644 --- a/packages/loot-core/src/platform/server/fetch/index.electron.ts +++ b/packages/loot-core/src/platform/server/fetch/index.electron.ts @@ -1,12 +1,9 @@ -// @ts-strict-ignore -import nodeFetch from 'node-fetch'; - export const fetch = async ( input: RequestInfo | URL, options?: RequestInit, ) => { try { - return await nodeFetch(input, { + return await globalThis.fetch(input, { ...options, headers: { ...options?.headers, diff --git a/packages/loot-core/webpack/webpack.desktop.config.js b/packages/loot-core/webpack/webpack.desktop.config.js index 50460f5597c..a6379dd5b85 100644 --- a/packages/loot-core/webpack/webpack.desktop.config.js +++ b/packages/loot-core/webpack/webpack.desktop.config.js @@ -28,7 +28,7 @@ module.exports = { 'pegjs', ], }, - externals: ['better-sqlite3', 'node-fetch'], + externals: ['better-sqlite3'], plugins: [ new webpack.IgnorePlugin({ resourceRegExp: /original-fs/, diff --git a/yarn.lock b/yarn.lock index 215b01b80d5..18c1b768ef9 100644 --- a/yarn.lock +++ b/yarn.lock @@ -8633,7 +8633,6 @@ __metadata: electron: "npm:30.0.6" electron-builder: "npm:24.13.3" fs-extra: "npm:^11.2.0" - node-fetch: "npm:^2.7.0" promise-retry: "npm:^2.0.1" typescript: "npm:^5.5.4" languageName: unknown @@ -13336,7 +13335,6 @@ __metadata: memoize-one: "npm:^6.0.0" mitt: "npm:^3.0.1" mockdate: "npm:^3.0.5" - node-fetch: "npm:^2.7.0" npm-run-all: "npm:^4.1.5" path-browserify: "npm:^1.0.1" peggy: "npm:3.0.2" @@ -14542,20 +14540,6 @@ __metadata: languageName: node linkType: hard -"node-fetch@npm:^2.7.0": - version: 2.7.0 - resolution: "node-fetch@npm:2.7.0" - dependencies: - whatwg-url: "npm:^5.0.0" - peerDependencies: - encoding: ^0.1.0 - peerDependenciesMeta: - encoding: - optional: true - checksum: 10/b24f8a3dc937f388192e59bcf9d0857d7b6940a2496f328381641cb616efccc9866e89ec43f2ec956bbd6c3d3ee05524ce77fe7b29ccd34692b3a16f237d6676 - languageName: node - linkType: hard - "node-fetch@npm:^3.3.2": version: 3.3.2 resolution: "node-fetch@npm:3.3.2" @@ -18233,13 +18217,6 @@ __metadata: languageName: node linkType: hard -"tr46@npm:~0.0.3": - version: 0.0.3 - resolution: "tr46@npm:0.0.3" - checksum: 10/8f1f5aa6cb232f9e1bdc86f485f916b7aa38caee8a778b378ffec0b70d9307873f253f5cbadbe2955ece2ac5c83d0dc14a77513166ccd0a0c7fe197e21396695 - languageName: node - linkType: hard - "trampa@npm:^1.0.0": version: 1.0.1 resolution: "trampa@npm:1.0.1" @@ -19357,13 +19334,6 @@ __metadata: languageName: node linkType: hard -"webidl-conversions@npm:^3.0.0": - version: 3.0.1 - resolution: "webidl-conversions@npm:3.0.1" - checksum: 10/b65b9f8d6854572a84a5c69615152b63371395f0c5dcd6729c45789052296df54314db2bc3e977df41705eacb8bc79c247cee139a63fa695192f95816ed528ad - languageName: node - linkType: hard - "webidl-conversions@npm:^4.0.2": version: 4.0.2 resolution: "webidl-conversions@npm:4.0.2" @@ -19509,16 +19479,6 @@ __metadata: languageName: node linkType: hard -"whatwg-url@npm:^5.0.0": - version: 5.0.0 - resolution: "whatwg-url@npm:5.0.0" - dependencies: - tr46: "npm:~0.0.3" - webidl-conversions: "npm:^3.0.0" - checksum: 10/f95adbc1e80820828b45cc671d97da7cd5e4ef9deb426c31bcd5ab00dc7103042291613b3ef3caec0a2335ed09e0d5ed026c940755dbb6d404e2b27f940fdf07 - languageName: node - linkType: hard - "whatwg-url@npm:^7.0.0": version: 7.1.0 resolution: "whatwg-url@npm:7.1.0" From 26adfecbdd1cb45b8859c1c93c54eeb3d7db2bdf Mon Sep 17 00:00:00 2001 From: Mike Clark <mclarkgb@gmail.com> Date: Mon, 4 Nov 2024 12:28:03 +0000 Subject: [PATCH 38/55] clean up --- packages/desktop-electron/index.ts | 2 -- packages/desktop-electron/server.ts | 8 +++++++- 2 files changed, 7 insertions(+), 3 deletions(-) diff --git a/packages/desktop-electron/index.ts b/packages/desktop-electron/index.ts index 10fd2d95122..33085a2b22b 100644 --- a/packages/desktop-electron/index.ts +++ b/packages/desktop-electron/index.ts @@ -21,8 +21,6 @@ import { import { copy, exists, remove } from 'fs-extra'; import promiseRetry from 'promise-retry'; -import { GlobalPrefs } from 'loot-core/types/prefs'; - import { getMenu } from './menu'; import { get as getWindowState, diff --git a/packages/desktop-electron/server.ts b/packages/desktop-electron/server.ts index d84e94a69d5..65810bc6fed 100644 --- a/packages/desktop-electron/server.ts +++ b/packages/desktop-electron/server.ts @@ -1,10 +1,16 @@ const lazyLoadBackend = async (isDev: boolean) => { + if (process.env.lootCoreScript === undefined) { + throw new Error( + 'The environment variable `lootCoreScript` is not defined. Please define it to point to the server bundle.', + ); + } + try { const bundle = await import(process.env.lootCoreScript); bundle.initApp(isDev); } catch (error) { console.error('Failed to init the server bundle:', error); - throw new Error(`Failed to init the server bundle: ${error.message}`); + throw new Error(`Failed to init the server bundle: ${error?.message}`); } }; From 8cfd39fdc4504c723d9a71806ca8c81d57ae316d Mon Sep 17 00:00:00 2001 From: Mike Clark <mclarkgb@gmail.com> Date: Mon, 4 Nov 2024 12:30:39 +0000 Subject: [PATCH 39/55] error message --- packages/desktop-electron/server.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/desktop-electron/server.ts b/packages/desktop-electron/server.ts index 65810bc6fed..cf8a79f2ec9 100644 --- a/packages/desktop-electron/server.ts +++ b/packages/desktop-electron/server.ts @@ -10,7 +10,7 @@ const lazyLoadBackend = async (isDev: boolean) => { bundle.initApp(isDev); } catch (error) { console.error('Failed to init the server bundle:', error); - throw new Error(`Failed to init the server bundle: ${error?.message}`); + throw new Error(`Failed to init the server bundle: ${error}`); } }; From 78fbebe80e7f50670d7991a2625ccbf919adcd5d Mon Sep 17 00:00:00 2001 From: Mike Clark <mclarkgb@gmail.com> Date: Mon, 4 Nov 2024 15:52:49 +0000 Subject: [PATCH 40/55] adding the sync server into the workspace as an experiment --- bin/package-electron | 1 - packages/desktop-electron/package.json | 2 +- packages/sync-server/.dockerignore | 12 + packages/sync-server/.editorconfig | 11 + packages/sync-server/.eslintignore | 6 + packages/sync-server/.eslintrc.cjs | 21 + packages/sync-server/.gitignore | 33 + packages/sync-server/.node-version | 1 + packages/sync-server/.prettierrc.json | 4 + packages/sync-server/.yarnrc.yml | 3 + packages/sync-server/Dockerfile | 25 + packages/sync-server/LICENSE.txt | 7 + packages/sync-server/README.md | 17 + packages/sync-server/app.js | 11 + packages/sync-server/babel.config.json | 3 + packages/sync-server/docker-compose.yml | 22 + .../sync-server/docker/download-artifacts.sh | 14 + .../sync-server/docker/edge-alpine.Dockerfile | 36 + .../sync-server/docker/edge-ubuntu.Dockerfile | 36 + .../docker/stable-alpine.Dockerfile | 26 + .../docker/stable-ubuntu.Dockerfile | 26 + packages/sync-server/jest.config.json | 14 + packages/sync-server/jest.global-setup.js | 10 + packages/sync-server/jest.global-teardown.js | 5 + .../1694360000000-create-folders.js | 24 + .../1694360479680-create-account-db.js | 30 + .../1694362247011-create-secret-table.js | 16 + .../1702667624000-rename-nordigen-secrets.js | 19 + packages/sync-server/package.json | 66 + packages/sync-server/src/account-db.js | 111 ++ packages/sync-server/src/app-account.js | 101 + .../sync-server/src/app-gocardless/README.md | 164 ++ .../src/app-gocardless/app-gocardless.js | 261 +++ .../src/app-gocardless/bank-factory.js | 111 ++ .../banks/FORTUNEO_FTNOFRP1XXX.js | 62 + .../app-gocardless/banks/abanca-caglesmm.js | 24 + .../banks/american-express-aesudef1.js | 55 + .../banks/bancsabadell-bsabesbbb.js | 37 + .../app-gocardless/banks/bank.interface.ts | 44 + .../banks/bankinter-bkbkesmm.js | 43 + .../app-gocardless/banks/belfius_gkccbebb.js | 24 + .../banks/berliner_sparkasse_beladebexxx.js | 98 + .../app-gocardless/banks/bnp-be-gebabebb.js | 79 + .../src/app-gocardless/banks/cbc_cregbebb.js | 36 + .../banks/danskebank-dabno22.js | 60 + .../app-gocardless/banks/easybank-bawaatww.js | 61 + .../src/app-gocardless/banks/ing-ingbrobu.js | 58 + .../src/app-gocardless/banks/ing-ingddeff.js | 64 + .../app-gocardless/banks/ing-pl-ingbplpw.js | 61 + .../app-gocardless/banks/integration-bank.js | 98 + .../src/app-gocardless/banks/kbc_kredbebb.js | 36 + .../banks/mbank-retail-brexplpw.js | 57 + .../banks/nationwide-naiagb21.js | 46 + .../app-gocardless/banks/nbg_ethngraaxxx.js | 71 + .../banks/norwegian-xx-norwnok1.js | 97 + .../app-gocardless/banks/revolut_revolt21.js | 60 + .../banks/sandboxfinance-sfin0000.js | 59 + .../app-gocardless/banks/seb-kort-bank-ab.js | 71 + .../src/app-gocardless/banks/seb-privat.js | 47 + .../app-gocardless/banks/sparnord-spnodk22.js | 30 + .../banks/spk-karlsruhe-karsde66.js | 98 + .../spk-marburg-biedenkopf-heladef1mar.js | 65 + .../banks/spk-worms-alzey-ried-malade51wor.js | 29 + .../banks/tests/FORTUNEO_FTNOFRP1XXX.spec.js | 210 ++ .../banks/tests/abanca-caglesmm.spec.js | 25 + .../tests/bancsabadell-bsabesbbb.spec.js | 57 + .../banks/tests/belfius_gkccbebb.spec.js | 21 + .../banks/tests/cbc_cregbebb.spec.js | 32 + .../banks/tests/easybank-bawaatww.spec.js | 54 + .../banks/tests/ing-ingddeff.spec.js | 300 +++ .../banks/tests/ing-pl-ingbplpw.spec.js | 200 ++ .../banks/tests/integration-bank.spec.js | 157 ++ .../banks/tests/kbc_kredbebb.spec.js | 36 + .../banks/tests/mbank-retail-brexplpw.spec.js | 169 ++ .../banks/tests/nationwide-naiagb21.spec.js | 105 + .../banks/tests/nbg_ethngraaxxx.spec.js | 48 + .../banks/tests/revolut_revolt21.spec.js | 46 + .../tests/sandboxfinance-sfin0000.spec.js | 131 ++ ...spk-marburg-biedenkopf-heladef1mar.spec.js | 254 +++ .../banks/tests/virgin_nrnbgb22.spec.js | 65 + .../extract-payeeName-from-remittanceInfo.js | 36 + .../app-gocardless/banks/virgin_nrnbgb22.js | 39 + .../sync-server/src/app-gocardless/errors.js | 84 + .../app-gocardless/gocardless-node.types.ts | 487 +++++ .../src/app-gocardless/gocardless.types.ts | 93 + .../sync-server/src/app-gocardless/link.html | 18 + .../services/gocardless-service.js | 621 ++++++ .../app-gocardless/services/tests/fixtures.js | 180 ++ .../services/tests/gocardless-service.spec.js | 528 +++++ .../app-gocardless/tests/bank-factory.spec.js | 21 + .../src/app-gocardless/tests/utils.spec.js | 37 + .../src/app-gocardless/util/handle-error.js | 16 + .../sync-server/src/app-gocardless/utils.js | 16 + packages/sync-server/src/app-secrets.js | 32 + .../src/app-simplefin/app-simplefin.js | 371 ++++ packages/sync-server/src/app-sync.js | 394 ++++ packages/sync-server/src/app-sync.test.js | 811 ++++++++ packages/sync-server/src/app.js | 87 + packages/sync-server/src/config-types.ts | 23 + packages/sync-server/src/db.js | 58 + packages/sync-server/src/load-config.js | 163 ++ packages/sync-server/src/migrations.js | 34 + packages/sync-server/src/run-migrations.js | 8 + .../sync-server/src/scripts/health-check.js | 20 + .../sync-server/src/scripts/reset-password.js | 36 + packages/sync-server/src/secrets.test.js | 82 + .../src/services/secrets-service.js | 90 + packages/sync-server/src/sql/messages.sql | 9 + packages/sync-server/src/sync-simple.js | 95 + packages/sync-server/src/util/hash.js | 5 + packages/sync-server/src/util/middlewares.js | 44 + packages/sync-server/src/util/paths.js | 12 + packages/sync-server/src/util/payee-name.js | 45 + packages/sync-server/src/util/prompt.js | 88 + packages/sync-server/src/util/title/index.js | 59 + .../sync-server/src/util/title/lower-case.js | 93 + .../sync-server/src/util/title/specials.js | 21 + .../sync-server/src/util/validate-user.js | 53 + packages/sync-server/tsconfig.json | 21 + .../sync-server/upcoming-release-notes/474.md | 6 + .../sync-server/upcoming-release-notes/487.md | 6 + .../upcoming-release-notes/README.md | 1 + yarn.lock | 1775 ++++++++++++++++- 123 files changed, 11514 insertions(+), 33 deletions(-) create mode 100644 packages/sync-server/.dockerignore create mode 100644 packages/sync-server/.editorconfig create mode 100644 packages/sync-server/.eslintignore create mode 100644 packages/sync-server/.eslintrc.cjs create mode 100644 packages/sync-server/.gitignore create mode 100644 packages/sync-server/.node-version create mode 100644 packages/sync-server/.prettierrc.json create mode 100644 packages/sync-server/.yarnrc.yml create mode 100644 packages/sync-server/Dockerfile create mode 100644 packages/sync-server/LICENSE.txt create mode 100644 packages/sync-server/README.md create mode 100644 packages/sync-server/app.js create mode 100644 packages/sync-server/babel.config.json create mode 100644 packages/sync-server/docker-compose.yml create mode 100644 packages/sync-server/docker/download-artifacts.sh create mode 100644 packages/sync-server/docker/edge-alpine.Dockerfile create mode 100644 packages/sync-server/docker/edge-ubuntu.Dockerfile create mode 100644 packages/sync-server/docker/stable-alpine.Dockerfile create mode 100644 packages/sync-server/docker/stable-ubuntu.Dockerfile create mode 100644 packages/sync-server/jest.config.json create mode 100644 packages/sync-server/jest.global-setup.js create mode 100644 packages/sync-server/jest.global-teardown.js create mode 100644 packages/sync-server/migrations/1694360000000-create-folders.js create mode 100644 packages/sync-server/migrations/1694360479680-create-account-db.js create mode 100644 packages/sync-server/migrations/1694362247011-create-secret-table.js create mode 100644 packages/sync-server/migrations/1702667624000-rename-nordigen-secrets.js create mode 100644 packages/sync-server/package.json create mode 100644 packages/sync-server/src/account-db.js create mode 100644 packages/sync-server/src/app-account.js create mode 100644 packages/sync-server/src/app-gocardless/README.md create mode 100644 packages/sync-server/src/app-gocardless/app-gocardless.js create mode 100644 packages/sync-server/src/app-gocardless/bank-factory.js create mode 100644 packages/sync-server/src/app-gocardless/banks/FORTUNEO_FTNOFRP1XXX.js create mode 100644 packages/sync-server/src/app-gocardless/banks/abanca-caglesmm.js create mode 100644 packages/sync-server/src/app-gocardless/banks/american-express-aesudef1.js create mode 100644 packages/sync-server/src/app-gocardless/banks/bancsabadell-bsabesbbb.js create mode 100644 packages/sync-server/src/app-gocardless/banks/bank.interface.ts create mode 100644 packages/sync-server/src/app-gocardless/banks/bankinter-bkbkesmm.js create mode 100644 packages/sync-server/src/app-gocardless/banks/belfius_gkccbebb.js create mode 100644 packages/sync-server/src/app-gocardless/banks/berliner_sparkasse_beladebexxx.js create mode 100644 packages/sync-server/src/app-gocardless/banks/bnp-be-gebabebb.js create mode 100644 packages/sync-server/src/app-gocardless/banks/cbc_cregbebb.js create mode 100644 packages/sync-server/src/app-gocardless/banks/danskebank-dabno22.js create mode 100644 packages/sync-server/src/app-gocardless/banks/easybank-bawaatww.js create mode 100644 packages/sync-server/src/app-gocardless/banks/ing-ingbrobu.js create mode 100644 packages/sync-server/src/app-gocardless/banks/ing-ingddeff.js create mode 100644 packages/sync-server/src/app-gocardless/banks/ing-pl-ingbplpw.js create mode 100644 packages/sync-server/src/app-gocardless/banks/integration-bank.js create mode 100644 packages/sync-server/src/app-gocardless/banks/kbc_kredbebb.js create mode 100644 packages/sync-server/src/app-gocardless/banks/mbank-retail-brexplpw.js create mode 100644 packages/sync-server/src/app-gocardless/banks/nationwide-naiagb21.js create mode 100644 packages/sync-server/src/app-gocardless/banks/nbg_ethngraaxxx.js create mode 100644 packages/sync-server/src/app-gocardless/banks/norwegian-xx-norwnok1.js create mode 100644 packages/sync-server/src/app-gocardless/banks/revolut_revolt21.js create mode 100644 packages/sync-server/src/app-gocardless/banks/sandboxfinance-sfin0000.js create mode 100644 packages/sync-server/src/app-gocardless/banks/seb-kort-bank-ab.js create mode 100644 packages/sync-server/src/app-gocardless/banks/seb-privat.js create mode 100644 packages/sync-server/src/app-gocardless/banks/sparnord-spnodk22.js create mode 100644 packages/sync-server/src/app-gocardless/banks/spk-karlsruhe-karsde66.js create mode 100644 packages/sync-server/src/app-gocardless/banks/spk-marburg-biedenkopf-heladef1mar.js create mode 100644 packages/sync-server/src/app-gocardless/banks/spk-worms-alzey-ried-malade51wor.js create mode 100644 packages/sync-server/src/app-gocardless/banks/tests/FORTUNEO_FTNOFRP1XXX.spec.js create mode 100644 packages/sync-server/src/app-gocardless/banks/tests/abanca-caglesmm.spec.js create mode 100644 packages/sync-server/src/app-gocardless/banks/tests/bancsabadell-bsabesbbb.spec.js create mode 100644 packages/sync-server/src/app-gocardless/banks/tests/belfius_gkccbebb.spec.js create mode 100644 packages/sync-server/src/app-gocardless/banks/tests/cbc_cregbebb.spec.js create mode 100644 packages/sync-server/src/app-gocardless/banks/tests/easybank-bawaatww.spec.js create mode 100644 packages/sync-server/src/app-gocardless/banks/tests/ing-ingddeff.spec.js create mode 100644 packages/sync-server/src/app-gocardless/banks/tests/ing-pl-ingbplpw.spec.js create mode 100644 packages/sync-server/src/app-gocardless/banks/tests/integration-bank.spec.js create mode 100644 packages/sync-server/src/app-gocardless/banks/tests/kbc_kredbebb.spec.js create mode 100644 packages/sync-server/src/app-gocardless/banks/tests/mbank-retail-brexplpw.spec.js create mode 100644 packages/sync-server/src/app-gocardless/banks/tests/nationwide-naiagb21.spec.js create mode 100644 packages/sync-server/src/app-gocardless/banks/tests/nbg_ethngraaxxx.spec.js create mode 100644 packages/sync-server/src/app-gocardless/banks/tests/revolut_revolt21.spec.js create mode 100644 packages/sync-server/src/app-gocardless/banks/tests/sandboxfinance-sfin0000.spec.js create mode 100644 packages/sync-server/src/app-gocardless/banks/tests/spk-marburg-biedenkopf-heladef1mar.spec.js create mode 100644 packages/sync-server/src/app-gocardless/banks/tests/virgin_nrnbgb22.spec.js create mode 100644 packages/sync-server/src/app-gocardless/banks/util/extract-payeeName-from-remittanceInfo.js create mode 100644 packages/sync-server/src/app-gocardless/banks/virgin_nrnbgb22.js create mode 100644 packages/sync-server/src/app-gocardless/errors.js create mode 100644 packages/sync-server/src/app-gocardless/gocardless-node.types.ts create mode 100644 packages/sync-server/src/app-gocardless/gocardless.types.ts create mode 100644 packages/sync-server/src/app-gocardless/link.html create mode 100644 packages/sync-server/src/app-gocardless/services/gocardless-service.js create mode 100644 packages/sync-server/src/app-gocardless/services/tests/fixtures.js create mode 100644 packages/sync-server/src/app-gocardless/services/tests/gocardless-service.spec.js create mode 100644 packages/sync-server/src/app-gocardless/tests/bank-factory.spec.js create mode 100644 packages/sync-server/src/app-gocardless/tests/utils.spec.js create mode 100644 packages/sync-server/src/app-gocardless/util/handle-error.js create mode 100644 packages/sync-server/src/app-gocardless/utils.js create mode 100644 packages/sync-server/src/app-secrets.js create mode 100644 packages/sync-server/src/app-simplefin/app-simplefin.js create mode 100644 packages/sync-server/src/app-sync.js create mode 100644 packages/sync-server/src/app-sync.test.js create mode 100644 packages/sync-server/src/app.js create mode 100644 packages/sync-server/src/config-types.ts create mode 100644 packages/sync-server/src/db.js create mode 100644 packages/sync-server/src/load-config.js create mode 100644 packages/sync-server/src/migrations.js create mode 100644 packages/sync-server/src/run-migrations.js create mode 100644 packages/sync-server/src/scripts/health-check.js create mode 100644 packages/sync-server/src/scripts/reset-password.js create mode 100644 packages/sync-server/src/secrets.test.js create mode 100644 packages/sync-server/src/services/secrets-service.js create mode 100644 packages/sync-server/src/sql/messages.sql create mode 100644 packages/sync-server/src/sync-simple.js create mode 100644 packages/sync-server/src/util/hash.js create mode 100644 packages/sync-server/src/util/middlewares.js create mode 100644 packages/sync-server/src/util/paths.js create mode 100644 packages/sync-server/src/util/payee-name.js create mode 100644 packages/sync-server/src/util/prompt.js create mode 100644 packages/sync-server/src/util/title/index.js create mode 100644 packages/sync-server/src/util/title/lower-case.js create mode 100644 packages/sync-server/src/util/title/specials.js create mode 100644 packages/sync-server/src/util/validate-user.js create mode 100644 packages/sync-server/tsconfig.json create mode 100644 packages/sync-server/upcoming-release-notes/474.md create mode 100644 packages/sync-server/upcoming-release-notes/487.md create mode 100644 packages/sync-server/upcoming-release-notes/README.md diff --git a/bin/package-electron b/bin/package-electron index ac29060e879..164382f454f 100755 --- a/bin/package-electron +++ b/bin/package-electron @@ -45,7 +45,6 @@ yarn workspace @actual-app/web build --mode=desktop # electron specific build yarn workspace loot-core build:browser yarn workspace @actual-app/web build:browser - yarn workspace desktop-electron update-client ( diff --git a/packages/desktop-electron/package.json b/packages/desktop-electron/package.json index e87474ba47e..d469f950779 100644 --- a/packages/desktop-electron/package.json +++ b/packages/desktop-electron/package.json @@ -76,7 +76,7 @@ }, "dependencies": { "@ngrok/ngrok": "^1.4.1", - "actual-sync": "file:../../../actual-server", + "actual-sync": "*", "better-sqlite3": "^9.6.0", "fs-extra": "^11.2.0", "promise-retry": "^2.0.1" diff --git a/packages/sync-server/.dockerignore b/packages/sync-server/.dockerignore new file mode 100644 index 00000000000..9a3b7d67742 --- /dev/null +++ b/packages/sync-server/.dockerignore @@ -0,0 +1,12 @@ +node_modules +user-files +server-files + +# Yarn +.pnp.* +.yarn/* +!.yarn/patches +!.yarn/plugins +!.yarn/releases +!.yarn/sdks +!.yarn/versions diff --git a/packages/sync-server/.editorconfig b/packages/sync-server/.editorconfig new file mode 100644 index 00000000000..551e30b9204 --- /dev/null +++ b/packages/sync-server/.editorconfig @@ -0,0 +1,11 @@ +# https://editorconfig.org + +root = true + +[*] +charset = utf-8 +end_of_line = lf +indent_size = 2 +indent_style = space +insert_final_newline = true +trim_trailing_whitespace = true diff --git a/packages/sync-server/.eslintignore b/packages/sync-server/.eslintignore new file mode 100644 index 00000000000..f4572de01e7 --- /dev/null +++ b/packages/sync-server/.eslintignore @@ -0,0 +1,6 @@ +**/node_modules/* +**/log/* +**/shared/* +/build + +supervise diff --git a/packages/sync-server/.eslintrc.cjs b/packages/sync-server/.eslintrc.cjs new file mode 100644 index 00000000000..c5538530206 --- /dev/null +++ b/packages/sync-server/.eslintrc.cjs @@ -0,0 +1,21 @@ +module.exports = { + root: true, + env: { + browser: true, + amd: true, + node: true, + jest: true + }, + parser: '@typescript-eslint/parser', + plugins: ['@typescript-eslint', 'prettier'], + extends: ['eslint:recommended', 'plugin:@typescript-eslint/recommended'], + rules: { + 'prettier/prettier': 'error', + '@typescript-eslint/no-unused-vars': [ + 'error', + { + argsIgnorePattern: '^_' + } + ] + } +}; diff --git a/packages/sync-server/.gitignore b/packages/sync-server/.gitignore new file mode 100644 index 00000000000..ad1e69fb0b1 --- /dev/null +++ b/packages/sync-server/.gitignore @@ -0,0 +1,33 @@ +.DS_Store +.#* +config.json +node_modules +log +supervise +bin/large-sync-data.txt +user-files +server-files +test-user-files +test-server-files +fly.toml +build/ +*.crt +*.pem +*.key +artifacts.json +.migrate +.migrate-test + +# Yarn +.pnp.* +.yarn/* +!.yarn/patches +!.yarn/plugins +!.yarn/releases +!.yarn/sdks +!.yarn/versions + +dist +.idea +/coverage +/coverage-e2e diff --git a/packages/sync-server/.node-version b/packages/sync-server/.node-version new file mode 100644 index 00000000000..e6db45a9079 --- /dev/null +++ b/packages/sync-server/.node-version @@ -0,0 +1 @@ +18.14.0 diff --git a/packages/sync-server/.prettierrc.json b/packages/sync-server/.prettierrc.json new file mode 100644 index 00000000000..a20502b7f06 --- /dev/null +++ b/packages/sync-server/.prettierrc.json @@ -0,0 +1,4 @@ +{ + "singleQuote": true, + "trailingComma": "all" +} diff --git a/packages/sync-server/.yarnrc.yml b/packages/sync-server/.yarnrc.yml new file mode 100644 index 00000000000..fd5296c36ec --- /dev/null +++ b/packages/sync-server/.yarnrc.yml @@ -0,0 +1,3 @@ +nodeLinker: node-modules + +yarnPath: .yarn/releases/yarn-4.3.1.cjs diff --git a/packages/sync-server/Dockerfile b/packages/sync-server/Dockerfile new file mode 100644 index 00000000000..ad5aa098ed9 --- /dev/null +++ b/packages/sync-server/Dockerfile @@ -0,0 +1,25 @@ +FROM node:18-bullseye as base +RUN apt-get update && apt-get install -y openssl +WORKDIR /app +ADD .yarn ./.yarn +ADD yarn.lock package.json .yarnrc.yml ./ +RUN yarn workspaces focus --all --production + +FROM node:18-bullseye-slim as prod +RUN apt-get update && apt-get install tini && apt-get clean -y && rm -rf /var/lib/apt/lists/* + +ARG USERNAME=actual +ARG USER_UID=1001 +ARG USER_GID=$USER_UID +RUN groupadd --gid $USER_GID $USERNAME \ + && useradd --uid $USER_UID --gid $USER_GID -m $USERNAME +RUN mkdir /data && chown -R ${USERNAME}:${USERNAME} /data + +WORKDIR /app +COPY --from=base /app/node_modules /app/node_modules +ADD package.json app.js ./ +ADD src ./src +ADD migrations ./migrations +ENTRYPOINT ["/usr/bin/tini","-g", "--"] +EXPOSE 5006 +CMD ["node", "app.js"] diff --git a/packages/sync-server/LICENSE.txt b/packages/sync-server/LICENSE.txt new file mode 100644 index 00000000000..3898f370d3c --- /dev/null +++ b/packages/sync-server/LICENSE.txt @@ -0,0 +1,7 @@ +Copyright James Long + +Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. diff --git a/packages/sync-server/README.md b/packages/sync-server/README.md new file mode 100644 index 00000000000..b1474e4d2d5 --- /dev/null +++ b/packages/sync-server/README.md @@ -0,0 +1,17 @@ +This is the main project to run [Actual](https://github.com/actualbudget/actual), a local-first personal finance tool. It comes with the latest version of Actual, and a server to persist changes and make data available across all devices. + +### Getting Started +Actual is a local-first personal finance tool. It is 100% free and open-source, written in NodeJS, it has a synchronization element so that all your changes can move between devices without any heavy lifting. + +If you are interested in contributing, or want to know how development works, see our [contributing](https://actualbudget.org/docs/contributing/) document we would love to have you. + +Want to say thanks? Click the ⭐ at the top of the page. + +### Documentation + +We have a wide range of documentation on how to use Actual. This is all available in our [Community Documentation](https://actualbudget.org/docs/), including topics on [installing](https://actualbudget.org/docs/install/), [Budgeting](https://actualbudget.org/docs/budgeting/), [Account Management](https://actualbudget.org/docs/accounts/), [Tips & Tricks](https://actualbudget.org/docs/getting-started/tips-tricks) and some documentation for developers. + +### Feature Requests +Current feature requests can be seen [here](https://github.com/actualbudget/actual/issues?q=is%3Aissue+label%3A%22needs+votes%22+sort%3Areactions-%2B1-desc). Vote for your favorite requests by reacting 👍 to the top comment of the request. + +To add new feature requests, open a new Issue of the "Feature Request" type. diff --git a/packages/sync-server/app.js b/packages/sync-server/app.js new file mode 100644 index 00000000000..d0647fb88fd --- /dev/null +++ b/packages/sync-server/app.js @@ -0,0 +1,11 @@ +import runMigrations from './src/migrations.js'; + +runMigrations() + .then(() => { + //import the app here becasue initial migrations need to be run first - they are dependencies of the app.js + import('./src/app.js').then((app) => app.default()); // run the app + }) + .catch((err) => { + console.log('Error starting app:', err); + process.exit(1); + }); diff --git a/packages/sync-server/babel.config.json b/packages/sync-server/babel.config.json new file mode 100644 index 00000000000..e15ac017a2e --- /dev/null +++ b/packages/sync-server/babel.config.json @@ -0,0 +1,3 @@ +{ + "presets": ["@babel/preset-typescript"] +} diff --git a/packages/sync-server/docker-compose.yml b/packages/sync-server/docker-compose.yml new file mode 100644 index 00000000000..424844e4a10 --- /dev/null +++ b/packages/sync-server/docker-compose.yml @@ -0,0 +1,22 @@ +services: + actual_server: + image: docker.io/actualbudget/actual-server:latest + ports: + # This line makes Actual available at port 5006 of the device you run the server on, + # i.e. http://localhost:5006. You can change the first number to change the port, if you want. + - '5006:5006' + environment: + # Uncomment any of the lines below to set configuration options. + # - ACTUAL_HTTPS_KEY=/data/selfhost.key + # - ACTUAL_HTTPS_CERT=/data/selfhost.crt + # - ACTUAL_PORT=5006 + # - ACTUAL_UPLOAD_FILE_SYNC_SIZE_LIMIT_MB=20 + # - ACTUAL_UPLOAD_SYNC_ENCRYPTED_FILE_SYNC_SIZE_LIMIT_MB=50 + # - ACTUAL_UPLOAD_FILE_SIZE_LIMIT_MB=20 + # See all options and more details at https://actualbudget.github.io/docs/Installing/Configuration + # !! If you are not using any of these options, remove the 'environment:' tag entirely. + volumes: + # Change './actual-data' below to the path to the folder you want Actual to store its data in on your server. + # '/data' is the path Actual will look for its files in by default, so leave that as-is. + - ./actual-data:/data + restart: unless-stopped diff --git a/packages/sync-server/docker/download-artifacts.sh b/packages/sync-server/docker/download-artifacts.sh new file mode 100644 index 00000000000..e378954efd7 --- /dev/null +++ b/packages/sync-server/docker/download-artifacts.sh @@ -0,0 +1,14 @@ +#!/bin/bash + +URL="https://api.github.com/repos/actualbudget/actual/actions/artifacts?name=actual-web&per_page=100" + +if [ -n "$GITHUB_TOKEN" ]; then + curl -L -o artifacts.json --header "Authorization: Bearer ${GITHUB_TOKEN}" $URL +else + curl -L -o artifacts.json $URL +fi + +if [ $? -ne 0 ]; then + echo "Failed to download artifacts.json" + exit 1 +fi diff --git a/packages/sync-server/docker/edge-alpine.Dockerfile b/packages/sync-server/docker/edge-alpine.Dockerfile new file mode 100644 index 00000000000..7c3f57ec030 --- /dev/null +++ b/packages/sync-server/docker/edge-alpine.Dockerfile @@ -0,0 +1,36 @@ +FROM alpine:3.17 as base +RUN apk add --no-cache nodejs yarn npm python3 openssl build-base jq curl +WORKDIR /app +ADD .yarn ./.yarn +ADD yarn.lock package.json .yarnrc.yml ./ +RUN if [ "$(uname -m)" = "armv7l" ]; then yarn config set taskPoolConcurrency 2; yarn config set networkConcurrency 5; fi +RUN yarn workspaces focus --all --production +RUN if [ "$(uname -m)" = "armv7l" ]; then npm install bcrypt better-sqlite3 --build-from-source; fi + +RUN mkdir /public +ADD artifacts.json /tmp/artifacts.json +RUN jq -r '[.artifacts[] | select(.workflow_run.head_branch == "master" and .workflow_run.head_repository_id == .workflow_run.repository_id)][0]' /tmp/artifacts.json > /tmp/latest-build.json + +ARG GITHUB_TOKEN +RUN curl -L -o /tmp/desktop-client.zip --header "Authorization: Bearer ${GITHUB_TOKEN}" $(jq -r '.archive_download_url' /tmp/latest-build.json) +RUN unzip /tmp/desktop-client.zip -d /public + +FROM alpine:3.17 as prod +RUN apk add --no-cache nodejs tini + +ARG USERNAME=actual +ARG USER_UID=1001 +ARG USER_GID=$USER_UID +RUN addgroup -S ${USERNAME} -g ${USER_GID} && adduser -S ${USERNAME} -G ${USERNAME} -u ${USER_UID} +RUN mkdir /data && chown -R ${USERNAME}:${USERNAME} /data + +WORKDIR /app +COPY --from=base /app/node_modules /app/node_modules +COPY --from=base /public /public +ADD package.json app.js ./ +ADD src ./src +ADD migrations ./migrations +ENTRYPOINT ["/sbin/tini","-g", "--"] +ENV ACTUAL_WEB_ROOT=/public +EXPOSE 5006 +CMD ["node", "app.js"] diff --git a/packages/sync-server/docker/edge-ubuntu.Dockerfile b/packages/sync-server/docker/edge-ubuntu.Dockerfile new file mode 100644 index 00000000000..99e4e757ef3 --- /dev/null +++ b/packages/sync-server/docker/edge-ubuntu.Dockerfile @@ -0,0 +1,36 @@ +FROM node:18-bullseye as base +RUN apt-get update && apt-get install -y openssl jq +WORKDIR /app +ADD .yarn ./.yarn +ADD yarn.lock package.json .yarnrc.yml ./ +RUN if [ "$(uname -m)" = "armv7l" ]; then yarn config set taskPoolConcurrency 2; yarn config set networkConcurrency 5; fi +RUN yarn workspaces focus --all --production + +RUN mkdir /public +ADD artifacts.json /tmp/artifacts.json +RUN jq -r '[.artifacts[] | select(.workflow_run.head_branch == "master" and .workflow_run.head_repository_id == .workflow_run.repository_id)][0]' /tmp/artifacts.json > /tmp/latest-build.json + +ARG GITHUB_TOKEN +RUN curl -L -o /tmp/desktop-client.zip --header "Authorization: Bearer ${GITHUB_TOKEN}" $(jq -r '.archive_download_url' /tmp/latest-build.json) +RUN unzip /tmp/desktop-client.zip -d /public + +FROM node:18-bullseye-slim as prod +RUN apt-get update && apt-get install tini && apt-get clean -y && rm -rf /var/lib/apt/lists/* + +ARG USERNAME=actual +ARG USER_UID=1001 +ARG USER_GID=$USER_UID +RUN groupadd --gid $USER_GID $USERNAME \ + && useradd --uid $USER_UID --gid $USER_GID -m $USERNAME +RUN mkdir /data && chown -R ${USERNAME}:${USERNAME} /data + +WORKDIR /app +COPY --from=base /app/node_modules /app/node_modules +COPY --from=base /public /public +ADD package.json app.js ./ +ADD src ./src +ADD migrations ./migrations +ENTRYPOINT ["/usr/bin/tini","-g", "--"] +ENV ACTUAL_WEB_ROOT=/public +EXPOSE 5006 +CMD ["node", "app.js"] diff --git a/packages/sync-server/docker/stable-alpine.Dockerfile b/packages/sync-server/docker/stable-alpine.Dockerfile new file mode 100644 index 00000000000..e6b21652046 --- /dev/null +++ b/packages/sync-server/docker/stable-alpine.Dockerfile @@ -0,0 +1,26 @@ +FROM alpine:3.17 as base +RUN apk add --no-cache nodejs yarn npm python3 openssl build-base +WORKDIR /app +ADD .yarn ./.yarn +ADD yarn.lock package.json .yarnrc.yml ./ +RUN if [ "$(uname -m)" = "armv7l" ]; then yarn config set taskPoolConcurrency 2; yarn config set networkConcurrency 5; fi +RUN yarn workspaces focus --all --production +RUN if [ "$(uname -m)" = "armv7l" ]; then npm install bcrypt better-sqlite3 --build-from-source; fi + +FROM alpine:3.17 as prod +RUN apk add --no-cache nodejs tini + +ARG USERNAME=actual +ARG USER_UID=1001 +ARG USER_GID=$USER_UID +RUN addgroup -S ${USERNAME} -g ${USER_GID} && adduser -S ${USERNAME} -G ${USERNAME} -u ${USER_UID} +RUN mkdir /data && chown -R ${USERNAME}:${USERNAME} /data + +WORKDIR /app +COPY --from=base /app/node_modules /app/node_modules +ADD package.json app.js ./ +ADD src ./src +ADD migrations ./migrations +ENTRYPOINT ["/sbin/tini","-g", "--"] +EXPOSE 5006 +CMD ["node", "app.js"] diff --git a/packages/sync-server/docker/stable-ubuntu.Dockerfile b/packages/sync-server/docker/stable-ubuntu.Dockerfile new file mode 100644 index 00000000000..eb02aad66f5 --- /dev/null +++ b/packages/sync-server/docker/stable-ubuntu.Dockerfile @@ -0,0 +1,26 @@ +FROM node:18-bullseye as base +RUN apt-get update && apt-get install -y openssl +WORKDIR /app +ADD .yarn ./.yarn +ADD yarn.lock package.json .yarnrc.yml ./ +RUN if [ "$(uname -m)" = "armv7l" ]; then yarn config set taskPoolConcurrency 2; yarn config set networkConcurrency 5; fi +RUN yarn workspaces focus --all --production + +FROM node:18-bullseye-slim as prod +RUN apt-get update && apt-get install tini && apt-get clean -y && rm -rf /var/lib/apt/lists/* + +ARG USERNAME=actual +ARG USER_UID=1001 +ARG USER_GID=$USER_UID +RUN groupadd --gid $USER_GID $USERNAME \ + && useradd --uid $USER_UID --gid $USER_GID -m $USERNAME +RUN mkdir /data && chown -R ${USERNAME}:${USERNAME} /data + +WORKDIR /app +COPY --from=base /app/node_modules /app/node_modules +ADD package.json app.js ./ +ADD src ./src +ADD migrations ./migrations +ENTRYPOINT ["/usr/bin/tini","-g", "--"] +EXPOSE 5006 +CMD ["node", "app.js"] diff --git a/packages/sync-server/jest.config.json b/packages/sync-server/jest.config.json new file mode 100644 index 00000000000..f33061a9d76 --- /dev/null +++ b/packages/sync-server/jest.config.json @@ -0,0 +1,14 @@ +{ + "globalSetup": "./jest.global-setup.js", + "globalTeardown": "./jest.global-teardown.js", + "testPathIgnorePatterns": ["dist", "/node_modules/", "/build/"], + "roots": ["<rootDir>"], + "moduleFileExtensions": ["ts", "js", "json"], + "testEnvironment": "node", + "collectCoverage": true, + "collectCoverageFrom": ["**/*.{js,ts,tsx}"], + "coveragePathIgnorePatterns": ["dist", "/node_modules/", "/build/", "/coverage/"], + "coverageReporters": ["html", "lcov", "text", "text-summary"], + "resetMocks": true, + "restoreMocks": true +} diff --git a/packages/sync-server/jest.global-setup.js b/packages/sync-server/jest.global-setup.js new file mode 100644 index 00000000000..36f53cf1cc5 --- /dev/null +++ b/packages/sync-server/jest.global-setup.js @@ -0,0 +1,10 @@ +import getAccountDb from './src/account-db.js'; +import runMigrations from './src/migrations.js'; + +export default async function setup() { + await runMigrations(); + + // Insert a fake "valid-token" fixture that can be reused + const db = getAccountDb(); + await db.mutate('INSERT INTO sessions (token) VALUES (?)', ['valid-token']); +} diff --git a/packages/sync-server/jest.global-teardown.js b/packages/sync-server/jest.global-teardown.js new file mode 100644 index 00000000000..4e19fc38547 --- /dev/null +++ b/packages/sync-server/jest.global-teardown.js @@ -0,0 +1,5 @@ +import runMigrations from './src/migrations.js'; + +export default async function teardown() { + await runMigrations('down'); +} diff --git a/packages/sync-server/migrations/1694360000000-create-folders.js b/packages/sync-server/migrations/1694360000000-create-folders.js new file mode 100644 index 00000000000..2ba62b8df4a --- /dev/null +++ b/packages/sync-server/migrations/1694360000000-create-folders.js @@ -0,0 +1,24 @@ +import fs from 'node:fs/promises'; +import config from '../src/load-config.js'; + +async function ensureExists(path) { + try { + await fs.mkdir(path); + } catch (err) { + if (err.code == 'EEXIST') { + return null; + } + + throw err; + } +} + +export const up = async function () { + await ensureExists(config.serverFiles); + await ensureExists(config.userFiles); +}; + +export const down = async function () { + await fs.rm(config.serverFiles, { recursive: true, force: true }); + await fs.rm(config.userFiles, { recursive: true, force: true }); +}; diff --git a/packages/sync-server/migrations/1694360479680-create-account-db.js b/packages/sync-server/migrations/1694360479680-create-account-db.js new file mode 100644 index 00000000000..fd57b79cc8d --- /dev/null +++ b/packages/sync-server/migrations/1694360479680-create-account-db.js @@ -0,0 +1,30 @@ +import getAccountDb from '../src/account-db.js'; + +export const up = async function () { + await getAccountDb().exec(` + CREATE TABLE IF NOT EXISTS auth + (password TEXT PRIMARY KEY); + + CREATE TABLE IF NOT EXISTS sessions + (token TEXT PRIMARY KEY); + + CREATE TABLE IF NOT EXISTS files + (id TEXT PRIMARY KEY, + group_id TEXT, + sync_version SMALLINT, + encrypt_meta TEXT, + encrypt_keyid TEXT, + encrypt_salt TEXT, + encrypt_test TEXT, + deleted BOOLEAN DEFAULT FALSE, + name TEXT); + `); +}; + +export const down = async function () { + await getAccountDb().exec(` + DROP TABLE auth; + DROP TABLE sessions; + DROP TABLE files; + `); +}; diff --git a/packages/sync-server/migrations/1694362247011-create-secret-table.js b/packages/sync-server/migrations/1694362247011-create-secret-table.js new file mode 100644 index 00000000000..2f60a081513 --- /dev/null +++ b/packages/sync-server/migrations/1694362247011-create-secret-table.js @@ -0,0 +1,16 @@ +import getAccountDb from '../src/account-db.js'; + +export const up = async function () { + await getAccountDb().exec(` + CREATE TABLE IF NOT EXISTS secrets ( + name TEXT PRIMARY KEY, + value BLOB + ); + `); +}; + +export const down = async function () { + await getAccountDb().exec(` + DROP TABLE secrets; + `); +}; diff --git a/packages/sync-server/migrations/1702667624000-rename-nordigen-secrets.js b/packages/sync-server/migrations/1702667624000-rename-nordigen-secrets.js new file mode 100644 index 00000000000..5dcaff73574 --- /dev/null +++ b/packages/sync-server/migrations/1702667624000-rename-nordigen-secrets.js @@ -0,0 +1,19 @@ +import getAccountDb from '../src/account-db.js'; + +export const up = async function () { + await getAccountDb().exec( + `UPDATE secrets SET name = 'gocardless_secretId' WHERE name = 'nordigen_secretId'`, + ); + await getAccountDb().exec( + `UPDATE secrets SET name = 'gocardless_secretKey' WHERE name = 'nordigen_secretKey'`, + ); +}; + +export const down = async function () { + await getAccountDb().exec( + `UPDATE secrets SET name = 'nordigen_secretId' WHERE name = 'gocardless_secretId'`, + ); + await getAccountDb().exec( + `UPDATE secrets SET name = 'nordigen_secretKey' WHERE name = 'gocardless_secretKey'`, + ); +}; diff --git a/packages/sync-server/package.json b/packages/sync-server/package.json new file mode 100644 index 00000000000..5113b7e10f6 --- /dev/null +++ b/packages/sync-server/package.json @@ -0,0 +1,66 @@ +{ + "name": "actual-sync", + "version": "24.10.1", + "license": "MIT", + "description": "actual syncing server", + "type": "module", + "scripts": { + "start": "node app", + "lint": "eslint . --max-warnings 0", + "lint:fix": "eslint . --fix", + "build": "tsc", + "test": "NODE_ENV=test NODE_OPTIONS='--experimental-vm-modules --trace-warnings' jest --coverage", + "db:migrate": "NODE_ENV=development node src/run-migrations.js up", + "db:downgrade": "NODE_ENV=development node src/run-migrations.js down", + "db:test-migrate": "NODE_ENV=test node src/run-migrations.js up", + "db:test-downgrade": "NODE_ENV=test node src/run-migrations.js down", + "types": "tsc --noEmit --incremental", + "verify": "yarn lint && yarn types", + "reset-password": "node src/scripts/reset-password.js", + "health-check": "node src/scripts/health-check.js" + }, + "dependencies": { + "@actual-app/crdt": "*", + "@actual-app/web": "*", + "bcrypt": "^5.1.1", + "better-sqlite3": "^9.6.0", + "body-parser": "^1.20.3", + "cors": "^2.8.5", + "date-fns": "^2.30.0", + "debug": "^4.3.4", + "express": "4.20.0", + "express-actuator": "1.8.4", + "express-rate-limit": "^6.7.0", + "express-response-size": "^0.0.3", + "express-winston": "^4.2.0", + "jws": "^4.0.0", + "migrate": "^2.0.1", + "nordigen-node": "^1.4.0", + "uuid": "^9.0.0", + "winston": "^3.14.2" + }, + "devDependencies": { + "@babel/preset-typescript": "^7.20.2", + "@types/bcrypt": "^5.0.2", + "@types/better-sqlite3": "^7.6.7", + "@types/cors": "^2.8.13", + "@types/express": "^4.17.17", + "@types/express-actuator": "^1.8.0", + "@types/jest": "^29.2.3", + "@types/node": "^17.0.45", + "@types/supertest": "^2.0.12", + "@types/uuid": "^9.0.0", + "@typescript-eslint/eslint-plugin": "^5.51.0", + "@typescript-eslint/parser": "^5.51.0", + "eslint": "^8.33.0", + "eslint-plugin-prettier": "^4.2.1", + "jest": "^29.3.1", + "prettier": "^2.8.3", + "supertest": "^6.3.1", + "typescript": "^4.9.5" + }, + "engines": { + "node": ">=18.0.0" + }, + "packageManager": "yarn@4.3.1" +} diff --git a/packages/sync-server/src/account-db.js b/packages/sync-server/src/account-db.js new file mode 100644 index 00000000000..cc2fe56746c --- /dev/null +++ b/packages/sync-server/src/account-db.js @@ -0,0 +1,111 @@ +import { join } from 'node:path'; +import openDatabase from './db.js'; +import config from './load-config.js'; +import * as uuid from 'uuid'; +import * as bcrypt from 'bcrypt'; + +let _accountDb; + +export default function getAccountDb() { + if (_accountDb === undefined) { + const dbPath = join(config.serverFiles, 'account.sqlite'); + _accountDb = openDatabase(dbPath); + } + + return _accountDb; +} + +function hashPassword(password) { + return bcrypt.hashSync(password, 12); +} + +export function needsBootstrap() { + let accountDb = getAccountDb(); + let rows = accountDb.all('SELECT * FROM auth'); + return rows.length === 0; +} + +/* + * Get the Login Method in the following order + * req (the frontend can say which method in the case it wants to resort to forcing password auth) + * config options + * fall back to using password + */ +export function getLoginMethod(req) { + if ( + typeof req !== 'undefined' && + (req.body || { loginMethod: null }).loginMethod + ) { + return req.body.loginMethod; + } + return config.loginMethod || 'password'; +} + +export function bootstrap(password) { + if (password === undefined || password === '') { + return { error: 'invalid-password' }; + } + + let accountDb = getAccountDb(); + let rows = accountDb.all('SELECT * FROM auth'); + + if (rows.length !== 0) { + return { error: 'already-bootstrapped' }; + } + + // Hash the password. There's really not a strong need for this + // since this is a self-hosted instance owned by the user. + // However, just in case we do it. + let hashed = hashPassword(password); + accountDb.mutate('INSERT INTO auth (password) VALUES (?)', [hashed]); + + let token = uuid.v4(); + accountDb.mutate('INSERT INTO sessions (token) VALUES (?)', [token]); + + return { token }; +} + +export function login(password) { + if (password === undefined || password === '') { + return { error: 'invalid-password' }; + } + + let accountDb = getAccountDb(); + let row = accountDb.first('SELECT * FROM auth'); + + let confirmed = row && bcrypt.compareSync(password, row.password); + + if (!confirmed) { + return { error: 'invalid-password' }; + } + + // Right now, tokens are permanent and there's just one in the + // system. In the future this should probably evolve to be a + // "session" that times out after a long time or something, and + // maybe each device has a different token + let sessionRow = accountDb.first('SELECT * FROM sessions'); + return { token: sessionRow.token }; +} + +export function changePassword(newPassword) { + if (newPassword === undefined || newPassword === '') { + return { error: 'invalid-password' }; + } + + let accountDb = getAccountDb(); + + let hashed = hashPassword(newPassword); + let token = uuid.v4(); + + // Note that this doesn't have a WHERE. This table only ever has 1 + // row (maybe that will change in the future? if so this will not work) + accountDb.mutate('UPDATE auth SET password = ?', [hashed]); + accountDb.mutate('UPDATE sessions SET token = ?', [token]); + + return {}; +} + +export function getSession(token) { + let accountDb = getAccountDb(); + return accountDb.first('SELECT * FROM sessions WHERE token = ?', [token]); +} diff --git a/packages/sync-server/src/app-account.js b/packages/sync-server/src/app-account.js new file mode 100644 index 00000000000..cc18ea32ecb --- /dev/null +++ b/packages/sync-server/src/app-account.js @@ -0,0 +1,101 @@ +import express from 'express'; +import { + errorMiddleware, + requestLoggerMiddleware, +} from './util/middlewares.js'; +import validateUser, { validateAuthHeader } from './util/validate-user.js'; +import { + bootstrap, + login, + changePassword, + needsBootstrap, + getLoginMethod, +} from './account-db.js'; + +let app = express(); +app.use(errorMiddleware); +app.use(requestLoggerMiddleware); +export { app as handlers }; + +// Non-authenticated endpoints: +// +// /needs-bootstrap +// /boostrap (special endpoint for setting up the instance, cant call again) +// /login + +app.get('/needs-bootstrap', (req, res) => { + res.send({ + status: 'ok', + data: { bootstrapped: !needsBootstrap(), loginMethod: getLoginMethod() }, + }); +}); + +app.post('/bootstrap', (req, res) => { + let { error, token } = bootstrap(req.body.password); + + if (error) { + res.status(400).send({ status: 'error', reason: error }); + return; + } + + res.send({ status: 'ok', data: { token } }); +}); + +app.post('/login', (req, res) => { + let loginMethod = getLoginMethod(req); + console.log('Logging in via ' + loginMethod); + let tokenRes = null; + switch (loginMethod) { + case 'header': { + let headerVal = req.get('x-actual-password') || ''; + const obfuscated = + '*'.repeat(headerVal.length) || 'No password provided.'; + console.debug('HEADER VALUE: ' + obfuscated); + if (headerVal == '') { + res.send({ status: 'error', reason: 'invalid-header' }); + return; + } else { + if (validateAuthHeader(req)) { + tokenRes = login(headerVal); + } else { + res.send({ status: 'error', reason: 'proxy-not-trusted' }); + return; + } + } + break; + } + case 'password': + default: + tokenRes = login(req.body.password); + break; + } + let { error, token } = tokenRes; + + if (error) { + res.status(400).send({ status: 'error', reason: error }); + return; + } + + res.send({ status: 'ok', data: { token } }); +}); + +app.post('/change-password', (req, res) => { + let user = validateUser(req, res); + if (!user) return; + + let { error } = changePassword(req.body.password); + + if (error) { + res.send({ status: 'error', reason: error }); + return; + } + + res.send({ status: 'ok', data: {} }); +}); + +app.get('/validate', (req, res) => { + let user = validateUser(req, res); + if (user) { + res.send({ status: 'ok', data: { validated: true } }); + } +}); diff --git a/packages/sync-server/src/app-gocardless/README.md b/packages/sync-server/src/app-gocardless/README.md new file mode 100644 index 00000000000..23f8300e966 --- /dev/null +++ b/packages/sync-server/src/app-gocardless/README.md @@ -0,0 +1,164 @@ +# Integration new bank + +If the default bank integration does not work for you, you can integrate a new bank by following these steps. + +1. Find in [this google doc](https://docs.google.com/spreadsheets/d/1ogpzydzotOltbssrc3IQ8rhBLlIZbQgm5QCiiNJrkyA/edit#gid=489769432) what is the identifier of the bank which you want to integrate. + +2. Launch frontend and backend server. + +3. In the frontend, create a new linked account selecting the institution which you are interested in. + + This will trigger the process of fetching the data from the bank and will log the data in the backend. Use this data to fill the logic of the bank class. + +4. Create new a bank class based on `app-gocardless/banks/sandboxfinance-sfin0000.js`. + + Name of the file and class should be created based on the ID of the integrated institution, found in step 1. + +5. Fill the logic of `normalizeAccount`, `normalizeTransaction`, `sortTransactions`, and `calculateStartingBalance` functions. + You do not need to fill every function, only those which are necessary for the integration to work. + + You should do it based on the data which you found in the logs. + + Example logs which help you to fill: + + - `normalizeAccount` function: + + ```log + Available account properties for new institution integration { + account: '{ + "iban": "PL00000000000000000987654321", + "currency": "PLN", + "ownerName": "John Example", + "displayName": "Product name", + "product": "Daily account", + "usage": "PRIV", + "ownerAddressUnstructured": [ + "POL", + "UL. Example 1", + "00-000 Warsaw" + ], + "id": "XXXXXXXX-XXXX-XXXXX-XXXXXX-XXXXXXXXX", + "created": "2023-01-18T12:15:16.502446Z", + "last_accessed": null, + "institution_id": "MBANK_RETAIL_BREXPLPW", + "status": "READY", + "owner_name": "", + "institution": { + "id": "MBANK_RETAIL_BREXPLPW", + "name": "mBank Retail", + "bic": "BREXPLPW", + "transaction_total_days": "90", + "countries": [ + "PL" + ], + "logo": "https://cdn.nordigen.com/ais/MBANK_RETAIL_BREXCZPP.png", + "supported_payments": {}, + "supported_features": [ + "access_scopes", + "business_accounts", + "card_accounts", + "corporate_accounts", + "pending_transactions", + "private_accounts" + ] + } + }' + } + ``` + + - `sortTransactions` function: + + ```log + Available (first 10) transactions properties for new integration of institution in sortTransactions function + { + top10SortedTransactions: '[ + { + "transactionId": "20220101001", + "bookingDate": "2022-01-01", + "valueDate": "2022-01-01", + "transactionAmount": { + "amount": "5.01", + "currency": "EUR" + }, + "creditorName": "JOHN EXAMPLE", + "creditorAccount": { + "iban": "PL00000000000000000987654321" + }, + "debtorName": "CHRIS EXAMPLE", + "debtorAccount": { + "iban": "PL12345000000000000987654321" + }, + "remittanceInformationUnstructured": "TEST BANK TRANSFER", + "remittanceInformationUnstructuredArray": [ + "TEST BANK TRANSFER" + ], + "balanceAfterTransaction": { + "balanceAmount": { + "amount": "448.52", + "currency": "EUR" + }, + "balanceType": "interimBooked" + }, + "internalTransactionId": "casfib7720c2a02c0331cw2" + } + ]' + } + ``` + + - `calculateStartingBalance` function: + + ```log + Available (first 10) transactions properties for new integration of institution in calculateStartingBalance function { + balances: '[ + { + "balanceAmount": { + "amount": "448.52", + "currency": "EUR" + }, + "balanceType": "forwardAvailable" + }, + { + "balanceAmount": { + "amount": "448.52", + "currency": "EUR" + }, + "balanceType": "interimBooked" + } + ]', + top10SortedTransactions: '[ + { + "transactionId": "20220101001", + "bookingDate": "2022-01-01", + "valueDate": "2022-01-01", + "transactionAmount": { + "amount": "5.01", + "currency": "EUR" + }, + "creditorName": "JOHN EXAMPLE", + "creditorAccount": { + "iban": "PL00000000000000000987654321" + }, + "debtorName": "CHRIS EXAMPLE", + "debtorAccount": { + "iban": "PL12345000000000000987654321" + }, + "remittanceInformationUnstructured": "TEST BANK TRANSFER", + "remittanceInformationUnstructuredArray": [ + "TEST BANK TRANSFER" + ], + "balanceAfterTransaction": { + "balanceAmount": { + "amount": "448.52", + "currency": "EUR" + }, + "balanceType": "interimBooked" + }, + "internalTransactionId": "casfib7720c2a02c0331cw2" + } + ]' + } + ``` + +6. Add new bank integration to `BankFactory` class in file `actual-server/app-gocardless/bank-factory.js` + +7. Remember to add tests for new bank integration in diff --git a/packages/sync-server/src/app-gocardless/app-gocardless.js b/packages/sync-server/src/app-gocardless/app-gocardless.js new file mode 100644 index 00000000000..cdba0dca689 --- /dev/null +++ b/packages/sync-server/src/app-gocardless/app-gocardless.js @@ -0,0 +1,261 @@ +import { isAxiosError } from 'axios'; +import express from 'express'; +import path from 'path'; +import { inspect } from 'util'; + +import { goCardlessService } from './services/gocardless-service.js'; +import { + AccountNotLinkedToRequisition, + GenericGoCardlessError, + RateLimitError, + RequisitionNotLinked, +} from './errors.js'; +import { handleError } from './util/handle-error.js'; +import { sha256String } from '../util/hash.js'; +import { + requestLoggerMiddleware, + validateUserMiddleware, +} from '../util/middlewares.js'; + +const app = express(); +app.use(requestLoggerMiddleware); + +app.get('/link', function (req, res) { + res.sendFile('link.html', { root: path.resolve('./src/app-gocardless') }); +}); + +export { app as handlers }; +app.use(express.json()); +app.use(validateUserMiddleware); + +app.post('/status', async (req, res) => { + res.send({ + status: 'ok', + data: { + configured: goCardlessService.isConfigured(), + }, + }); +}); + +app.post( + '/create-web-token', + handleError(async (req, res) => { + const { institutionId } = req.body; + const { origin } = req.headers; + + const { link, requisitionId } = await goCardlessService.createRequisition({ + institutionId, + host: origin, + }); + + res.send({ + status: 'ok', + data: { + link, + requisitionId, + }, + }); + }), +); + +app.post( + '/get-accounts', + handleError(async (req, res) => { + const { requisitionId } = req.body; + + try { + const { requisition, accounts } = + await goCardlessService.getRequisitionWithAccounts(requisitionId); + + res.send({ + status: 'ok', + data: { + ...requisition, + accounts: await Promise.all( + accounts.map(async (account) => + account?.iban + ? { ...account, iban: await sha256String(account.iban) } + : account, + ), + ), + }, + }); + } catch (error) { + if (error instanceof RequisitionNotLinked) { + res.send({ + status: 'ok', + requisitionStatus: error.details.requisitionStatus, + }); + } else { + throw error; + } + } + }), +); + +app.post( + '/get-banks', + handleError(async (req, res) => { + let { country, showDemo = false } = req.body; + + await goCardlessService.setToken(); + const data = await goCardlessService.getInstitutions(country); + + res.send({ + status: 'ok', + data: showDemo + ? [ + { + id: 'SANDBOXFINANCE_SFIN0000', + name: 'DEMO bank (used for testing bank-sync)', + }, + ...data, + ] + : data, + }); + }), +); + +app.post( + '/remove-account', + handleError(async (req, res) => { + let { requisitionId } = req.body; + + const data = await goCardlessService.deleteRequisition(requisitionId); + if (data.summary === 'Requisition deleted') { + res.send({ + status: 'ok', + data, + }); + } else { + res.send({ + status: 'error', + data: { + data, + reason: 'Can not delete requisition', + }, + }); + } + }), +); + +app.post( + '/transactions', + handleError(async (req, res) => { + const { + requisitionId, + startDate, + endDate, + accountId, + includeBalance = true, + } = req.body; + + try { + if (includeBalance) { + const { + balances, + institutionId, + startingBalance, + transactions: { booked, pending, all }, + } = await goCardlessService.getTransactionsWithBalance( + requisitionId, + accountId, + startDate, + endDate, + ); + + res.send({ + status: 'ok', + data: { + balances, + institutionId, + startingBalance, + transactions: { + booked, + pending, + all, + }, + }, + }); + } else { + const { + institutionId, + transactions: { booked, pending, all }, + } = await goCardlessService.getNormalizedTransactions( + requisitionId, + accountId, + startDate, + endDate, + ); + + res.send({ + status: 'ok', + data: { + institutionId, + transactions: { + booked, + pending, + all, + }, + }, + }); + } + } catch (error) { + const sendErrorResponse = (data) => + res.send({ status: 'ok', data: { ...data, details: error.details } }); + + switch (true) { + case error instanceof RequisitionNotLinked: + sendErrorResponse({ + error_type: 'ITEM_ERROR', + error_code: 'ITEM_LOGIN_REQUIRED', + status: 'expired', + reason: + 'Access to account has expired as set in End User Agreement', + }); + break; + case error instanceof AccountNotLinkedToRequisition: + sendErrorResponse({ + error_type: 'INVALID_INPUT', + error_code: 'INVALID_ACCESS_TOKEN', + status: 'rejected', + reason: 'Account not linked with this requisition', + }); + break; + case error instanceof RateLimitError: + sendErrorResponse({ + error_type: 'RATE_LIMIT_EXCEEDED', + error_code: 'NORDIGEN_ERROR', + status: 'rejected', + reason: 'Rate limit exceeded', + }); + break; + case error instanceof GenericGoCardlessError: + console.log('Something went wrong', inspect(error, { depth: null })); + sendErrorResponse({ + error_type: 'SYNC_ERROR', + error_code: 'NORDIGEN_ERROR', + }); + break; + case isAxiosError(error): + console.log( + 'Something went wrong', + inspect(error.response?.data || error, { depth: null }), + ); + sendErrorResponse({ + error_type: 'SYNC_ERROR', + error_code: 'NORDIGEN_ERROR', + }); + break; + default: + console.log('Something went wrong', inspect(error, { depth: null })); + sendErrorResponse({ + error_type: 'UNKNOWN', + error_code: 'UNKNOWN', + reason: 'Something went wrong', + }); + break; + } + } + }), +); diff --git a/packages/sync-server/src/app-gocardless/bank-factory.js b/packages/sync-server/src/app-gocardless/bank-factory.js new file mode 100644 index 00000000000..2bf09e1ec71 --- /dev/null +++ b/packages/sync-server/src/app-gocardless/bank-factory.js @@ -0,0 +1,111 @@ +import AbancaCaglesmm from './banks/abanca-caglesmm.js'; +import AmericanExpressAesudef1 from './banks/american-express-aesudef1.js'; +import BancsabadellBsabesbb from './banks/bancsabadell-bsabesbbb.js'; +import BankinterBkbkesmm from './banks/bankinter-bkbkesmm.js'; +import Belfius from './banks/belfius_gkccbebb.js'; +import Berliner_Sparkasse_beladebexxx from './banks/berliner_sparkasse_beladebexxx.js'; +import BnpBeGebabebb from './banks/bnp-be-gebabebb.js'; +import CBCcregbebb from './banks/cbc_cregbebb.js'; +import DanskeBankDabNO22 from './banks/danskebank-dabno22.js'; +import EasybankBawaatww from './banks/easybank-bawaatww.js'; +import Fortuneo from './banks/FORTUNEO_FTNOFRP1XXX.js'; +import IngIngbrobu from './banks/ing-ingbrobu.js'; +import IngIngddeff from './banks/ing-ingddeff.js'; +import IngPlIngbplpw from './banks/ing-pl-ingbplpw.js'; +import IntegrationBank from './banks/integration-bank.js'; +import KBCkredbebb from './banks/kbc_kredbebb.js'; +import MbankRetailBrexplpw from './banks/mbank-retail-brexplpw.js'; +import NationwideNaiaGB21 from './banks/nationwide-naiagb21.js'; +import NbgEthngraaxxx from './banks/nbg_ethngraaxxx.js'; +import NorwegianXxNorwnok1 from './banks/norwegian-xx-norwnok1.js'; +import RevolutRevolt21 from './banks/revolut_revolt21.js'; +import SandboxfinanceSfin0000 from './banks/sandboxfinance-sfin0000.js'; +import SEBKortBankAB from './banks/seb-kort-bank-ab.js'; +import SEBPrivat from './banks/seb-privat.js'; +import SparNordSpNoDK22 from './banks/sparnord-spnodk22.js'; +import SpkKarlsruhekarsde66 from './banks/spk-karlsruhe-karsde66.js'; +import SpkMarburgBiedenkopfHeladef1mar from './banks/spk-marburg-biedenkopf-heladef1mar.js'; +import SpkWormsAlzeyRiedMalade51wor from './banks/spk-worms-alzey-ried-malade51wor.js'; +import VirginNrnbgb22 from './banks/virgin_nrnbgb22.js'; + +export const banks = [ + AbancaCaglesmm, + AmericanExpressAesudef1, + BancsabadellBsabesbb, + BankinterBkbkesmm, + Belfius, + Berliner_Sparkasse_beladebexxx, + BnpBeGebabebb, + CBCcregbebb, + DanskeBankDabNO22, + EasybankBawaatww, + Fortuneo, + IngIngbrobu, + IngIngddeff, + IngPlIngbplpw, + KBCkredbebb, + MbankRetailBrexplpw, + NationwideNaiaGB21, + NbgEthngraaxxx, + NorwegianXxNorwnok1, + RevolutRevolt21, + SandboxfinanceSfin0000, + SEBKortBankAB, + SEBPrivat, + SparNordSpNoDK22, + SpkKarlsruhekarsde66, + SpkMarburgBiedenkopfHeladef1mar, + SpkWormsAlzeyRiedMalade51wor, + VirginNrnbgb22, +]; + +export default (institutionId) => + banks.find((b) => b.institutionIds.includes(institutionId)) || + IntegrationBank; + +export const BANKS_WITH_LIMITED_HISTORY = [ + 'BANCA_AIDEXA_AIDXITMM', + 'BANCA_PATRIMONI_SENVITT1', + 'BANCA_SELLA_SELBIT2B', + 'BANKINTER_BKBKESMM', + 'BBVA_BBVAESMM', + 'BRED_BREDFRPPXXX', + 'CAIXABANK_CAIXESBB', + 'CARTALIS_CIMTITR1', + 'CESKA_SPORITELNA_LONG_GIBACZPX', + 'COOP_EKRDEE22', + 'DOTS_HYEEIT22', + 'HYPE_BUSINESS_HYEEIT22', + 'HYPE_HYEEIT2', + 'ILLIMITY_ITTPIT2M', + 'INDUSTRA_MULTLV2X', + 'JEKYLL_JEYKLL002', + 'LABORALKUTXA_CLPEES2M', + 'LHV_LHVBEE22', + 'LUMINOR_AGBLLT2X', + 'LUMINOR_NDEAEE2X', + 'LUMINOR_NDEALT2X', + 'LUMINOR_NDEALV2X', + 'LUMINOR_RIKOEE22', + 'LUMINOR_RIKOLV2X', + 'MEDICINOSBANK_MDBALT22XXX', + 'NORDEA_NDEADKKK', + 'OPYN_BITAITRRB2B', + 'PAYTIPPER_PAYTITM1', + 'REVOLUT_REVOLT21', + 'SANTANDER_BSCHESMM', + 'SANTANDER_DE_SCFBDE33', + 'SEB_CBVILT2X', + 'SEB_EEUHEE2X', + 'SEB_UNLALV2X', + 'SELLA_PERSONAL_CREDIT_SELBIT22', + 'BANCOACTIVOBANK_ACTVPTPL', + 'SMARTIKA_SELBIT22', + 'SWEDBANK_HABAEE2X', + 'SWEDBANK_HABALT22', + 'SWEDBANK_HABALV22', + 'SWEDBANK_SWEDSESS', + 'TIM_HYEEIT22', + 'TOT_SELBIT2B', + 'VUB_BANKA_SUBASKBX', +]; diff --git a/packages/sync-server/src/app-gocardless/banks/FORTUNEO_FTNOFRP1XXX.js b/packages/sync-server/src/app-gocardless/banks/FORTUNEO_FTNOFRP1XXX.js new file mode 100644 index 00000000000..f73d8156b1f --- /dev/null +++ b/packages/sync-server/src/app-gocardless/banks/FORTUNEO_FTNOFRP1XXX.js @@ -0,0 +1,62 @@ +import { formatPayeeName } from '../../util/payee-name.js'; +import Fallback from './integration-bank.js'; +import * as d from 'date-fns'; + +/** @type {import('./bank.interface.js').IBank} */ +export default { + ...Fallback, + + institutionIds: ['FORTUNEO_FTNOFRP1XXX'], + + accessValidForDays: 90, + + normalizeTransaction(transaction, _booked) { + const date = + transaction.bookingDate || + transaction.bookingDateTime || + transaction.valueDate || + transaction.valueDateTime; + // If we couldn't find a valid date field we filter out this transaction + // and hope that we will import it again once the bank has processed the + // transaction further. + if (!date) { + return null; + } + + // Most of the information from the transaction is in the remittanceInformationUnstructuredArray field. + // We extract the creditor and debtor names from this field. + // The remittanceInformationUnstructuredArray field usually contain keywords like "Vir" for + // bank transfers or "Carte 03/06" for card payments, as well as the date. + // We remove these keywords to get a cleaner payee name. + const keywordsToRemove = [ + 'VIR INST', + 'VIR', + 'PRLV', + 'ANN CARTE', + 'CARTE \\d{2}\\/\\d{2}', + ]; + + const details = + transaction.remittanceInformationUnstructuredArray.join(' '); + const amount = transaction.transactionAmount.amount; + + const regex = new RegExp(keywordsToRemove.join('|'), 'g'); + const payeeName = details.replace(regex, '').trim(); + + // The amount is negative for outgoing transactions, positive for incoming transactions. + const isCreditorPayee = parseFloat(amount) < 0; + + // The payee name is the creditor name for outgoing transactions and the debtor name for incoming transactions. + const creditorName = isCreditorPayee ? payeeName : null; + const debtorName = isCreditorPayee ? null : payeeName; + + transaction.creditorName = creditorName; + transaction.debtorName = debtorName; + + return { + ...transaction, + payeeName: formatPayeeName(transaction), + date: d.format(d.parseISO(date), 'yyyy-MM-dd'), + }; + }, +}; diff --git a/packages/sync-server/src/app-gocardless/banks/abanca-caglesmm.js b/packages/sync-server/src/app-gocardless/banks/abanca-caglesmm.js new file mode 100644 index 00000000000..bd485331bbb --- /dev/null +++ b/packages/sync-server/src/app-gocardless/banks/abanca-caglesmm.js @@ -0,0 +1,24 @@ +import Fallback from './integration-bank.js'; + +import { formatPayeeName } from '../../util/payee-name.js'; + +/** @type {import('./bank.interface.js').IBank} */ +export default { + ...Fallback, + + institutionIds: ['ABANCA_CAGLESMM', 'ABANCA_CAGLPTPL'], + + accessValidForDays: 180, + + // Abanca transactions doesn't get the creditorName/debtorName properly + normalizeTransaction(transaction, _booked) { + transaction.creditorName = transaction.remittanceInformationStructured; + transaction.debtorName = transaction.remittanceInformationStructured; + + return { + ...transaction, + payeeName: formatPayeeName(transaction), + date: transaction.bookingDate || transaction.valueDate, + }; + }, +}; diff --git a/packages/sync-server/src/app-gocardless/banks/american-express-aesudef1.js b/packages/sync-server/src/app-gocardless/banks/american-express-aesudef1.js new file mode 100644 index 00000000000..1f5e22833be --- /dev/null +++ b/packages/sync-server/src/app-gocardless/banks/american-express-aesudef1.js @@ -0,0 +1,55 @@ +import Fallback from './integration-bank.js'; + +import { amountToInteger } from '../utils.js'; +import { formatPayeeName } from '../../util/payee-name.js'; + +/** @type {import('./bank.interface.js').IBank} */ +export default { + ...Fallback, + + institutionIds: ['AMERICAN_EXPRESS_AESUDEF1'], + + accessValidForDays: 180, + + normalizeAccount(account) { + return { + account_id: account.id, + institution: account.institution, + // The `iban` field for these American Express cards is actually a masked + // version of the PAN. No IBAN is provided. + mask: account.iban.slice(-5), + iban: null, + name: [account.details, `(${account.iban.slice(-5)})`].join(' '), + official_name: account.details, + // The Actual account `type` field is legacy and is currently not used + // for anything, so we leave it as the default of `checking`. + type: 'checking', + }; + }, + + normalizeTransaction(transaction, _booked) { + return { + ...transaction, + payeeName: formatPayeeName(transaction), + date: transaction.bookingDate, + }; + }, + + /** + * For AMERICAN_EXPRESS_AESUDEF1 we don't know what balance was + * after each transaction so we have to calculate it by getting + * current balance from the account and subtract all the transactions + * + * As a current balance we use the non-standard `information` balance type + * which is the only one provided for American Express. + */ + calculateStartingBalance(sortedTransactions = [], balances = []) { + const currentBalance = balances.find( + (balance) => 'information' === balance.balanceType.toString(), + ); + + return sortedTransactions.reduce((total, trans) => { + return total - amountToInteger(trans.transactionAmount.amount); + }, amountToInteger(currentBalance.balanceAmount.amount)); + }, +}; diff --git a/packages/sync-server/src/app-gocardless/banks/bancsabadell-bsabesbbb.js b/packages/sync-server/src/app-gocardless/banks/bancsabadell-bsabesbbb.js new file mode 100644 index 00000000000..832fb8953cf --- /dev/null +++ b/packages/sync-server/src/app-gocardless/banks/bancsabadell-bsabesbbb.js @@ -0,0 +1,37 @@ +import Fallback from './integration-bank.js'; + +import { formatPayeeName } from '../../util/payee-name.js'; + +/** @type {import('./bank.interface.js').IBank} */ +export default { + ...Fallback, + + institutionIds: ['BANCSABADELL_BSABESBB'], + + accessValidForDays: 180, + + // Sabadell transactions don't get the creditorName/debtorName properly + normalizeTransaction(transaction, _booked) { + const amount = transaction.transactionAmount.amount; + + // The amount is negative for outgoing transactions, positive for incoming transactions. + const isCreditorPayee = Number.parseFloat(amount) < 0; + + const payeeName = transaction.remittanceInformationUnstructuredArray + .join(' ') + .trim(); + + // The payee name is the creditor name for outgoing transactions and the debtor name for incoming transactions. + const creditorName = isCreditorPayee ? payeeName : null; + const debtorName = isCreditorPayee ? null : payeeName; + + transaction.creditorName = creditorName; + transaction.debtorName = debtorName; + + return { + ...transaction, + payeeName: formatPayeeName(transaction), + date: transaction.bookingDate || transaction.valueDate, + }; + }, +}; diff --git a/packages/sync-server/src/app-gocardless/banks/bank.interface.ts b/packages/sync-server/src/app-gocardless/banks/bank.interface.ts new file mode 100644 index 00000000000..21e87581567 --- /dev/null +++ b/packages/sync-server/src/app-gocardless/banks/bank.interface.ts @@ -0,0 +1,44 @@ +import { + DetailedAccountWithInstitution, + NormalizedAccountDetails, +} from '../gocardless.types.js'; +import { Transaction, Balance } from '../gocardless-node.types.js'; + +export interface IBank { + institutionIds: string[]; + + accessValidForDays: number; + + /** + * Returns normalized object with required data for the frontend + */ + normalizeAccount: ( + account: DetailedAccountWithInstitution, + ) => NormalizedAccountDetails; + + /** + * Returns a normalized transaction object + * + * The GoCardless integrations with different banks are very inconsistent in + * what each of the different date fields actually mean, so this function is + * expected to set a `date` field which corresponds to the expected + * transaction date. + */ + normalizeTransaction: ( + transaction: Transaction, + booked: boolean, + ) => (Transaction & { date?: string; payeeName: string }) | null; + + /** + * Function sorts an array of transactions from newest to oldest + */ + sortTransactions: <T extends Transaction>(transactions: T[]) => T[]; + + /** + * Calculates account balance before which was before transactions provided in sortedTransactions param + */ + calculateStartingBalance: ( + sortedTransactions: Transaction[], + balances: Balance[], + ) => number; +} diff --git a/packages/sync-server/src/app-gocardless/banks/bankinter-bkbkesmm.js b/packages/sync-server/src/app-gocardless/banks/bankinter-bkbkesmm.js new file mode 100644 index 00000000000..c7e17059f4a --- /dev/null +++ b/packages/sync-server/src/app-gocardless/banks/bankinter-bkbkesmm.js @@ -0,0 +1,43 @@ +import Fallback from './integration-bank.js'; + +import { printIban } from '../utils.js'; +import { formatPayeeName } from '../../util/payee-name.js'; + +/** @type {import('./bank.interface.js').IBank} */ +export default { + ...Fallback, + + institutionIds: ['BANKINTER_BKBKESMM'], + + accessValidForDays: 90, + + normalizeAccount(account) { + return { + account_id: account.id, + institution: account.institution, + mask: account.iban.slice(-4), + iban: account.iban, + name: [account.name, printIban(account)].join(' '), + official_name: account.product, + type: 'checking', + }; + }, + + normalizeTransaction(transaction, _booked) { + transaction.remittanceInformationUnstructured = + transaction.remittanceInformationUnstructured + .replaceAll(/\/Txt\/(\w\|)?/gi, '') + .replaceAll(';', ' '); + + transaction.debtorName = transaction.debtorName?.replaceAll(';', ' '); + transaction.creditorName = + transaction.creditorName?.replaceAll(';', ' ') ?? + transaction.remittanceInformationUnstructured; + + return { + ...transaction, + payeeName: formatPayeeName(transaction), + date: transaction.bookingDate || transaction.valueDate, + }; + }, +}; diff --git a/packages/sync-server/src/app-gocardless/banks/belfius_gkccbebb.js b/packages/sync-server/src/app-gocardless/banks/belfius_gkccbebb.js new file mode 100644 index 00000000000..ebec54e1269 --- /dev/null +++ b/packages/sync-server/src/app-gocardless/banks/belfius_gkccbebb.js @@ -0,0 +1,24 @@ +import Fallback from './integration-bank.js'; + +import { formatPayeeName } from '../../util/payee-name.js'; + +/** @type {import('./bank.interface.js').IBank} */ +export default { + ...Fallback, + + institutionIds: ['BELFIUS_GKCCBEBB'], + + accessValidForDays: 180, + + // The problem is that we have transaction with duplicated transaction ids. + // This is not expected and the nordigen api has a work-around for some backs + // They will set an internalTransactionId which is unique + normalizeTransaction(transaction, _booked) { + return { + ...transaction, + transactionId: transaction.internalTransactionId, + payeeName: formatPayeeName(transaction), + date: transaction.bookingDate || transaction.valueDate, + }; + }, +}; diff --git a/packages/sync-server/src/app-gocardless/banks/berliner_sparkasse_beladebexxx.js b/packages/sync-server/src/app-gocardless/banks/berliner_sparkasse_beladebexxx.js new file mode 100644 index 00000000000..6279d9e38e7 --- /dev/null +++ b/packages/sync-server/src/app-gocardless/banks/berliner_sparkasse_beladebexxx.js @@ -0,0 +1,98 @@ +import Fallback from './integration-bank.js'; + +import { printIban, amountToInteger } from '../utils.js'; +import { formatPayeeName } from '../../util/payee-name.js'; + +/** @type {import('./bank.interface.js').IBank} */ +export default { + ...Fallback, + + institutionIds: ['BERLINER_SPARKASSE_BELADEBEXXX'], + + accessValidForDays: 90, + + normalizeAccount(account) { + return { + account_id: account.id, + institution: account.institution, + mask: account.iban.slice(-4), + iban: account.iban, + name: [account.name, printIban(account)].join(' '), + official_name: account.product, + type: 'checking', + }; + }, + + /** + * Following the GoCardless documentation[0] we should prefer `bookingDate` + * here, though some of their bank integrations uses the date field + * differently from what's described in their documentation and so it's + * sometimes necessary to use `valueDate` instead. + * + * [0]: https://nordigen.zendesk.com/hc/en-gb/articles/7899367372829-valueDate-and-bookingDate-for-transactions + */ + normalizeTransaction(transaction, _booked) { + const date = + transaction.bookingDate || + transaction.bookingDateTime || + transaction.valueDate || + transaction.valueDateTime; + + // If we couldn't find a valid date field we filter out this transaction + // and hope that we will import it again once the bank has processed the + // transaction further. + if (!date) { + return null; + } + + let remittanceInformationUnstructured; + + if (transaction.remittanceInformationUnstructured) { + remittanceInformationUnstructured = + transaction.remittanceInformationUnstructured; + } else if (transaction.remittanceInformationStructured) { + remittanceInformationUnstructured = + transaction.remittanceInformationStructured; + } else if (transaction.remittanceInformationStructuredArray?.length > 0) { + remittanceInformationUnstructured = + transaction.remittanceInformationStructuredArray?.join(' '); + } + + if (transaction.additionalInformation) + remittanceInformationUnstructured += + ' ' + transaction.additionalInformation; + + const usefulCreditorName = + transaction.ultimateCreditor || + transaction.creditorName || + transaction.debtorName; + + transaction.creditorName = usefulCreditorName; + transaction.remittanceInformationUnstructured = + remittanceInformationUnstructured; + + return { + ...transaction, + payeeName: formatPayeeName(transaction), + date: transaction.bookingDate || transaction.valueDate, + }; + }, + + /** + * For SANDBOXFINANCE_SFIN0000 we don't know what balance was + * after each transaction so we have to calculate it by getting + * current balance from the account and subtract all the transactions + * + * As a current balance we use `interimBooked` balance type because + * it includes transaction placed during current day + */ + calculateStartingBalance(sortedTransactions = [], balances = []) { + const currentBalance = balances.find( + (balance) => 'interimAvailable' === balance.balanceType, + ); + + return sortedTransactions.reduce((total, trans) => { + return total - amountToInteger(trans.transactionAmount.amount); + }, amountToInteger(currentBalance.balanceAmount.amount)); + }, +}; diff --git a/packages/sync-server/src/app-gocardless/banks/bnp-be-gebabebb.js b/packages/sync-server/src/app-gocardless/banks/bnp-be-gebabebb.js new file mode 100644 index 00000000000..9af2fa674b9 --- /dev/null +++ b/packages/sync-server/src/app-gocardless/banks/bnp-be-gebabebb.js @@ -0,0 +1,79 @@ +import Fallback from './integration-bank.js'; + +import { formatPayeeName } from '../../util/payee-name.js'; + +/** @type {import('./bank.interface.js').IBank} */ +export default { + ...Fallback, + + institutionIds: [ + 'FINTRO_BE_GEBABEBB', + 'HELLO_BE_GEBABEBB', + 'BNP_BE_GEBABEBB', + ], + + accessValidForDays: 180, + + /** BNP_BE_GEBABEBB provides a lot of useful information via the 'additionalField' + * There does not seem to be a specification of this field, but the following information is contained in its subfields: + * - for pending transactions: the 'atmPosName' + * - for booked transactions: the 'narrative'. + * This narrative subfield is most useful as it contains information required to identify the transaction, + * especially in case of debit card or instant payment transactions. + * Do note that the narrative subfield ALSO contains the remittance information if any. + * The goal of the normalization is to place any relevant information of the additionalInformation + * field in the remittanceInformationUnstructuredArray field. + */ + normalizeTransaction(transaction, _booked) { + // Extract the creditor name to fill it in with information from the + // additionalInformation field in case it's not yet defined. + let creditorName = transaction.creditorName; + + if (transaction.additionalInformation) { + let additionalInformationObject = {}; + const additionalInfoRegex = /(, )?([^:]+): ((\[.*?\])|([^,]*))/g; + let matches = + transaction.additionalInformation.matchAll(additionalInfoRegex); + if (matches) { + let creditorNameFromNarrative; // Possible value for creditorName + for (let match of matches) { + let key = match[2].trim(); + let value = (match[4] || match[5]).trim(); + if (key === 'narrative') { + // Set narrativeName to the first element in the "narrative" array. + let first_value = value.matchAll(/'(.+?)'/g)?.next().value; + creditorNameFromNarrative = first_value + ? first_value[1].trim() + : undefined; + } + // Remove square brackets and single quotes and commas + value = value.replace(/[[\]',]/g, ''); + additionalInformationObject[key] = value; + } + // Keep existing unstructuredArray and add atmPosName and narrative + transaction.remittanceInformationUnstructuredArray = [ + transaction.remittanceInformationUnstructuredArray ?? '', + additionalInformationObject?.atmPosName ?? '', + additionalInformationObject?.narrative ?? '', + ].filter(Boolean); + + // If the creditor name doesn't exist in the original transactions, + // set it to the atmPosName or narrativeName if they exist; otherwise + // leave empty and let the default rules handle it. + creditorName = + creditorName ?? + additionalInformationObject?.atmPosName ?? + creditorNameFromNarrative ?? + null; + } + } + + transaction.creditorName = creditorName; + + return { + ...transaction, + payeeName: formatPayeeName(transaction), + date: transaction.valueDate || transaction.bookingDate, + }; + }, +}; diff --git a/packages/sync-server/src/app-gocardless/banks/cbc_cregbebb.js b/packages/sync-server/src/app-gocardless/banks/cbc_cregbebb.js new file mode 100644 index 00000000000..bf881dacf36 --- /dev/null +++ b/packages/sync-server/src/app-gocardless/banks/cbc_cregbebb.js @@ -0,0 +1,36 @@ +import { extractPayeeNameFromRemittanceInfo } from './util/extract-payeeName-from-remittanceInfo.js'; +import Fallback from './integration-bank.js'; + +/** @type {import('./bank.interface.js').IBank} */ +export default { + ...Fallback, + + institutionIds: ['CBC_CREGBEBB'], + + /** + * For negative amounts, the only payee information we have is returned in + * remittanceInformationUnstructured. + */ + normalizeTransaction(transaction, _booked) { + if (Number(transaction.transactionAmount.amount) > 0) { + return { + ...transaction, + payeeName: + transaction.debtorName || + transaction.remittanceInformationUnstructured, + date: transaction.bookingDate || transaction.valueDate, + }; + } + + return { + ...transaction, + payeeName: + transaction.creditorName || + extractPayeeNameFromRemittanceInfo( + transaction.remittanceInformationUnstructured, + ['Paiement', 'Domiciliation', 'Transfert', 'Ordre permanent'], + ), + date: transaction.bookingDate || transaction.valueDate, + }; + }, +}; diff --git a/packages/sync-server/src/app-gocardless/banks/danskebank-dabno22.js b/packages/sync-server/src/app-gocardless/banks/danskebank-dabno22.js new file mode 100644 index 00000000000..99fa891aad1 --- /dev/null +++ b/packages/sync-server/src/app-gocardless/banks/danskebank-dabno22.js @@ -0,0 +1,60 @@ +import Fallback from './integration-bank.js'; + +import { printIban, amountToInteger } from '../utils.js'; +import { formatPayeeName } from '../../util/payee-name.js'; + +/** @type {import('./bank.interface.js').IBank} */ +export default { + ...Fallback, + + institutionIds: ['DANSKEBANK_DABANO22'], + + accessValidForDays: 180, + + normalizeAccount(account) { + return { + account_id: account.id, + institution: account.institution, + mask: account.iban.slice(-4), + iban: account.iban, + name: [account.name, printIban(account)].join(' '), + official_name: account.name, + type: 'checking', + }; + }, + + normalizeTransaction(transaction, _booked) { + /** + * Danske Bank appends the EndToEndID: NOTPROVIDED to + * remittanceInformationUnstructured, cluttering the data. + * + * We clean thais up by removing any instances of this string from all transactions. + * + */ + transaction.remittanceInformationUnstructured = + transaction.remittanceInformationUnstructured.replace( + '\nEndToEndID: NOTPROVIDED', + '', + ); + + /** + * The valueDate in transactions from Danske Bank is not the one expected, but rather the date + * the funds are expected to be paid back for credit accounts. + */ + return { + ...transaction, + payeeName: formatPayeeName(transaction), + date: transaction.bookingDate, + }; + }, + + calculateStartingBalance(sortedTransactions = [], balances = []) { + const currentBalance = balances.find( + (balance) => balance.balanceType === 'interimAvailable', + ); + + return sortedTransactions.reduce((total, trans) => { + return total - amountToInteger(trans.transactionAmount.amount); + }, amountToInteger(currentBalance.balanceAmount.amount)); + }, +}; diff --git a/packages/sync-server/src/app-gocardless/banks/easybank-bawaatww.js b/packages/sync-server/src/app-gocardless/banks/easybank-bawaatww.js new file mode 100644 index 00000000000..e738d8302a6 --- /dev/null +++ b/packages/sync-server/src/app-gocardless/banks/easybank-bawaatww.js @@ -0,0 +1,61 @@ +import Fallback from './integration-bank.js'; + +import { formatPayeeName } from '../../util/payee-name.js'; +import d from 'date-fns'; +import { title } from '../../util/title/index.js'; + +/** @type {import('./bank.interface.js').IBank} */ +export default { + ...Fallback, + + institutionIds: ['EASYBANK_BAWAATWW'], + + accessValidForDays: 180, + + // If date is same, sort by transactionId + sortTransactions: (transactions = []) => + transactions.sort((a, b) => { + const diff = + +new Date(b.valueDate || b.bookingDate) - + +new Date(a.valueDate || a.bookingDate); + if (diff != 0) return diff; + return parseInt(b.transactionId) - parseInt(a.transactionId); + }), + + normalizeTransaction(transaction, _booked) { + const date = transaction.bookingDate || transaction.valueDate; + + // If we couldn't find a valid date field we filter out this transaction + // and hope that we will import it again once the bank has processed the + // transaction further. + if (!date) { + return null; + } + + let payeeName = formatPayeeName(transaction); + if (!payeeName) payeeName = extractPayeeName(transaction); + + return { + ...transaction, + payeeName: payeeName, + date: d.format(d.parseISO(date), 'yyyy-MM-dd'), + }; + }, +}; + +/** + * Extracts the payee name from the remittanceInformationStructured + * @param {import('../gocardless-node.types.js').Transaction} transaction + */ +function extractPayeeName(transaction) { + const structured = transaction.remittanceInformationStructured; + // The payee name is betweeen the transaction timestamp (11.07. 11:36) and the location, that starts with \\ + const regex = /\d{2}\.\d{2}\. \d{2}:\d{2}(.*)\\\\/; + const matches = structured.match(regex); + if (matches && matches.length > 1 && matches[1]) { + return title(matches[1]); + } else { + // As a fallback if still no payee is found, the whole information is used + return structured; + } +} diff --git a/packages/sync-server/src/app-gocardless/banks/ing-ingbrobu.js b/packages/sync-server/src/app-gocardless/banks/ing-ingbrobu.js new file mode 100644 index 00000000000..bbf39b6f4e7 --- /dev/null +++ b/packages/sync-server/src/app-gocardless/banks/ing-ingbrobu.js @@ -0,0 +1,58 @@ +import Fallback from './integration-bank.js'; + +/** @type {import('./bank.interface.js').IBank} */ +export default { + ...Fallback, + + institutionIds: ['ING_INGBROBU'], + + accessValidForDays: 180, + + normalizeTransaction(transaction, booked) { + //Merchant transactions all have the same transactionId of 'NOTPROVIDED'. + //For booked transactions, this can be set to the internalTransactionId + //For pending transactions, this needs to be removed for them to show up in Actual + + //For deduplication to work better, payeeName needs to be standardized + //and converted from a pending transaction form ("payeeName":"Card no: xxxxxxxxxxxx1111"') to a booked transaction form ("payeeName":"Card no: Xxxx Xxxx Xxxx 1111") + if (transaction.transactionId === 'NOTPROVIDED') { + if (booked) { + transaction.transactionId = transaction.internalTransactionId; + if ( + transaction.remittanceInformationUnstructured + .toLowerCase() + .includes('card no:') + ) { + transaction.creditorName = + transaction.remittanceInformationUnstructured.split(',')[0]; + //Catch all case for other types of payees + } else { + transaction.creditorName = + transaction.remittanceInformationUnstructured; + } + } else { + transaction.transactionId = null; + if ( + transaction.remittanceInformationUnstructured + .toLowerCase() + .includes('card no:') + ) { + transaction.creditorName = + transaction.remittanceInformationUnstructured.replace( + /x{4}/g, + 'Xxxx ', + ); + //Catch all case for other types of payees + } else { + transaction.creditorName = + transaction.remittanceInformationUnstructured; + } + //Remove remittanceInformationUnstructured from pending transactions, so the `notes` field remains empty (there is no merchant information) + //Once booked, the right `notes` (containing the merchant) will be populated + transaction.remittanceInformationUnstructured = null; + } + } + + return Fallback.normalizeTransaction(transaction, booked); + }, +}; diff --git a/packages/sync-server/src/app-gocardless/banks/ing-ingddeff.js b/packages/sync-server/src/app-gocardless/banks/ing-ingddeff.js new file mode 100644 index 00000000000..2fecdaa24cd --- /dev/null +++ b/packages/sync-server/src/app-gocardless/banks/ing-ingddeff.js @@ -0,0 +1,64 @@ +import Fallback from './integration-bank.js'; + +import { printIban, amountToInteger } from '../utils.js'; +import { formatPayeeName } from '../../util/payee-name.js'; + +/** @type {import('./bank.interface.js').IBank} */ +export default { + ...Fallback, + + institutionIds: ['ING_INGDDEFF'], + + accessValidForDays: 180, + + normalizeAccount(account) { + return { + account_id: account.id, + institution: account.institution, + mask: account.iban.slice(-4), + iban: account.iban, + name: [account.product, printIban(account)].join(' '), + official_name: account.product, + type: 'checking', + }; + }, + + normalizeTransaction(transaction, _booked) { + const remittanceInformationMatch = /remittanceinformation:(.*)$/.exec( + transaction.remittanceInformationUnstructured, + ); + + transaction.remittanceInformationUnstructured = remittanceInformationMatch + ? remittanceInformationMatch[1] + : transaction.remittanceInformationUnstructured; + + return { + ...transaction, + payeeName: formatPayeeName(transaction), + date: transaction.bookingDate || transaction.valueDate, + }; + }, + + sortTransactions(transactions = []) { + return transactions.sort((a, b) => { + const diff = + +new Date(b.valueDate || b.bookingDate) - + +new Date(a.valueDate || a.bookingDate); + if (diff) return diff; + const idA = parseInt(a.transactionId); + const idB = parseInt(b.transactionId); + if (!isNaN(idA) && !isNaN(idB)) return idB - idA; + return 0; + }); + }, + + calculateStartingBalance(sortedTransactions = [], balances = []) { + const currentBalance = balances.find( + (balance) => 'interimBooked' === balance.balanceType, + ); + + return sortedTransactions.reduce((total, trans) => { + return total - amountToInteger(trans.transactionAmount.amount); + }, amountToInteger(currentBalance.balanceAmount.amount)); + }, +}; diff --git a/packages/sync-server/src/app-gocardless/banks/ing-pl-ingbplpw.js b/packages/sync-server/src/app-gocardless/banks/ing-pl-ingbplpw.js new file mode 100644 index 00000000000..13928fbe2e3 --- /dev/null +++ b/packages/sync-server/src/app-gocardless/banks/ing-pl-ingbplpw.js @@ -0,0 +1,61 @@ +import Fallback from './integration-bank.js'; + +import { printIban, amountToInteger } from '../utils.js'; +import { formatPayeeName } from '../../util/payee-name.js'; + +/** @type {import('./bank.interface.js').IBank} */ +export default { + ...Fallback, + + institutionIds: ['ING_PL_INGBPLPW'], + + accessValidForDays: 180, + + normalizeAccount(account) { + return { + account_id: account.id, + institution: account.institution, + mask: account.iban.slice(-4), + iban: account.iban, + name: [account.product, printIban(account)].join(' ').trim(), + official_name: account.product, + type: 'checking', + }; + }, + + normalizeTransaction(transaction, _booked) { + return { + ...transaction, + payeeName: formatPayeeName(transaction), + date: transaction.bookingDate || transaction.valueDate, + }; + }, + + sortTransactions(transactions = []) { + return transactions.sort((a, b) => { + return ( + Number(b.transactionId.substr(2)) - Number(a.transactionId.substr(2)) + ); + }); + }, + + calculateStartingBalance(sortedTransactions = [], balances = []) { + if (sortedTransactions.length) { + const oldestTransaction = + sortedTransactions[sortedTransactions.length - 1]; + const oldestKnownBalance = amountToInteger( + oldestTransaction.balanceAfterTransaction.balanceAmount.amount, + ); + const oldestTransactionAmount = amountToInteger( + oldestTransaction.transactionAmount.amount, + ); + + return oldestKnownBalance - oldestTransactionAmount; + } else { + return amountToInteger( + balances.find((balance) => 'interimBooked' === balance.balanceType) + .balanceAmount.amount, + ); + } + }, +}; diff --git a/packages/sync-server/src/app-gocardless/banks/integration-bank.js b/packages/sync-server/src/app-gocardless/banks/integration-bank.js new file mode 100644 index 00000000000..cbe6cd57f96 --- /dev/null +++ b/packages/sync-server/src/app-gocardless/banks/integration-bank.js @@ -0,0 +1,98 @@ +import * as d from 'date-fns'; +import { + amountToInteger, + printIban, + sortByBookingDateOrValueDate, +} from '../utils.js'; +import { formatPayeeName } from '../../util/payee-name.js'; + +const SORTED_BALANCE_TYPE_LIST = [ + 'closingBooked', + 'expected', + 'forwardAvailable', + 'interimAvailable', + 'interimBooked', + 'nonInvoiced', + 'openingBooked', +]; + +/** @type {import('./bank.interface.js').IBank} */ +export default { + institutionIds: ['IntegrationBank'], + + // EEA need to allow at least 180 days now but this doesn't apply to UK + // banks, and it's possible that there are EEA banks which still don't follow + // the new requirements. See: + // - https://nordigen.zendesk.com/hc/en-gb/articles/13239212055581-EEA-180-day-access + // - https://nordigen.zendesk.com/hc/en-gb/articles/6760902653085-Extended-history-and-continuous-access-edge-cases + accessValidForDays: 90, + + normalizeAccount(account) { + console.log( + 'Available account properties for new institution integration', + { account: JSON.stringify(account) }, + ); + + return { + account_id: account.id, + institution: account.institution, + mask: (account?.iban || '0000').slice(-4), + iban: account?.iban || null, + name: [account.name, printIban(account), account.currency] + .filter(Boolean) + .join(' '), + official_name: `integration-${account.institution_id}`, + type: 'checking', + }; + }, + + normalizeTransaction(transaction, _booked) { + const date = + transaction.bookingDate || + transaction.bookingDateTime || + transaction.valueDate || + transaction.valueDateTime; + // If we couldn't find a valid date field we filter out this transaction + // and hope that we will import it again once the bank has processed the + // transaction further. + if (!date) { + return null; + } + return { + ...transaction, + payeeName: formatPayeeName(transaction), + date: d.format(d.parseISO(date), 'yyyy-MM-dd'), + }; + }, + + sortTransactions(transactions = []) { + console.log( + 'Available (first 10) transactions properties for new integration of institution in sortTransactions function', + { top10Transactions: JSON.stringify(transactions.slice(0, 10)) }, + ); + return sortByBookingDateOrValueDate(transactions); + }, + + calculateStartingBalance(sortedTransactions = [], balances = []) { + console.log( + 'Available (first 10) transactions properties for new integration of institution in calculateStartingBalance function', + { + balances: JSON.stringify(balances), + top10SortedTransactions: JSON.stringify( + sortedTransactions.slice(0, 10), + ), + }, + ); + + const currentBalance = balances + .filter((item) => SORTED_BALANCE_TYPE_LIST.includes(item.balanceType)) + .sort( + (a, b) => + SORTED_BALANCE_TYPE_LIST.indexOf(a.balanceType) - + SORTED_BALANCE_TYPE_LIST.indexOf(b.balanceType), + )[0]; + return sortedTransactions.reduce((total, trans) => { + return total - amountToInteger(trans.transactionAmount.amount); + }, amountToInteger(currentBalance?.balanceAmount?.amount || 0)); + }, +}; diff --git a/packages/sync-server/src/app-gocardless/banks/kbc_kredbebb.js b/packages/sync-server/src/app-gocardless/banks/kbc_kredbebb.js new file mode 100644 index 00000000000..f2c7a725e42 --- /dev/null +++ b/packages/sync-server/src/app-gocardless/banks/kbc_kredbebb.js @@ -0,0 +1,36 @@ +import { extractPayeeNameFromRemittanceInfo } from './util/extract-payeeName-from-remittanceInfo.js'; +import Fallback from './integration-bank.js'; + +/** @type {import('./bank.interface.js').IBank} */ +export default { + ...Fallback, + + institutionIds: ['KBC_KREDBEBB'], + + /** + * For negative amounts, the only payee information we have is returned in + * remittanceInformationUnstructured. + */ + normalizeTransaction(transaction, _booked) { + if (Number(transaction.transactionAmount.amount) > 0) { + return { + ...transaction, + payeeName: + transaction.debtorName || + transaction.remittanceInformationUnstructured, + date: transaction.bookingDate || transaction.valueDate, + }; + } + + return { + ...transaction, + payeeName: + transaction.creditorName || + extractPayeeNameFromRemittanceInfo( + transaction.remittanceInformationUnstructured, + ['Betaling met', 'Domiciliëring', 'Overschrijving'], + ), + date: transaction.bookingDate || transaction.valueDate, + }; + }, +}; diff --git a/packages/sync-server/src/app-gocardless/banks/mbank-retail-brexplpw.js b/packages/sync-server/src/app-gocardless/banks/mbank-retail-brexplpw.js new file mode 100644 index 00000000000..c2f84ad326e --- /dev/null +++ b/packages/sync-server/src/app-gocardless/banks/mbank-retail-brexplpw.js @@ -0,0 +1,57 @@ +import Fallback from './integration-bank.js'; + +import { printIban, amountToInteger } from '../utils.js'; +import { formatPayeeName } from '../../util/payee-name.js'; + +/** @type {import('./bank.interface.js').IBank} */ +export default { + ...Fallback, + + institutionIds: ['MBANK_RETAIL_BREXPLPW'], + + accessValidForDays: 179, + + normalizeAccount(account) { + return { + account_id: account.id, + institution: account.institution, + mask: account.iban.slice(-4), + iban: account.iban, + name: [account.displayName, printIban(account)].join(' '), + official_name: account.product, + type: 'checking', + }; + }, + + normalizeTransaction(transaction, _booked) { + return { + ...transaction, + payeeName: formatPayeeName(transaction), + date: transaction.bookingDate || transaction.valueDate, + }; + }, + + sortTransactions(transactions = []) { + return transactions.sort( + (a, b) => Number(b.transactionId) - Number(a.transactionId), + ); + }, + + /** + * For MBANK_RETAIL_BREXPLPW we don't know what balance was + * after each transaction so we have to calculate it by getting + * current balance from the account and subtract all the transactions + * + * As a current balance we use `interimBooked` balance type because + * it includes transaction placed during current day + */ + calculateStartingBalance(sortedTransactions = [], balances = []) { + const currentBalance = balances.find( + (balance) => 'interimBooked' === balance.balanceType, + ); + + return sortedTransactions.reduce((total, trans) => { + return total - amountToInteger(trans.transactionAmount.amount); + }, amountToInteger(currentBalance.balanceAmount.amount)); + }, +}; diff --git a/packages/sync-server/src/app-gocardless/banks/nationwide-naiagb21.js b/packages/sync-server/src/app-gocardless/banks/nationwide-naiagb21.js new file mode 100644 index 00000000000..90a4c81a3ba --- /dev/null +++ b/packages/sync-server/src/app-gocardless/banks/nationwide-naiagb21.js @@ -0,0 +1,46 @@ +import Fallback from './integration-bank.js'; + +/** @type {import('./bank.interface.js').IBank} */ +export default { + ...Fallback, + + institutionIds: ['NATIONWIDE_NAIAGB21'], + + accessValidForDays: 90, + + normalizeTransaction(transaction, booked) { + // Nationwide can sometimes return pending transactions with a date + // representing the latest a transaction could be booked. This stops + // actual's deduplication logic from working as it only checks 7 days + // ahead/behind and the transactionID from Nationwide changes when a + // transaction is booked + if (!booked) { + const useDate = new Date( + Math.min( + new Date(transaction.bookingDate).getTime(), + new Date().getTime(), + ), + ); + transaction.bookingDate = useDate.toISOString().slice(0, 10); + } + + // Nationwide also occasionally returns erroneous transaction_ids + // that are malformed and can even change after import. This will ignore + // these ids and unset them. When a correct ID is returned then it will + // update via the deduplication logic + const debitCreditRegex = /^00(DEB|CRED)IT.+$/; + const validLengths = [ + 40, // Nationwide credit cards + 32, // Nationwide current accounts + ]; + + if ( + transaction.transactionId?.match(debitCreditRegex) || + !validLengths.includes(transaction.transactionId?.length) + ) { + transaction.transactionId = null; + } + + return Fallback.normalizeTransaction(transaction, booked); + }, +}; diff --git a/packages/sync-server/src/app-gocardless/banks/nbg_ethngraaxxx.js b/packages/sync-server/src/app-gocardless/banks/nbg_ethngraaxxx.js new file mode 100644 index 00000000000..a03c1663e9a --- /dev/null +++ b/packages/sync-server/src/app-gocardless/banks/nbg_ethngraaxxx.js @@ -0,0 +1,71 @@ +import Fallback from './integration-bank.js'; + +import { printIban, amountToInteger } from '../utils.js'; +import { formatPayeeName } from '../../util/payee-name.js'; + +/** @type {import('./bank.interface.js').IBank} */ +export default { + ...Fallback, + + institutionIds: ['NBG_ETHNGRAAXXX'], + + accessValidForDays: 90, + + normalizeAccount(account) { + return { + account_id: account.id, + institution: account.institution, + mask: account.iban.slice(-4), + iban: account.iban, + name: [account.name, printIban(account)].join(' '), + official_name: account.product, + type: 'checking', + }; + }, + + /** + * Fixes for the pending transactions: + * - Corrects amount to negative (nbg erroneously omits the minus sign in pending transactions) + * - Removes prefix 'ΑΓΟΡΑ' from remittance information to align with the booked transaction (necessary for fuzzy matching to work) + */ + normalizeTransaction(transaction, _booked) { + if ( + !transaction.transactionId && + transaction.remittanceInformationUnstructured.startsWith('ΑΓΟΡΑ ') + ) { + transaction = { + ...transaction, + transactionAmount: { + amount: '-' + transaction.transactionAmount.amount, + currency: transaction.transactionAmount.currency, + }, + remittanceInformationUnstructured: + transaction.remittanceInformationUnstructured.substring(6), + }; + } + + return { + ...transaction, + payeeName: formatPayeeName(transaction), + date: transaction.bookingDate || transaction.valueDate, + }; + }, + + /** + * For NBG_ETHNGRAAXXX we don't know what balance was + * after each transaction so we have to calculate it by getting + * current balance from the account and subtract all the transactions + * + * As a current balance we use `interimBooked` balance type because + * it includes transaction placed during current day + */ + calculateStartingBalance(sortedTransactions = [], balances = []) { + const currentBalance = balances.find( + (balance) => 'interimAvailable' === balance.balanceType, + ); + + return sortedTransactions.reduce((total, trans) => { + return total - amountToInteger(trans.transactionAmount.amount); + }, amountToInteger(currentBalance.balanceAmount.amount)); + }, +}; diff --git a/packages/sync-server/src/app-gocardless/banks/norwegian-xx-norwnok1.js b/packages/sync-server/src/app-gocardless/banks/norwegian-xx-norwnok1.js new file mode 100644 index 00000000000..6e8404e0424 --- /dev/null +++ b/packages/sync-server/src/app-gocardless/banks/norwegian-xx-norwnok1.js @@ -0,0 +1,97 @@ +import Fallback from './integration-bank.js'; + +import { printIban, amountToInteger } from '../utils.js'; +import { formatPayeeName } from '../../util/payee-name.js'; + +/** @type {import('./bank.interface.js').IBank} */ +export default { + ...Fallback, + + institutionIds: [ + 'NORWEGIAN_NO_NORWNOK1', + 'NORWEGIAN_SE_NORWNOK1', + 'NORWEGIAN_DE_NORWNOK1', + 'NORWEGIAN_DK_NORWNOK1', + 'NORWEGIAN_ES_NORWNOK1', + 'NORWEGIAN_FI_NORWNOK1', + ], + + accessValidForDays: 180, + + normalizeAccount(account) { + return { + account_id: account.id, + institution: account.institution, + mask: account.iban.slice(-4), + iban: account.iban, + name: [account.name, printIban(account)].join(' '), + official_name: account.product, + type: 'checking', + }; + }, + + normalizeTransaction(transaction, booked) { + if (booked) { + return { + ...transaction, + payeeName: formatPayeeName(transaction), + date: transaction.bookingDate, + }; + } + + /** + * For pending transactions there are two possibilities: + * + * - Either a `valueDate` was set, in which case it corresponds to when the + * transaction actually occurred, or + * - There is no date field, in which case we try to parse the correct date + * out of the `remittanceInformationStructured` field. + * + * If neither case succeeds then we return `null` causing this transaction + * to be filtered out for now, and hopefully we'll be able to import it + * once the bank has processed it further. + */ + if (transaction.valueDate !== undefined) { + return { + ...transaction, + payeeName: formatPayeeName(transaction), + date: transaction.valueDate, + }; + } + + if (transaction.remittanceInformationStructured) { + const remittanceInfoRegex = / (\d{4}-\d{2}-\d{2}) /; + const matches = + transaction.remittanceInformationStructured.match(remittanceInfoRegex); + if (matches) { + transaction.valueDate = matches[1]; + return { + ...transaction, + payeeName: formatPayeeName(transaction), + date: matches[1], + }; + } + } + + return null; + }, + + /** + * For NORWEGIAN_XX_NORWNOK1 we don't know what balance was + * after each transaction so we have to calculate it by getting + * current balance from the account and subtract all the transactions + * + * As a current balance we use `expected` balance type because it + * corresponds to the current running balance, whereas `interimAvailable` + * holds the remaining credit limit. + */ + calculateStartingBalance(sortedTransactions = [], balances = []) { + const currentBalance = balances.find( + (balance) => 'expected' === balance.balanceType, + ); + + return sortedTransactions.reduce((total, trans) => { + return total - amountToInteger(trans.transactionAmount.amount); + }, amountToInteger(currentBalance.balanceAmount.amount)); + }, +}; diff --git a/packages/sync-server/src/app-gocardless/banks/revolut_revolt21.js b/packages/sync-server/src/app-gocardless/banks/revolut_revolt21.js new file mode 100644 index 00000000000..9e660c46565 --- /dev/null +++ b/packages/sync-server/src/app-gocardless/banks/revolut_revolt21.js @@ -0,0 +1,60 @@ +import { formatPayeeName } from '../../util/payee-name.js'; +import * as d from 'date-fns'; +import Fallback from './integration-bank.js'; + +/** @type {import('./bank.interface.js').IBank} */ +export default { + ...Fallback, + + institutionIds: ['REVOLUT_REVOLT21'], + + accessValidForDays: 90, + + normalizeTransaction(transaction, _booked) { + if ( + transaction.remittanceInformationUnstructuredArray[0].startsWith( + 'Bizum payment from: ', + ) + ) { + const date = + transaction.bookingDate || + transaction.bookingDateTime || + transaction.valueDate || + transaction.valueDateTime; + + return { + ...transaction, + payeeName: + transaction.remittanceInformationUnstructuredArray[0].replace( + 'Bizum payment from: ', + '', + ), + remittanceInformationUnstructured: + transaction.remittanceInformationUnstructuredArray[1], + date: d.format(d.parseISO(date), 'yyyy-MM-dd'), + }; + } + + if ( + transaction.remittanceInformationUnstructuredArray[0].startsWith( + 'Bizum payment to: ', + ) + ) { + const date = + transaction.bookingDate || + transaction.bookingDateTime || + transaction.valueDate || + transaction.valueDateTime; + + return { + ...transaction, + payeeName: formatPayeeName(transaction), + remittanceInformationUnstructured: + transaction.remittanceInformationUnstructuredArray[1], + date: d.format(d.parseISO(date), 'yyyy-MM-dd'), + }; + } + + return Fallback.normalizeTransaction(transaction, _booked); + }, +}; diff --git a/packages/sync-server/src/app-gocardless/banks/sandboxfinance-sfin0000.js b/packages/sync-server/src/app-gocardless/banks/sandboxfinance-sfin0000.js new file mode 100644 index 00000000000..015886b038f --- /dev/null +++ b/packages/sync-server/src/app-gocardless/banks/sandboxfinance-sfin0000.js @@ -0,0 +1,59 @@ +import Fallback from './integration-bank.js'; + +import { printIban, amountToInteger } from '../utils.js'; +import { formatPayeeName } from '../../util/payee-name.js'; + +/** @type {import('./bank.interface.js').IBank} */ +export default { + ...Fallback, + + institutionIds: ['SANDBOXFINANCE_SFIN0000'], + + accessValidForDays: 90, + + normalizeAccount(account) { + return { + account_id: account.id, + institution: account.institution, + mask: account.iban.slice(-4), + iban: account.iban, + name: [account.name, printIban(account)].join(' '), + official_name: account.product, + type: 'checking', + }; + }, + + /** + * Following the GoCardless documentation[0] we should prefer `bookingDate` + * here, though some of their bank integrations uses the date field + * differently from what's described in their documentation and so it's + * sometimes necessary to use `valueDate` instead. + * + * [0]: https://nordigen.zendesk.com/hc/en-gb/articles/7899367372829-valueDate-and-bookingDate-for-transactions + */ + normalizeTransaction(transaction, _booked) { + return { + ...transaction, + payeeName: formatPayeeName(transaction), + date: transaction.bookingDate || transaction.valueDate, + }; + }, + + /** + * For SANDBOXFINANCE_SFIN0000 we don't know what balance was + * after each transaction so we have to calculate it by getting + * current balance from the account and subtract all the transactions + * + * As a current balance we use `interimBooked` balance type because + * it includes transaction placed during current day + */ + calculateStartingBalance(sortedTransactions = [], balances = []) { + const currentBalance = balances.find( + (balance) => 'interimAvailable' === balance.balanceType, + ); + + return sortedTransactions.reduce((total, trans) => { + return total - amountToInteger(trans.transactionAmount.amount); + }, amountToInteger(currentBalance.balanceAmount.amount)); + }, +}; diff --git a/packages/sync-server/src/app-gocardless/banks/seb-kort-bank-ab.js b/packages/sync-server/src/app-gocardless/banks/seb-kort-bank-ab.js new file mode 100644 index 00000000000..bdb606759df --- /dev/null +++ b/packages/sync-server/src/app-gocardless/banks/seb-kort-bank-ab.js @@ -0,0 +1,71 @@ +import Fallback from './integration-bank.js'; + +import { printIban, amountToInteger } from '../utils.js'; +import { formatPayeeName } from '../../util/payee-name.js'; + +/** @type {import('./bank.interface.js').IBank} */ +export default { + ...Fallback, + + institutionIds: [ + 'SEB_KORT_AB_NO_SKHSFI21', + 'SEB_KORT_AB_SE_SKHSFI21', + 'SEB_CARD_ESSESESS', + ], + + accessValidForDays: 180, + + normalizeAccount(account) { + return { + account_id: account.id, + institution: account.institution, + mask: account.iban.slice(-4), + iban: account.iban, + name: [account.name, printIban(account)].join(' '), + official_name: account.product, + type: 'checking', + }; + }, + + /** + * Sign of transaction amount needs to be flipped for SEB credit cards + */ + normalizeTransaction(transaction, _booked) { + // Creditor name is stored in additionInformation for SEB + transaction.creditorName = transaction.additionalInformation; + transaction.transactionAmount = { + // Flip transaction amount sign + amount: (-parseFloat(transaction.transactionAmount.amount)).toString(), + currency: transaction.transactionAmount.currency, + }; + + return { + ...transaction, + payeeName: formatPayeeName(transaction), + date: transaction.valueDate, + }; + }, + + /** + * For SEB_KORT_AB_NO_SKHSFI21 and SEB_KORT_AB_SE_SKHSFI21 we don't know what balance was + * after each transaction so we have to calculate it by getting + * current balance from the account and subtract all the transactions + * + * As a current balance we use `expected` and `nonInvoiced` balance types because it + * corresponds to the current running balance, whereas `interimAvailable` + * holds the remaining credit limit. + */ + calculateStartingBalance(sortedTransactions = [], balances = []) { + const currentBalance = balances.find( + (balance) => 'expected' === balance.balanceType, + ); + + const nonInvoiced = balances.find( + (balance) => 'nonInvoiced' === balance.balanceType, + ); + + return sortedTransactions.reduce((total, trans) => { + return total - amountToInteger(trans.transactionAmount.amount); + }, -amountToInteger(currentBalance.balanceAmount.amount) + amountToInteger(nonInvoiced.balanceAmount.amount)); + }, +}; diff --git a/packages/sync-server/src/app-gocardless/banks/seb-privat.js b/packages/sync-server/src/app-gocardless/banks/seb-privat.js new file mode 100644 index 00000000000..0ff079ebf80 --- /dev/null +++ b/packages/sync-server/src/app-gocardless/banks/seb-privat.js @@ -0,0 +1,47 @@ +import Fallback from './integration-bank.js'; + +import * as d from 'date-fns'; +import { amountToInteger } from '../utils.js'; +import { formatPayeeName } from '../../util/payee-name.js'; + +/** @type {import('./bank.interface.js').IBank} */ +export default { + ...Fallback, + + institutionIds: ['SEB_ESSESESS_PRIVATE'], + + accessValidForDays: 180, + + normalizeTransaction(transaction, _booked) { + const date = + transaction.bookingDate || + transaction.bookingDateTime || + transaction.valueDate || + transaction.valueDateTime; + // If we couldn't find a valid date field we filter out this transaction + // and hope that we will import it again once the bank has processed the + // transaction further. + if (!date) { + return null; + } + + // Creditor name is stored in additionInformation for SEB + transaction.creditorName = transaction.additionalInformation; + + return { + ...transaction, + payeeName: formatPayeeName(transaction), + date: d.format(d.parseISO(date), 'yyyy-MM-dd'), + }; + }, + + calculateStartingBalance(sortedTransactions = [], balances = []) { + const currentBalance = balances.find( + (balance) => 'interimBooked' === balance.balanceType, + ); + + return sortedTransactions.reduce((total, trans) => { + return total - amountToInteger(trans.transactionAmount.amount); + }, amountToInteger(currentBalance.balanceAmount.amount)); + }, +}; diff --git a/packages/sync-server/src/app-gocardless/banks/sparnord-spnodk22.js b/packages/sync-server/src/app-gocardless/banks/sparnord-spnodk22.js new file mode 100644 index 00000000000..37980fafbb4 --- /dev/null +++ b/packages/sync-server/src/app-gocardless/banks/sparnord-spnodk22.js @@ -0,0 +1,30 @@ +import Fallback from './integration-bank.js'; + +import { formatPayeeName } from '../../util/payee-name.js'; + +/** @type {import('./bank.interface.js').IBank} */ +export default { + ...Fallback, + + institutionIds: [ + 'SPARNORD_SPNODK22', + 'LAGERNES_BANK_LAPNDKK1', + 'ANDELSKASSEN_FALLESKASSEN_FAELDKK1', + ], + + accessValidForDays: 180, + + /** + * Banks on the BEC backend only give information regarding the transaction in additionalInformation + */ + normalizeTransaction(transaction, _booked) { + transaction.remittanceInformationUnstructured = + transaction.additionalInformation; + + return { + ...transaction, + payeeName: formatPayeeName(transaction), + date: transaction.bookingDate, + }; + }, +}; diff --git a/packages/sync-server/src/app-gocardless/banks/spk-karlsruhe-karsde66.js b/packages/sync-server/src/app-gocardless/banks/spk-karlsruhe-karsde66.js new file mode 100644 index 00000000000..317d63ec0e2 --- /dev/null +++ b/packages/sync-server/src/app-gocardless/banks/spk-karlsruhe-karsde66.js @@ -0,0 +1,98 @@ +import Fallback from './integration-bank.js'; + +import { printIban, amountToInteger } from '../utils.js'; +import { formatPayeeName } from '../../util/payee-name.js'; + +/** @type {import('./bank.interface.js').IBank} */ +export default { + ...Fallback, + + institutionIds: ['SPK_KARLSRUHE_KARSDE66XXX'], + + accessValidForDays: 90, + + normalizeAccount(account) { + return { + account_id: account.id, + institution: account.institution, + mask: account.iban.slice(-4), + iban: account.iban, + name: [account.name, printIban(account)].join(' '), + official_name: account.product, + type: 'checking', + }; + }, + + /** + * Following the GoCardless documentation[0] we should prefer `bookingDate` + * here, though some of their bank integrations uses the date field + * differently from what's described in their documentation and so it's + * sometimes necessary to use `valueDate` instead. + * + * [0]: https://nordigen.zendesk.com/hc/en-gb/articles/7899367372829-valueDate-and-bookingDate-for-transactions + */ + normalizeTransaction(transaction, _booked) { + const date = + transaction.bookingDate || + transaction.bookingDateTime || + transaction.valueDate || + transaction.valueDateTime; + + // If we couldn't find a valid date field we filter out this transaction + // and hope that we will import it again once the bank has processed the + // transaction further. + if (!date) { + return null; + } + + let remittanceInformationUnstructured; + + if (transaction.remittanceInformationUnstructured) { + remittanceInformationUnstructured = + transaction.remittanceInformationUnstructured; + } else if (transaction.remittanceInformationStructured) { + remittanceInformationUnstructured = + transaction.remittanceInformationStructured; + } else if (transaction.remittanceInformationStructuredArray?.length > 0) { + remittanceInformationUnstructured = + transaction.remittanceInformationStructuredArray?.join(' '); + } + + if (transaction.additionalInformation) + remittanceInformationUnstructured += + ' ' + transaction.additionalInformation; + + const usefulCreditorName = + transaction.ultimateCreditor || + transaction.creditorName || + transaction.debtorName; + + transaction.creditorName = usefulCreditorName; + transaction.remittanceInformationUnstructured = + remittanceInformationUnstructured; + + return { + ...transaction, + payeeName: formatPayeeName(transaction), + date: transaction.bookingDate || transaction.valueDate, + }; + }, + + /** + * For SANDBOXFINANCE_SFIN0000 we don't know what balance was + * after each transaction so we have to calculate it by getting + * current balance from the account and subtract all the transactions + * + * As a current balance we use `interimBooked` balance type because + * it includes transaction placed during current day + */ + calculateStartingBalance(sortedTransactions = [], balances = []) { + const currentBalance = balances.find( + (balance) => 'interimAvailable' === balance.balanceType, + ); + + return sortedTransactions.reduce((total, trans) => { + return total - amountToInteger(trans.transactionAmount.amount); + }, amountToInteger(currentBalance.balanceAmount.amount)); + }, +}; diff --git a/packages/sync-server/src/app-gocardless/banks/spk-marburg-biedenkopf-heladef1mar.js b/packages/sync-server/src/app-gocardless/banks/spk-marburg-biedenkopf-heladef1mar.js new file mode 100644 index 00000000000..70615bb76a9 --- /dev/null +++ b/packages/sync-server/src/app-gocardless/banks/spk-marburg-biedenkopf-heladef1mar.js @@ -0,0 +1,65 @@ +import Fallback from './integration-bank.js'; + +import d from 'date-fns'; +import { printIban } from '../utils.js'; +import { formatPayeeName } from '../../util/payee-name.js'; + +/** @type {import('./bank.interface.js').IBank} */ +export default { + ...Fallback, + + institutionIds: ['SPK_MARBURG_BIEDENKOPF_HELADEF1MAR'], + + accessValidForDays: 180, + + normalizeAccount(account) { + return { + account_id: account.id, + institution: account.institution, + mask: (account?.iban || '0000').slice(-4), + iban: account?.iban || null, + name: [account.product, printIban(account), account.currency] + .filter(Boolean) + .join(' '), + official_name: account.product, + type: 'checking', + }; + }, + + normalizeTransaction(transaction, _booked) { + const date = + transaction.bookingDate || + transaction.bookingDateTime || + transaction.valueDate || + transaction.valueDateTime; + + // If we couldn't find a valid date field we filter out this transaction + // and hope that we will import it again once the bank has processed the + // transaction further. + if (!date) { + return null; + } + + let remittanceInformationUnstructured; + + if (transaction.remittanceInformationUnstructured) { + remittanceInformationUnstructured = + transaction.remittanceInformationUnstructured; + } else if (transaction.remittanceInformationStructured) { + remittanceInformationUnstructured = + transaction.remittanceInformationStructured; + } else if (transaction.remittanceInformationStructuredArray?.length > 0) { + remittanceInformationUnstructured = + transaction.remittanceInformationStructuredArray?.join(' '); + } + + transaction.remittanceInformationUnstructured = + remittanceInformationUnstructured; + + return { + ...transaction, + payeeName: formatPayeeName(transaction), + date: d.format(d.parseISO(date), 'yyyy-MM-dd'), + }; + }, +}; diff --git a/packages/sync-server/src/app-gocardless/banks/spk-worms-alzey-ried-malade51wor.js b/packages/sync-server/src/app-gocardless/banks/spk-worms-alzey-ried-malade51wor.js new file mode 100644 index 00000000000..fb45cee0d73 --- /dev/null +++ b/packages/sync-server/src/app-gocardless/banks/spk-worms-alzey-ried-malade51wor.js @@ -0,0 +1,29 @@ +import Fallback from './integration-bank.js'; + +import { formatPayeeName } from '../../util/payee-name.js'; + +/** @type {import('./bank.interface.js').IBank} */ +export default { + ...Fallback, + + institutionIds: ['SPK_WORMS_ALZEY_RIED_MALADE51WOR'], + + accessValidForDays: 90, + + normalizeTransaction(transaction, _booked) { + const date = transaction.bookingDate || transaction.valueDate; + if (!date) { + return null; + } + + transaction.remittanceInformationUnstructured = + transaction.remittanceInformationUnstructured ?? + transaction.remittanceInformationStructured ?? + transaction.remittanceInformationStructuredArray?.join(' '); + return { + ...transaction, + payeeName: formatPayeeName(transaction), + date: transaction.bookingDate || transaction.valueDate, + }; + }, +}; diff --git a/packages/sync-server/src/app-gocardless/banks/tests/FORTUNEO_FTNOFRP1XXX.spec.js b/packages/sync-server/src/app-gocardless/banks/tests/FORTUNEO_FTNOFRP1XXX.spec.js new file mode 100644 index 00000000000..5188699eac3 --- /dev/null +++ b/packages/sync-server/src/app-gocardless/banks/tests/FORTUNEO_FTNOFRP1XXX.spec.js @@ -0,0 +1,210 @@ +import Fortuneo from '../FORTUNEO_FTNOFRP1XXX.js'; + +describe('Fortuneo', () => { + describe('#normalizeTransaction', () => { + const transactionsRaw = [ + { + bookingDate: '2024-07-05', + valueDate: '2024-07-05', + transactionAmount: { + amount: '-12.0', + currency: 'EUR', + }, + remittanceInformationUnstructuredArray: ['PRLV ONG'], + internalTransactionId: '674323725470140d5caaf7b85a135817', + date: '2024-07-05', + }, + { + bookingDate: '2024-07-04', + valueDate: '2024-07-04', + transactionAmount: { + amount: '-7.72', + currency: 'EUR', + }, + remittanceInformationUnstructuredArray: [ + 'PRLV PRIXTEL SCOR/53766825A', + ], + internalTransactionId: 'e8365f68077f2be249f8dfa9183296e4', + date: '2024-07-04', + }, + { + bookingDate: '2024-07-04', + valueDate: '2024-07-04', + transactionAmount: { + amount: '-500.0', + currency: 'EUR', + }, + remittanceInformationUnstructuredArray: ['VIR XXXYYYYZZZ'], + internalTransactionId: '0c12be495b71a63d14e46c43bfcb12f6', + date: '2024-07-04', + }, + { + bookingDate: '2024-07-04', + valueDate: '2024-07-04', + transactionAmount: { + amount: '-10.49', + currency: 'EUR', + }, + remittanceInformationUnstructuredArray: [ + 'CARTE 04/07 Google Payment I Dublin', + ], + internalTransactionId: 'b09df9be4711cb06bdd2a53aef5423cc', + date: '2024-07-04', + }, + { + bookingDate: '2024-07-04', + valueDate: '2024-07-04', + transactionAmount: { + amount: '-6.38', + currency: 'EUR', + }, + remittanceInformationUnstructuredArray: ['CARTE 03/07 SPORT MARKET'], + internalTransactionId: '67552cc7782c742f1df8297e614470ea', + date: '2024-07-04', + }, + { + bookingDate: '2024-07-04', + valueDate: '2024-07-04', + transactionAmount: { + amount: '26.52', + currency: 'EUR', + }, + remittanceInformationUnstructuredArray: [ + 'ANN CARTE WEEZEVENT SOMEPLACE', + ], + internalTransactionId: 'c0bed1b61806bd45fd07732e5dfb1f11', + date: '2024-07-04', + }, + { + bookingDate: '2024-07-03', + valueDate: '2024-07-03', + transactionAmount: { + amount: '-104.9', + currency: 'EUR', + }, + remittanceInformationUnstructuredArray: [ + "CARTE 02/07 HPY*L'APPAC - Sport JANDA", + ], + internalTransactionId: '7716b23b56cda848efd788a0d8c79d12', + date: '2024-07-03', + }, + { + bookingDate: '2024-07-03', + valueDate: '2024-07-02', + transactionAmount: { + amount: '-22.95', + currency: 'EUR', + }, + remittanceInformationUnstructuredArray: [ + 'VIR INST Leclerc XXXX Leclerc XXXX 44321IXCRT211141232', + ], + internalTransactionId: 'e75304593c9557f20014904f90eb23a2', + date: '2024-07-03', + }, + { + bookingDate: '2024-07-02', + valueDate: '2024-07-02', + transactionAmount: { + amount: '-8.9', + currency: 'EUR', + }, + remittanceInformationUnstructuredArray: ['CARTE 01/07 CHIK CHAK'], + internalTransactionId: 'e9811e50c8d7453c459f4e42453cf07c', + date: '2024-07-02', + }, + { + bookingDate: '2024-07-02', + valueDate: '2024-07-02', + transactionAmount: { + amount: '-8.0', + currency: 'EUR', + }, + remittanceInformationUnstructuredArray: [ + 'CARTE 01/07 SERVICE 1228 GENEV 8,00 EUR', + ], + internalTransactionId: '354a49232bd05de583a3d2ab834e20cd', + date: '2024-07-02', + }, + ]; + + it('sets debtor and creditor name according to amount', () => { + const creditorTransaction = transactionsRaw[0]; + const debtorTransaction = transactionsRaw[5]; + + const normalizedCreditorTransaction = Fortuneo.normalizeTransaction( + creditorTransaction, + true, + ); + const normalizedDebtorTransaction = Fortuneo.normalizeTransaction( + debtorTransaction, + true, + ); + + expect(normalizedCreditorTransaction.creditorName).toBeDefined(); + expect(normalizedCreditorTransaction.debtorName).toBeNull(); + expect( + parseFloat(normalizedCreditorTransaction.transactionAmount.amount), + ).toBeLessThan(0); + + expect(normalizedDebtorTransaction.debtorName).toBeDefined(); + expect(normalizedDebtorTransaction.creditorName).toBeNull(); + expect( + parseFloat(normalizedDebtorTransaction.transactionAmount.amount), + ).toBeGreaterThan(0); + }); + + it('extracts payee name from remittanceInformationUnstructured', () => { + const transaction0 = transactionsRaw[0]; + const normalizedTransaction = Fortuneo.normalizeTransaction( + transaction0, + true, + ); + + expect(normalizedTransaction.creditorName).toBe('ONG'); + + const transaction2 = transactionsRaw[2]; + const normalizedTransaction2 = Fortuneo.normalizeTransaction( + transaction2, + true, + ); + + expect(normalizedTransaction2.creditorName).toBe('XXXYYYYZZZ'); + + const transaction3 = transactionsRaw[3]; + const normalizedTransaction3 = Fortuneo.normalizeTransaction( + transaction3, + true, + ); + + expect(normalizedTransaction3.creditorName).toBe( + 'Google Payment I Dublin', + ); + + const transaction4 = transactionsRaw[4]; + const normalizedTransaction4 = Fortuneo.normalizeTransaction( + transaction4, + true, + ); + + expect(normalizedTransaction4.creditorName).toBe('SPORT MARKET'); + + const transaction5 = transactionsRaw[5]; + const normalizedTransaction5 = Fortuneo.normalizeTransaction( + transaction5, + true, + ); + + expect(normalizedTransaction5.debtorName).toBe('WEEZEVENT SOMEPLACE'); + + const transaction7 = transactionsRaw[7]; + const normalizedTransaction7 = Fortuneo.normalizeTransaction( + transaction7, + true, + ); + + expect(normalizedTransaction7.creditorName).toBe( + 'Leclerc XXXX Leclerc XXXX 44321IXCRT211141232', + ); + }); + }); +}); diff --git a/packages/sync-server/src/app-gocardless/banks/tests/abanca-caglesmm.spec.js b/packages/sync-server/src/app-gocardless/banks/tests/abanca-caglesmm.spec.js new file mode 100644 index 00000000000..c57961375fb --- /dev/null +++ b/packages/sync-server/src/app-gocardless/banks/tests/abanca-caglesmm.spec.js @@ -0,0 +1,25 @@ +import Abanca from '../abanca-caglesmm.js'; +import { mockTransactionAmount } from '../../services/tests/fixtures.js'; + +describe('Abanca', () => { + describe('#normalizeTransaction', () => { + it('returns the creditorName and debtorName as remittanceInformationStructured', () => { + const transaction = { + transactionId: 'non-unique-id', + internalTransactionId: 'D202301180000003', + transactionAmount: mockTransactionAmount, + remittanceInformationStructured: 'some-creditor-name', + }; + const normalizedTransaction = Abanca.normalizeTransaction( + transaction, + true, + ); + expect(normalizedTransaction.creditorName).toEqual( + transaction.remittanceInformationStructured, + ); + expect(normalizedTransaction.debtorName).toEqual( + transaction.remittanceInformationStructured, + ); + }); + }); +}); diff --git a/packages/sync-server/src/app-gocardless/banks/tests/bancsabadell-bsabesbbb.spec.js b/packages/sync-server/src/app-gocardless/banks/tests/bancsabadell-bsabesbbb.spec.js new file mode 100644 index 00000000000..61071084ebc --- /dev/null +++ b/packages/sync-server/src/app-gocardless/banks/tests/bancsabadell-bsabesbbb.spec.js @@ -0,0 +1,57 @@ +import Sabadell from '../bancsabadell-bsabesbbb.js'; + +describe('BancSabadell', () => { + describe('#normalizeTransaction', () => { + describe('returns the creditorName and debtorName from remittanceInformationUnstructuredArray', () => { + it('debtor role - amount < 0', () => { + const transaction = { + transactionAmount: { amount: '-100', currency: 'EUR' }, + remittanceInformationUnstructuredArray: ['some-creditor-name'], + internalTransactionId: 'd7dca139cf31d9', + transactionId: '04704109322', + bookingDate: '2022-05-01', + }; + const normalizedTransaction = Sabadell.normalizeTransaction( + transaction, + true, + ); + expect(normalizedTransaction.creditorName).toEqual( + 'some-creditor-name', + ); + expect(normalizedTransaction.debtorName).toEqual(null); + }); + + it('creditor role - amount > 0', () => { + const transaction = { + transactionAmount: { amount: '100', currency: 'EUR' }, + remittanceInformationUnstructuredArray: ['some-debtor-name'], + internalTransactionId: 'd7dca139cf31d9', + transactionId: '04704109322', + bookingDate: '2022-05-01', + }; + const normalizedTransaction = Sabadell.normalizeTransaction( + transaction, + true, + ); + expect(normalizedTransaction.debtorName).toEqual('some-debtor-name'); + expect(normalizedTransaction.creditorName).toEqual(null); + }); + }); + + it('extract date', () => { + const transaction = { + transactionAmount: { amount: '-100', currency: 'EUR' }, + remittanceInformationUnstructuredArray: ['some-creditor-name'], + internalTransactionId: 'd7dca139cf31d9', + transactionId: '04704109322', + bookingDate: '2024-10-02', + valueDate: '2024-10-05', + }; + const normalizedTransaction = Sabadell.normalizeTransaction( + transaction, + true, + ); + expect(normalizedTransaction.date).toEqual('2024-10-02'); + }); + }); +}); diff --git a/packages/sync-server/src/app-gocardless/banks/tests/belfius_gkccbebb.spec.js b/packages/sync-server/src/app-gocardless/banks/tests/belfius_gkccbebb.spec.js new file mode 100644 index 00000000000..25291a6a897 --- /dev/null +++ b/packages/sync-server/src/app-gocardless/banks/tests/belfius_gkccbebb.spec.js @@ -0,0 +1,21 @@ +import Belfius from '../belfius_gkccbebb.js'; +import { mockTransactionAmount } from '../../services/tests/fixtures.js'; + +describe('Belfius', () => { + describe('#normalizeTransaction', () => { + it('returns the internalTransactionId as transactionId', () => { + const transaction = { + transactionId: 'non-unique-id', + internalTransactionId: 'D202301180000003', + transactionAmount: mockTransactionAmount, + }; + const normalizedTransaction = Belfius.normalizeTransaction( + transaction, + true, + ); + expect(normalizedTransaction.transactionId).toEqual( + transaction.internalTransactionId, + ); + }); + }); +}); diff --git a/packages/sync-server/src/app-gocardless/banks/tests/cbc_cregbebb.spec.js b/packages/sync-server/src/app-gocardless/banks/tests/cbc_cregbebb.spec.js new file mode 100644 index 00000000000..4114ac4944f --- /dev/null +++ b/packages/sync-server/src/app-gocardless/banks/tests/cbc_cregbebb.spec.js @@ -0,0 +1,32 @@ +import CBCcregbebb from '../cbc_cregbebb.js'; + +describe('cbc_cregbebb', () => { + describe('#normalizeTransaction', () => { + it('returns the remittanceInformationUnstructured as payeeName when the amount is negative', () => { + const transaction = { + remittanceInformationUnstructured: + 'ONKART FR Viry Paiement Maestro par Carte de débit CBC 05-09-2024 à 15.43 heures 6703 19XX XXXX X201 5 JOHN DOE', + transactionAmount: { amount: '-45.00', currency: 'EUR' }, + }; + const normalizedTransaction = CBCcregbebb.normalizeTransaction( + transaction, + true, + ); + expect(normalizedTransaction.payeeName).toEqual('ONKART FR Viry'); + }); + + it('returns the debtorName as payeeName when the amount is positive', () => { + const transaction = { + debtorName: 'ONKART FR Viry', + remittanceInformationUnstructured: + 'ONKART FR Viry Paiement Maestro par Carte de débit CBC 05-09-2024 à 15.43 heures 6703 19XX XXXX X201 5 JOHN DOE', + transactionAmount: { amount: '10.99', currency: 'EUR' }, + }; + const normalizedTransaction = CBCcregbebb.normalizeTransaction( + transaction, + true, + ); + expect(normalizedTransaction.payeeName).toEqual('ONKART FR Viry'); + }); + }); +}); diff --git a/packages/sync-server/src/app-gocardless/banks/tests/easybank-bawaatww.spec.js b/packages/sync-server/src/app-gocardless/banks/tests/easybank-bawaatww.spec.js new file mode 100644 index 00000000000..09363b7a455 --- /dev/null +++ b/packages/sync-server/src/app-gocardless/banks/tests/easybank-bawaatww.spec.js @@ -0,0 +1,54 @@ +import EasybankBawaatww from '../easybank-bawaatww.js'; +import { mockTransactionAmount } from '../../services/tests/fixtures.js'; + +describe('easybank', () => { + describe('#normalizeTransaction', () => { + it('returns the expected payeeName from a transaction with a set creditorName', () => { + const transaction = { + creditorName: 'Some Payee Name', + transactionAmount: mockTransactionAmount, + bookingDate: '2024-01-01', + creditorAccount: 'AT611904300234573201', + }; + + const normalizedTransaction = EasybankBawaatww.normalizeTransaction( + transaction, + true, + ); + + expect(normalizedTransaction.payeeName).toEqual('Some Payee Name'); + }); + + it('returns the expected payeeName from a transaction with payee name inside structuredInformation', () => { + const transaction = { + payeeName: '', + transactionAmount: mockTransactionAmount, + remittanceInformationStructured: + 'Bezahlung Karte MC/000001234POS 1234 K001 12.12. 23:59SOME PAYEE NAME\\\\LOCATION\\1', + bookingDate: '2023-12-31', + }; + const normalizedTransaction = EasybankBawaatww.normalizeTransaction( + transaction, + true, + ); + expect(normalizedTransaction.payeeName).toEqual('Some Payee Name'); + }); + + it('returns the full structured information as payeeName from a transaction with no payee name', () => { + const transaction = { + payeeName: '', + transactionAmount: mockTransactionAmount, + remittanceInformationStructured: + 'Auszahlung Karte MC/000001234AUTOMAT 00012345 K001 31.12. 23:59', + bookingDate: '2023-12-31', + }; + const normalizedTransaction = EasybankBawaatww.normalizeTransaction( + transaction, + true, + ); + expect(normalizedTransaction.payeeName).toEqual( + 'Auszahlung Karte MC/000001234AUTOMAT 00012345 K001 31.12. 23:59', + ); + }); + }); +}); diff --git a/packages/sync-server/src/app-gocardless/banks/tests/ing-ingddeff.spec.js b/packages/sync-server/src/app-gocardless/banks/tests/ing-ingddeff.spec.js new file mode 100644 index 00000000000..a3d027c2f0c --- /dev/null +++ b/packages/sync-server/src/app-gocardless/banks/tests/ing-ingddeff.spec.js @@ -0,0 +1,300 @@ +import IngIngddeff from '../ing-ingddeff.js'; + +describe('IngIngddeff', () => { + describe('#normalizeAccount', () => { + /** @type {import('../../gocardless.types.js').DetailedAccountWithInstitution} */ + const accountRaw = { + resourceId: 'e896eec6-6096-4efc-a941-756bd9d74765', + iban: 'DE02500105170137075030', + currency: 'EUR', + ownerName: 'Jane Doe', + product: 'Girokonto', + id: 'a787ba27-02ee-4fd6-be86-73831adc5498', + created: '2023-12-29T14:17:11.630352Z', + last_accessed: '2023-12-29T14:19:42.709478Z', + institution_id: 'ING_INGDDEFF', + status: 'READY', + owner_name: 'Jane Doe', + institution: { + id: 'ING_INGDDEFF', + name: 'ING', + bic: 'INGDDEFFXXX', + transaction_total_days: '390', + countries: ['DE'], + logo: 'https://storage.googleapis.com/gc-prd-institution_icons-production/DE/PNG/ing.png', + supported_payments: { + 'single-payment': ['SCT'], + }, + supported_features: [ + 'account_selection', + 'business_accounts', + 'corporate_accounts', + 'payments', + 'pending_transactions', + 'private_accounts', + ], + /*identification_codes: [],*/ + }, + }; + + it('returns normalized account data returned to Frontend', () => { + expect(IngIngddeff.normalizeAccount(accountRaw)).toEqual({ + account_id: 'a787ba27-02ee-4fd6-be86-73831adc5498', + iban: 'DE02500105170137075030', + institution: { + bic: 'INGDDEFFXXX', + countries: ['DE'], + id: 'ING_INGDDEFF', + logo: 'https://storage.googleapis.com/gc-prd-institution_icons-production/DE/PNG/ing.png', + name: 'ING', + supported_features: [ + 'account_selection', + 'business_accounts', + 'corporate_accounts', + 'payments', + 'pending_transactions', + 'private_accounts', + ], + supported_payments: { + 'single-payment': ['SCT'], + }, + transaction_total_days: '390', + }, + mask: '5030', + name: 'Girokonto (XXX 5030)', + official_name: 'Girokonto', + type: 'checking', + }); + }); + }); + + const transactionsRaw = [ + { + transactionId: '000010348081381', + endToEndId: 'NOTPROVIDED', + bookingDate: '2023-12-29', + valueDate: '2023-12-29', + transactionAmount: { + amount: '-4.00', + currency: 'EUR', + }, + creditorName: 'VISA XXXXXXXXXXXXXXXXXXXX ', + remittanceInformationUnstructured: + 'mandatereference:,creditorid:,remittanceinformation:NR XXXX 63053 51590342815 KAUFUMSATZ 24.90 2311825 ARN044873748454374484719431 Google Pay ', + proprietaryBankTransactionCode: 'Lastschrifteinzug', + internalTransactionId: '085179a2e5fa34b0ff71b3f2c9f4876f', + date: '2023-12-29', + }, + { + transactionId: '000010348081380', + endToEndId: 'NOTPROVIDED', + bookingDate: '2023-12-29', + valueDate: '2023-12-29', + transactionAmount: { + amount: '-2.00', + currency: 'EUR', + }, + creditorName: 'VISA XXXXXXXXXXXXXXXXXXXX ', + remittanceInformationUnstructured: + 'mandatereference:,creditorid:,remittanceinformation:NR XXXX 8987 90671935362 KAUFUMSATZ 94.81 929614 ARN54795476045598005130492 Google Pay ', + proprietaryBankTransactionCode: 'Lastschrifteinzug', + internalTransactionId: '0707bbe2de27e5aabfd5dc614c584951', + date: '2023-12-29', + }, + { + transactionId: '000010348081379', + endToEndId: 'NOTPROVIDED', + bookingDate: '2023-12-29', + valueDate: '2023-12-29', + transactionAmount: { + amount: '-6.00', + currency: 'EUR', + }, + creditorName: 'VISA XXXXXXXXXXXXXXXXXXXX ', + remittanceInformationUnstructured: + 'mandatereference:,creditorid:,remittanceinformation:NR XXXX 2206 17679024325 KAUFUMSATZ 55.25 819456 ARN08595270353806495555431 Google Pay ', + proprietaryBankTransactionCode: 'Lastschrifteinzug', + internalTransactionId: '4b15b590652c9ebdc3f974591b15b250', + date: '2023-12-29', + }, + { + transactionId: '000010348081378', + endToEndId: 'NOTPROVIDED', + bookingDate: '2023-12-29', + valueDate: '2023-12-29', + transactionAmount: { + amount: '-12.99', + currency: 'EUR', + }, + creditorName: 'VISA XXXXXXXXXXXXXXXXXXXX ', + remittanceInformationUnstructured: + 'mandatereference:,creditorid:,remittanceinformation:NR XXXX 9437 535-182-825 LU KAUFUMSATZ 43.79 665448 ARN86236748928277201384604 ', + proprietaryBankTransactionCode: 'Lastschrifteinzug', + internalTransactionId: 'f930f8c153f3e37fb9906e4b3a2b4552', + date: '2023-12-29', + }, + { + transactionId: '000010348081377', + endToEndId: 'NOTPROVIDED', + bookingDate: '2023-12-29', + valueDate: '2023-12-29', + transactionAmount: { + amount: '-9.00', + currency: 'EUR', + }, + creditorName: 'VISA XXXXXXXXXXXXXXXXXXXX ', + remittanceInformationUnstructured: + 'mandatereference:,creditorid:,remittanceinformation:NR XXXX 3582 98236826123 KAUFUMSATZ 88.90 477561 ARN64452564252952225664357 Google Pay ', + proprietaryBankTransactionCode: 'Lastschrifteinzug', + internalTransactionId: '1ce866282deb78cc4ff4cd108e11b8cc', + date: '2023-12-29', + }, + { + transactionId: '000010347374680', + endToEndId: '9212020-0900000070-2023121711315956', + bookingDate: '2023-12-29', + valueDate: '2023-12-29', + transactionAmount: { + amount: '2892.61', + currency: 'EUR', + }, + debtorName: 'XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX', + remittanceInformationUnstructured: + 'mandatereference:,creditorid:,remittanceinformation:F22685813 Gehalt 80/6586', + proprietaryBankTransactionCode: 'Gehalt/Rente', + internalTransactionId: 'e731d8eb47f1ae96ccc11e1fb8b76a60', + date: '2023-12-29', + }, + { + transactionId: '000010336959253', + endToEndId: 'NOTPROVIDED', + bookingDate: '2023-12-28', + valueDate: '2023-12-28', + transactionAmount: { + amount: '-85.80', + currency: 'EUR', + }, + creditorName: 'VISA XXXXXXXXXXXXXXXXXXXX ', + remittanceInformationUnstructured: + 'mandatereference:,creditorid:,remittanceinformation:NR XXXX 7082 FAUCOGNEY E FR KAUFUMSATZ 38.20 265113 ARN47998616225906149245029 ', + proprietaryBankTransactionCode: 'Lastschrifteinzug', + internalTransactionId: '2bbc054ae7ba299482a7849fded864f3', + date: '2023-12-28', + }, + { + transactionId: '000010350537843', + endToEndId: 'NOTPROVIDED', + bookingDate: '2023-12-29', + valueDate: '2023-12-27', + transactionAmount: { + amount: '2.79', + currency: 'EUR', + }, + debtorName: ' ', + remittanceInformationUnstructured: + 'mandatereference:,creditorid:,remittanceinformation: Zins/Dividende ISIN IE36B9RBWM04 VANG.FTSE', + proprietaryBankTransactionCode: 'Zins / Dividende WP', + internalTransactionId: '3bb7c58199d3fa5a44e85871d9001798', + date: '2023-12-29', + }, + { + transactionId: '000010341786083', + endToEndId: 'NOTPROVIDED', + bookingDate: '2023-12-28', + valueDate: '2023-12-27', + transactionAmount: { + amount: '79.80', + currency: 'EUR', + }, + debtorName: 'VISA XXXXXXXXXXXXXXXXXXXX ', + remittanceInformationUnstructured: + 'mandatereference:,creditorid:,remittanceinformation:NR XXXX 4619 GUTSCHRIFTSBELEG 03.91 134870 ', + proprietaryBankTransactionCode: 'Gutschrift', + internalTransactionId: '5570eefb7213e39153a6c7fb97d7dc6f', + date: '2023-12-28', + }, + { + transactionId: '000010328399902', + endToEndId: 'NOTPROVIDED', + bookingDate: '2023-12-27', + valueDate: '2023-12-27', + transactionAmount: { + amount: '-10.90', + currency: 'EUR', + }, + debtorName: 'VISA XXXXXXXXXXXXXXXXXXXX ', + remittanceInformationUnstructured: + 'mandatereference:,creditorid:,remittanceinformation:NR XXXX 3465 XXXXXXXXX KAUFUMSATZ 90.40 505416 ARN63639757770303957985044 Google Pay ', + proprietaryBankTransactionCode: 'Lastschrifteinzug', + internalTransactionId: '1b1bf30b23afb56ba4d41b9c65cf0efa', + date: '2023-12-27', + }, + ]; + + describe('#sortTransactions', () => { + it('handles empty arrays', () => { + const transactions = []; + const sortedTransactions = IngIngddeff.sortTransactions(transactions); + expect(sortedTransactions).toEqual([]); + }); + + it('returns empty array for undefined input', () => { + const sortedTransactions = IngIngddeff.sortTransactions(undefined); + expect(sortedTransactions).toEqual([]); + }); + + it('returns sorted array for unsorted inputs', () => { + const normalizeTransactions = transactionsRaw.map((tx) => + IngIngddeff.normalizeTransaction(tx, true), + ); + const originalOrder = Array.from(normalizeTransactions); + const swap = (a, b) => { + const swap = normalizeTransactions[a]; + normalizeTransactions[a] = normalizeTransactions[b]; + normalizeTransactions[b] = swap; + }; + swap(1, 4); + swap(3, 6); + swap(0, 7); + const sortedTransactions = IngIngddeff.sortTransactions( + normalizeTransactions, + ); + expect(sortedTransactions).toEqual(originalOrder); + }); + }); + + describe('#countStartingBalance', () => { + /** @type {import('../../gocardless-node.types.js').Balance[]} */ + const balances = [ + { + balanceAmount: { amount: '3596.87', currency: 'EUR' }, + balanceType: 'interimBooked', + lastChangeDateTime: '2023-12-29T16:44:06.479Z', + }, + ]; + + it('should calculate the starting balance correctly', () => { + const normalizeTransactions = transactionsRaw.map((tx) => + IngIngddeff.normalizeTransaction(tx, true), + ); + const sortedTransactions = IngIngddeff.sortTransactions( + normalizeTransactions, + ); + + const startingBalance = IngIngddeff.calculateStartingBalance( + sortedTransactions, + balances, + ); + + expect(startingBalance).toEqual(75236); + }); + + it('returns the same balance amount when no transactions', () => { + const transactions = []; + + expect( + IngIngddeff.calculateStartingBalance(transactions, balances), + ).toEqual(359687); + }); + }); +}); diff --git a/packages/sync-server/src/app-gocardless/banks/tests/ing-pl-ingbplpw.spec.js b/packages/sync-server/src/app-gocardless/banks/tests/ing-pl-ingbplpw.spec.js new file mode 100644 index 00000000000..ac8699ef604 --- /dev/null +++ b/packages/sync-server/src/app-gocardless/banks/tests/ing-pl-ingbplpw.spec.js @@ -0,0 +1,200 @@ +import IngPlIngbplpw from '../ing-pl-ingbplpw.js'; +import { mockTransactionAmount } from '../../services/tests/fixtures.js'; + +describe('IngPlIngbplpw', () => { + describe('#normalizeAccount', () => { + /** @type {import('../../gocardless.types.js').DetailedAccountWithInstitution} */ + const accountRaw = { + resourceId: 'PL00000000000000000987654321', + iban: 'PL00000000000000000987654321', + currency: 'PLN', + ownerName: 'John Example', + product: 'Current Account for Individuals (Retail)', + bic: 'INGBPLPW', + ownerAddressUnstructured: [ + 'UL. EXAMPLE STREET 10 M.1', + '00-000 WARSZAWA', + ], + id: 'd3eccc94-9536-48d3-98be-813f79199ee3', + created: '2022-07-24T20:45:47.929582Z', + last_accessed: '2023-01-24T22:12:00.193558Z', + institution_id: 'ING_PL_INGBPLPW', + status: 'READY', + owner_name: '', + institution: { + id: 'ING_PL_INGBPLPW', + name: 'ING', + bic: 'INGBPLPW', + transaction_total_days: '365', + countries: ['PL'], + logo: 'https://cdn.nordigen.com/ais/ING_PL_INGBPLPW.png', + supported_payments: {}, + supported_features: [ + 'access_scopes', + 'business_accounts', + 'card_accounts', + 'corporate_accounts', + 'pending_transactions', + 'private_accounts', + ], + }, + }; + + it('returns normalized account data returned to Frontend', () => { + const normalizedAccount = IngPlIngbplpw.normalizeAccount(accountRaw); + expect(normalizedAccount).toMatchInlineSnapshot(` + { + "account_id": "d3eccc94-9536-48d3-98be-813f79199ee3", + "iban": "PL00000000000000000987654321", + "institution": { + "bic": "INGBPLPW", + "countries": [ + "PL", + ], + "id": "ING_PL_INGBPLPW", + "logo": "https://cdn.nordigen.com/ais/ING_PL_INGBPLPW.png", + "name": "ING", + "supported_features": [ + "access_scopes", + "business_accounts", + "card_accounts", + "corporate_accounts", + "pending_transactions", + "private_accounts", + ], + "supported_payments": {}, + "transaction_total_days": "365", + }, + "mask": "4321", + "name": "Current Account for Individuals (Retail) (XXX 4321)", + "official_name": "Current Account for Individuals (Retail)", + "type": "checking", + } + `); + }); + }); + + describe('#sortTransactions', () => { + it('sorts transactions by time and sequence from newest to oldest', () => { + const transactions = [ + { + transactionId: 'D202301180000003', + transactionAmount: mockTransactionAmount, + }, + { + transactionId: 'D202301180000004', + transactionAmount: mockTransactionAmount, + }, + { + transactionId: 'D202301230000001', + transactionAmount: mockTransactionAmount, + }, + { + transactionId: 'D202301180000002', + transactionAmount: mockTransactionAmount, + }, + { + transactionId: 'D202301200000001', + transactionAmount: mockTransactionAmount, + }, + ]; + const sortedTransactions = IngPlIngbplpw.sortTransactions(transactions); + expect(sortedTransactions).toEqual([ + { + transactionId: 'D202301230000001', + transactionAmount: mockTransactionAmount, + }, + { + transactionId: 'D202301200000001', + transactionAmount: mockTransactionAmount, + }, + { + transactionId: 'D202301180000004', + transactionAmount: mockTransactionAmount, + }, + { + transactionId: 'D202301180000003', + transactionAmount: mockTransactionAmount, + }, + { + transactionId: 'D202301180000002', + transactionAmount: mockTransactionAmount, + }, + ]); + }); + + it('handles empty arrays', () => { + const transactions = []; + const sortedTransactions = IngPlIngbplpw.sortTransactions(transactions); + expect(sortedTransactions).toEqual([]); + }); + + it('returns empty array for undefined input', () => { + const sortedTransactions = IngPlIngbplpw.sortTransactions(undefined); + expect(sortedTransactions).toEqual([]); + }); + }); + + describe('#countStartingBalance', () => { + it('should calculate the starting balance correctly', () => { + /** @type {import('../../gocardless-node.types.js').Transaction[]} */ + const sortedTransactions = [ + { + transactionAmount: { amount: '-100.00', currency: 'USD' }, + balanceAfterTransaction: { + balanceAmount: { amount: '400.00', currency: 'USD' }, + balanceType: 'interimBooked', + }, + }, + { + transactionAmount: { amount: '50.00', currency: 'USD' }, + balanceAfterTransaction: { + balanceAmount: { amount: '450.00', currency: 'USD' }, + balanceType: 'interimBooked', + }, + }, + { + transactionAmount: { amount: '-25.00', currency: 'USD' }, + balanceAfterTransaction: { + balanceAmount: { amount: '475.00', currency: 'USD' }, + balanceType: 'interimBooked', + }, + }, + ]; + + /** @type {import('../../gocardless-node.types.js').Balance[]} */ + const balances = [ + { + balanceType: 'interimBooked', + balanceAmount: { amount: '500.00', currency: 'USD' }, + }, + { + balanceType: 'closingBooked', + balanceAmount: { amount: '600.00', currency: 'USD' }, + }, + ]; + + const startingBalance = IngPlIngbplpw.calculateStartingBalance( + sortedTransactions, + balances, + ); + + expect(startingBalance).toEqual(50000); + }); + + it('returns the same balance amount when no transactions', () => { + const transactions = []; + + /** @type {import('../../gocardless-node.types.js').Balance[]} */ + const balances = [ + { + balanceType: 'interimBooked', + balanceAmount: { amount: '500.00', currency: 'USD' }, + }, + ]; + expect( + IngPlIngbplpw.calculateStartingBalance(transactions, balances), + ).toEqual(50000); + }); + }); +}); diff --git a/packages/sync-server/src/app-gocardless/banks/tests/integration-bank.spec.js b/packages/sync-server/src/app-gocardless/banks/tests/integration-bank.spec.js new file mode 100644 index 00000000000..7f1f235d389 --- /dev/null +++ b/packages/sync-server/src/app-gocardless/banks/tests/integration-bank.spec.js @@ -0,0 +1,157 @@ +import { jest } from '@jest/globals'; +import IntegrationBank from '../integration-bank.js'; +import { + mockExtendAccountsAboutInstitutions, + mockInstitution, +} from '../../services/tests/fixtures.js'; + +describe('IntegrationBank', () => { + let consoleSpy; + + beforeEach(() => { + consoleSpy = jest.spyOn(console, 'log'); + }); + + describe('normalizeAccount', () => { + const account = mockExtendAccountsAboutInstitutions[0]; + + it('should return a normalized account object', () => { + const normalizedAccount = IntegrationBank.normalizeAccount(account); + expect(normalizedAccount).toEqual({ + account_id: account.id, + institution: mockInstitution, + mask: '4321', + iban: account.iban, + name: 'account-example-one (XXX 4321) PLN', + official_name: 'integration-SANDBOXFINANCE_SFIN0000', + type: 'checking', + }); + }); + + it('should return a normalized account object with masked value "0000" when no iban property is provided', () => { + const normalizedAccount = IntegrationBank.normalizeAccount({ + ...account, + iban: undefined, + }); + expect(normalizedAccount).toEqual({ + account_id: account.id, + institution: mockInstitution, + mask: '0000', + iban: null, + name: 'account-example-one PLN', + official_name: 'integration-SANDBOXFINANCE_SFIN0000', + type: 'checking', + }); + }); + + it('normalizeAccount logs available account properties', () => { + IntegrationBank.normalizeAccount(account); + expect(consoleSpy).toHaveBeenCalledWith( + 'Available account properties for new institution integration', + { + account: JSON.stringify(account), + }, + ); + }); + }); + + describe('sortTransactions', () => { + const transactions = [ + { + date: '2022-01-01', + bookingDate: '2022-01-01', + transactionAmount: { amount: '100', currency: 'EUR' }, + }, + { + date: '2022-01-03', + bookingDate: '2022-01-03', + transactionAmount: { amount: '100', currency: 'EUR' }, + }, + { + date: '2022-01-02', + bookingDate: '2022-01-02', + transactionAmount: { amount: '100', currency: 'EUR' }, + }, + ]; + const sortedTransactions = [ + { + date: '2022-01-03', + bookingDate: '2022-01-03', + transactionAmount: { amount: '100', currency: 'EUR' }, + }, + { + date: '2022-01-02', + bookingDate: '2022-01-02', + transactionAmount: { amount: '100', currency: 'EUR' }, + }, + { + date: '2022-01-01', + bookingDate: '2022-01-01', + transactionAmount: { amount: '100', currency: 'EUR' }, + }, + ]; + + it('should return transactions sorted by bookingDate', () => { + const sortedTransactions = IntegrationBank.sortTransactions(transactions); + expect(sortedTransactions).toEqual(sortedTransactions); + }); + + it('sortTransactions logs available transactions properties', () => { + IntegrationBank.sortTransactions(transactions); + expect(consoleSpy).toHaveBeenCalledWith( + 'Available (first 10) transactions properties for new integration of institution in sortTransactions function', + { top10Transactions: JSON.stringify(sortedTransactions.slice(0, 10)) }, + ); + }); + }); + + describe('calculateStartingBalance', () => { + /** @type {import('../../gocardless-node.types.js').Transaction[]} */ + const transactions = [ + { + bookingDate: '2022-01-01', + transactionAmount: { amount: '100', currency: 'EUR' }, + }, + { + bookingDate: '2022-02-01', + transactionAmount: { amount: '100', currency: 'EUR' }, + }, + { + bookingDate: '2022-03-01', + transactionAmount: { amount: '100', currency: 'EUR' }, + }, + ]; + + /** @type {import('../../gocardless-node.types.js').Balance[]} */ + const balances = [ + { + balanceAmount: { amount: '1000.00', currency: 'EUR' }, + balanceType: 'interimBooked', + }, + ]; + + it('should return 0 when no transactions or balances are provided', () => { + const startingBalance = IntegrationBank.calculateStartingBalance([], []); + expect(startingBalance).toEqual(0); + }); + + it('should return 70000 when transactions and balances are provided', () => { + const startingBalance = IntegrationBank.calculateStartingBalance( + transactions, + balances, + ); + expect(startingBalance).toEqual(70000); + }); + + it('logs available transactions and balances properties', () => { + IntegrationBank.calculateStartingBalance(transactions, balances); + expect(consoleSpy).toHaveBeenCalledWith( + 'Available (first 10) transactions properties for new integration of institution in calculateStartingBalance function', + { + balances: JSON.stringify(balances), + top10SortedTransactions: JSON.stringify(transactions.slice(0, 10)), + }, + ); + }); + }); +}); diff --git a/packages/sync-server/src/app-gocardless/banks/tests/kbc_kredbebb.spec.js b/packages/sync-server/src/app-gocardless/banks/tests/kbc_kredbebb.spec.js new file mode 100644 index 00000000000..21e67dcd36a --- /dev/null +++ b/packages/sync-server/src/app-gocardless/banks/tests/kbc_kredbebb.spec.js @@ -0,0 +1,36 @@ +import KBCkredbebb from '../kbc_kredbebb.js'; + +describe('kbc_kredbebb', () => { + describe('#normalizeTransaction', () => { + it('returns the remittanceInformationUnstructured as payeeName when the amount is negative', () => { + const transaction = { + remittanceInformationUnstructured: + 'CARREFOUR ST GIL BE1060 BRUXELLES Betaling met Google Pay via Debit Mastercard 28-08-2024 om 19.15 uur 5127 04XX XXXX 1637 5853 98XX XXXX 2266 JOHN SMITH', + transactionAmount: { amount: '-10.99', currency: 'EUR' }, + }; + const normalizedTransaction = KBCkredbebb.normalizeTransaction( + transaction, + true, + ); + expect(normalizedTransaction.payeeName).toEqual( + 'CARREFOUR ST GIL BE1060 BRUXELLES', + ); + }); + + it('returns the debtorName as payeeName when the amount is positive', () => { + const transaction = { + debtorName: 'CARREFOUR ST GIL BE1060 BRUXELLES', + remittanceInformationUnstructured: + 'CARREFOUR ST GIL BE1060 BRUXELLES Betaling met Google Pay via Debit Mastercard 28-08-2024 om 19.15 uur 5127 04XX XXXX 1637 5853 98XX XXXX 2266 JOHN SMITH', + transactionAmount: { amount: '10.99', currency: 'EUR' }, + }; + const normalizedTransaction = KBCkredbebb.normalizeTransaction( + transaction, + true, + ); + expect(normalizedTransaction.payeeName).toEqual( + 'CARREFOUR ST GIL BE1060 BRUXELLES', + ); + }); + }); +}); diff --git a/packages/sync-server/src/app-gocardless/banks/tests/mbank-retail-brexplpw.spec.js b/packages/sync-server/src/app-gocardless/banks/tests/mbank-retail-brexplpw.spec.js new file mode 100644 index 00000000000..d8212b26048 --- /dev/null +++ b/packages/sync-server/src/app-gocardless/banks/tests/mbank-retail-brexplpw.spec.js @@ -0,0 +1,169 @@ +import MbankRetailBrexplpw from '../mbank-retail-brexplpw.js'; + +describe('MbankRetailBrexplpw', () => { + describe('#normalizeAccount', () => { + /** @type {import('../../gocardless.types.js').DetailedAccountWithInstitution} */ + const accountRaw = { + iban: 'PL00000000000000000987654321', + currency: 'PLN', + ownerName: 'John Example', + displayName: 'EKONTO', + product: 'RACHUNEK BIEŻĄCY', + usage: 'PRIV', + ownerAddressUnstructured: [ + 'POL', + 'UL. EXAMPLE STREET 10 M.1', + '00-000 WARSZAWA', + ], + id: 'd3eccc94-9536-48d3-98be-813f79199ee3', + created: '2023-01-18T13:24:55.879512Z', + last_accessed: null, + institution_id: 'MBANK_RETAIL_BREXPLPW', + status: 'READY', + owner_name: '', + institution: { + id: 'MBANK_RETAIL_BREXPLPW', + name: 'mBank Retail', + bic: 'BREXPLPW', + transaction_total_days: '90', + countries: ['PL'], + logo: 'https://cdn.nordigen.com/ais/MBANK_RETAIL_BREXCZPP.png', + supported_payments: {}, + supported_features: [ + 'access_scopes', + 'business_accounts', + 'card_accounts', + 'corporate_accounts', + 'pending_transactions', + 'private_accounts', + ], + }, + }; + it('returns normalized account data returned to Frontend', () => { + expect(MbankRetailBrexplpw.normalizeAccount(accountRaw)) + .toMatchInlineSnapshot(` + { + "account_id": "d3eccc94-9536-48d3-98be-813f79199ee3", + "iban": "PL00000000000000000987654321", + "institution": { + "bic": "BREXPLPW", + "countries": [ + "PL", + ], + "id": "MBANK_RETAIL_BREXPLPW", + "logo": "https://cdn.nordigen.com/ais/MBANK_RETAIL_BREXCZPP.png", + "name": "mBank Retail", + "supported_features": [ + "access_scopes", + "business_accounts", + "card_accounts", + "corporate_accounts", + "pending_transactions", + "private_accounts", + ], + "supported_payments": {}, + "transaction_total_days": "90", + }, + "mask": "4321", + "name": "EKONTO (XXX 4321)", + "official_name": "RACHUNEK BIEŻĄCY", + "type": "checking", + } + `); + }); + }); + + describe('#sortTransactions', () => { + it('returns transactions from newest to oldest', () => { + const sortedTransactions = MbankRetailBrexplpw.sortTransactions([ + { + transactionId: '202212300001', + transactionAmount: { amount: '100', currency: 'EUR' }, + }, + { + transactionId: '202212300003', + transactionAmount: { amount: '100', currency: 'EUR' }, + }, + { + transactionId: '202212300002', + transactionAmount: { amount: '100', currency: 'EUR' }, + }, + { + transactionId: '202212300000', + transactionAmount: { amount: '100', currency: 'EUR' }, + }, + { + transactionId: '202112300001', + transactionAmount: { amount: '100', currency: 'EUR' }, + }, + ]); + + expect(sortedTransactions).toEqual([ + { + transactionId: '202212300003', + transactionAmount: { amount: '100', currency: 'EUR' }, + }, + { + transactionId: '202212300002', + transactionAmount: { amount: '100', currency: 'EUR' }, + }, + { + transactionId: '202212300001', + transactionAmount: { amount: '100', currency: 'EUR' }, + }, + { + transactionId: '202212300000', + transactionAmount: { amount: '100', currency: 'EUR' }, + }, + { + transactionId: '202112300001', + transactionAmount: { amount: '100', currency: 'EUR' }, + }, + ]); + }); + + it('returns empty array for empty input', () => { + const sortedTransactions = MbankRetailBrexplpw.sortTransactions([]); + expect(sortedTransactions).toEqual([]); + }); + + it('returns empty array for undefined input', () => { + const sortedTransactions = + MbankRetailBrexplpw.sortTransactions(undefined); + expect(sortedTransactions).toEqual([]); + }); + }); + + describe('#countStartingBalance', () => { + /** @type {import('../../gocardless-node.types.js').Balance[]} */ + const balances = [ + { + balanceAmount: { amount: '1000.00', currency: 'PLN' }, + balanceType: 'interimBooked', + }, + ]; + + it('returns the same balance amount when no transactions', () => { + const transactions = []; + + expect( + MbankRetailBrexplpw.calculateStartingBalance(transactions, balances), + ).toEqual(100000); + }); + + it('returns the balance minus the available transactions', () => { + const transactions = [ + { + transactionAmount: { amount: '200.00', currency: 'PLN' }, + }, + { + transactionAmount: { amount: '300.50', currency: 'PLN' }, + }, + ]; + + expect( + MbankRetailBrexplpw.calculateStartingBalance(transactions, balances), + ).toEqual(49950); + }); + }); +}); diff --git a/packages/sync-server/src/app-gocardless/banks/tests/nationwide-naiagb21.spec.js b/packages/sync-server/src/app-gocardless/banks/tests/nationwide-naiagb21.spec.js new file mode 100644 index 00000000000..5dc549528b7 --- /dev/null +++ b/packages/sync-server/src/app-gocardless/banks/tests/nationwide-naiagb21.spec.js @@ -0,0 +1,105 @@ +import Nationwide from '../nationwide-naiagb21.js'; +import { mockTransactionAmount } from '../../services/tests/fixtures.js'; + +describe('Nationwide', () => { + describe('#normalizeTransaction', () => { + it('retains date for booked transaction', () => { + const d = new Date(); + d.setDate(d.getDate() - 7); + + const date = d.toISOString().split('T')[0]; + + const transaction = { + bookingDate: date, + transactionAmount: mockTransactionAmount, + }; + + const normalizedTransaction = Nationwide.normalizeTransaction( + transaction, + true, + ); + + expect(normalizedTransaction.date).toEqual(date); + }); + + it('fixes date for pending transactions', () => { + const d = new Date(); + const date = d.toISOString().split('T')[0]; + + const transaction = { + bookingDate: date, + transactionAmount: mockTransactionAmount, + }; + + const normalizedTransaction = Nationwide.normalizeTransaction( + transaction, + false, + ); + + expect(new Date(normalizedTransaction.date).getTime()).toBeLessThan( + d.getTime(), + ); + }); + + it('keeps transactionId if in the correct format', () => { + const transactionId = 'a896729bb8b30b5ca862fe70bd5967185e2b5d3a'; + const transaction = { + bookingDate: '2024-01-01T00:00:00Z', + transactionId, + transactionAmount: mockTransactionAmount, + }; + + const normalizedTransaction = Nationwide.normalizeTransaction( + transaction, + false, + ); + + expect(normalizedTransaction.transactionId).toBe(transactionId); + }); + + it('unsets transactionId if not valid length', () => { + const transaction = { + bookingDate: '2024-01-01T00:00:00Z', + transactionId: '0123456789', + transactionAmount: mockTransactionAmount, + }; + + const normalizedTransaction = Nationwide.normalizeTransaction( + transaction, + false, + ); + + expect(normalizedTransaction.transactionId).toBeNull(); + }); + + it('unsets transactionId if debit placeholder found', () => { + const transaction = { + bookingDate: '2024-01-01T00:00:00Z', + transactionId: '00DEBIT202401010000000000-1000SUPERMARKET', + transactionAmount: mockTransactionAmount, + }; + + const normalizedTransaction = Nationwide.normalizeTransaction( + transaction, + false, + ); + + expect(normalizedTransaction.transactionId).toBeNull(); + }); + + it('unsets transactionId if credit placeholder found', () => { + const transaction = { + bookingDate: '2024-01-01T00:00:00Z', + transactionId: '00CREDIT202401010000000000-1000SUPERMARKET', + transactionAmount: mockTransactionAmount, + }; + + const normalizedTransaction = Nationwide.normalizeTransaction( + transaction, + false, + ); + + expect(normalizedTransaction.transactionId).toBeNull(); + }); + }); +}); diff --git a/packages/sync-server/src/app-gocardless/banks/tests/nbg_ethngraaxxx.spec.js b/packages/sync-server/src/app-gocardless/banks/tests/nbg_ethngraaxxx.spec.js new file mode 100644 index 00000000000..990c6cd5e2c --- /dev/null +++ b/packages/sync-server/src/app-gocardless/banks/tests/nbg_ethngraaxxx.spec.js @@ -0,0 +1,48 @@ +import NbgEthngraaxxx from '../nbg_ethngraaxxx.js'; + +describe('NbgEthngraaxxx', () => { + describe('#normalizeTransaction', () => { + it('provides correct amount in pending transaction and removes payee prefix', () => { + const transaction = { + bookingDate: '2024-09-03', + date: '2024-09-03', + remittanceInformationUnstructured: 'ΑΓΟΡΑ testingson', + transactionAmount: { + amount: '100.00', + currency: 'EUR', + }, + valueDate: '2024-09-03', + }; + + const normalizedTransaction = NbgEthngraaxxx.normalizeTransaction( + transaction, + false, + ); + + expect(normalizedTransaction.transactionAmount.amount).toEqual('-100.00'); + expect(normalizedTransaction.payeeName).toEqual('Testingson'); + }); + }); + + it('provides correct amount and payee in booked transaction', () => { + const transaction = { + transactionId: 'O244015L68IK', + bookingDate: '2024-09-03', + date: '2024-09-03', + remittanceInformationUnstructured: 'testingson', + transactionAmount: { + amount: '-100.00', + currency: 'EUR', + }, + valueDate: '2024-09-03', + }; + + const normalizedTransaction = NbgEthngraaxxx.normalizeTransaction( + transaction, + true, + ); + + expect(normalizedTransaction.transactionAmount.amount).toEqual('-100.00'); + expect(normalizedTransaction.payeeName).toEqual('Testingson'); + }); +}); diff --git a/packages/sync-server/src/app-gocardless/banks/tests/revolut_revolt21.spec.js b/packages/sync-server/src/app-gocardless/banks/tests/revolut_revolt21.spec.js new file mode 100644 index 00000000000..40e8bef752d --- /dev/null +++ b/packages/sync-server/src/app-gocardless/banks/tests/revolut_revolt21.spec.js @@ -0,0 +1,46 @@ +import RevolutRevolt21 from '../revolut_revolt21.js'; + +describe('RevolutRevolt21', () => { + describe('#normalizeTransaction', () => { + it('returns the expected remittanceInformationUnstructured from a bizum expense transfer', () => { + const transaction = { + transactionAmount: { amount: '-1.00', currency: 'EUR' }, + remittanceInformationUnstructuredArray: [ + 'Bizum payment to: CREDITOR NAME', + 'Bizum description', + ], + bookingDate: '2024-09-21', + }; + + const normalizedTransaction = RevolutRevolt21.normalizeTransaction( + transaction, + true, + ); + + expect(normalizedTransaction.remittanceInformationUnstructured).toEqual( + 'Bizum description', + ); + }); + }); + + it('returns the expected payeeName and remittanceInformationUnstructured from a bizum income transfer', () => { + const transaction = { + transactionAmount: { amount: '1.00', currency: 'EUR' }, + remittanceInformationUnstructuredArray: [ + 'Bizum payment from: DEBTOR NAME', + 'Bizum description', + ], + bookingDate: '2024-09-21', + }; + + const normalizedTransaction = RevolutRevolt21.normalizeTransaction( + transaction, + true, + ); + + expect(normalizedTransaction.payeeName).toEqual('DEBTOR NAME'); + expect(normalizedTransaction.remittanceInformationUnstructured).toEqual( + 'Bizum description', + ); + }); +}); diff --git a/packages/sync-server/src/app-gocardless/banks/tests/sandboxfinance-sfin0000.spec.js b/packages/sync-server/src/app-gocardless/banks/tests/sandboxfinance-sfin0000.spec.js new file mode 100644 index 00000000000..ba705f7e738 --- /dev/null +++ b/packages/sync-server/src/app-gocardless/banks/tests/sandboxfinance-sfin0000.spec.js @@ -0,0 +1,131 @@ +import SandboxfinanceSfin0000 from '../sandboxfinance-sfin0000.js'; + +describe('SandboxfinanceSfin0000', () => { + describe('#normalizeAccount', () => { + /** @type {import('../../gocardless.types.js').DetailedAccountWithInstitution} */ + const accountRaw = { + resourceId: '01F3NS5ASCNMVCTEJDT0G215YE', + iban: 'GL0865354374424724', + currency: 'EUR', + ownerName: 'Jane Doe', + name: 'Main Account', + product: 'Checkings', + cashAccountType: 'CACC', + id: '99a0bfe2-0bef-46df-bff2-e9ae0c6c5838', + created: '2022-02-21T13:43:55.608911Z', + last_accessed: '2023-01-25T16:50:15.078264Z', + institution_id: 'SANDBOXFINANCE_SFIN0000', + status: 'READY', + owner_name: 'Jane Doe', + institution: { + id: 'SANDBOXFINANCE_SFIN0000', + name: 'Sandbox Finance', + bic: 'SFIN0000', + transaction_total_days: '90', + countries: ['XX'], + logo: 'https://cdn.nordigen.com/ais/SANDBOXFINANCE_SFIN0000.png', + supported_payments: {}, + supported_features: [], + }, + }; + + it('returns normalized account data returned to Frontend', () => { + expect(SandboxfinanceSfin0000.normalizeAccount(accountRaw)) + .toMatchInlineSnapshot(` + { + "account_id": "99a0bfe2-0bef-46df-bff2-e9ae0c6c5838", + "iban": "GL0865354374424724", + "institution": { + "bic": "SFIN0000", + "countries": [ + "XX", + ], + "id": "SANDBOXFINANCE_SFIN0000", + "logo": "https://cdn.nordigen.com/ais/SANDBOXFINANCE_SFIN0000.png", + "name": "Sandbox Finance", + "supported_features": [], + "supported_payments": {}, + "transaction_total_days": "90", + }, + "mask": "4724", + "name": "Main Account (XXX 4724)", + "official_name": "Checkings", + "type": "checking", + } + `); + }); + }); + + describe('#sortTransactions', () => { + it('handles empty arrays', () => { + const transactions = []; + const sortedTransactions = + SandboxfinanceSfin0000.sortTransactions(transactions); + expect(sortedTransactions).toEqual([]); + }); + + it('returns empty array for undefined input', () => { + const sortedTransactions = + SandboxfinanceSfin0000.sortTransactions(undefined); + expect(sortedTransactions).toEqual([]); + }); + }); + + describe('#countStartingBalance', () => { + /** @type {import('../../gocardless-node.types.js').Balance[]} */ + const balances = [ + { + balanceAmount: { amount: '1000.00', currency: 'PLN' }, + balanceType: 'interimAvailable', + }, + ]; + + it('should calculate the starting balance correctly', () => { + const sortedTransactions = [ + { + transactionId: '2022-01-01-1', + transactionAmount: { amount: '-100.00', currency: 'USD' }, + }, + { + transactionId: '2022-01-01-2', + transactionAmount: { amount: '50.00', currency: 'USD' }, + }, + { + transactionId: '2022-01-01-3', + transactionAmount: { amount: '-25.00', currency: 'USD' }, + }, + ]; + + const startingBalance = SandboxfinanceSfin0000.calculateStartingBalance( + sortedTransactions, + balances, + ); + + expect(startingBalance).toEqual(107500); + }); + + it('returns the same balance amount when no transactions', () => { + const transactions = []; + + expect( + SandboxfinanceSfin0000.calculateStartingBalance(transactions, balances), + ).toEqual(100000); + }); + + it('returns the balance minus the available transactions', () => { + /** @type {import('../../gocardless-node.types.js').Transaction[]} */ + const transactions = [ + { + transactionAmount: { amount: '200.00', currency: 'PLN' }, + }, + { + transactionAmount: { amount: '300.50', currency: 'PLN' }, + }, + ]; + + expect( + SandboxfinanceSfin0000.calculateStartingBalance(transactions, balances), + ).toEqual(49950); + }); + }); +}); diff --git a/packages/sync-server/src/app-gocardless/banks/tests/spk-marburg-biedenkopf-heladef1mar.spec.js b/packages/sync-server/src/app-gocardless/banks/tests/spk-marburg-biedenkopf-heladef1mar.spec.js new file mode 100644 index 00000000000..5641400b217 --- /dev/null +++ b/packages/sync-server/src/app-gocardless/banks/tests/spk-marburg-biedenkopf-heladef1mar.spec.js @@ -0,0 +1,254 @@ +import SpkMarburgBiedenkopfHeladef1mar from '../spk-marburg-biedenkopf-heladef1mar.js'; + +describe('SpkMarburgBiedenkopfHeladef1mar', () => { + describe('#normalizeAccount', () => { + /** @type {import('../../gocardless.types.js').DetailedAccountWithInstitution} */ + const accountRaw = { + resourceId: 'e896eec6-6096-4efc-a941-756bd9d74765', + iban: 'DE50533500000123456789', + currency: 'EUR', + ownerName: 'JANE DOE', + product: 'Sichteinlagen', + bic: 'HELADEF1MAR', + usage: 'PRIV', + id: 'a787ba27-02ee-4fd6-be86-73831adc5498', + created: '2024-01-01T14:17:11.630352Z', + last_accessed: '2024-01-01T14:19:42.709478Z', + institution_id: 'SPK_MARBURG_BIEDENKOPF_HELADEF1MAR', + status: 'READY', + owner_name: 'JANE DOE', + institution: { + id: 'SPK_MARBURG_BIEDENKOPF_HELADEF1MAR', + name: 'Sparkasse Marburg-Biedenkopf', + bic: 'HELADEF1MAR', + transaction_total_days: '360', + countries: ['DE'], + logo: 'https://storage.googleapis.com/gc-prd-institution_icons-production/DE/PNG/sparkasse.png', + supported_payments: { + 'single-payment': ['SCT', 'ISCT'], + }, + supported_features: [ + 'card_accounts', + 'payments', + 'pending_transactions', + ], + /*"identification_codes": []*/ + }, + }; + + it('returns normalized account data returned to Frontend', () => { + expect( + SpkMarburgBiedenkopfHeladef1mar.normalizeAccount(accountRaw), + ).toEqual({ + account_id: 'a787ba27-02ee-4fd6-be86-73831adc5498', + iban: 'DE50533500000123456789', + institution: { + bic: 'HELADEF1MAR', + countries: ['DE'], + id: 'SPK_MARBURG_BIEDENKOPF_HELADEF1MAR', + logo: 'https://storage.googleapis.com/gc-prd-institution_icons-production/DE/PNG/sparkasse.png', + name: 'Sparkasse Marburg-Biedenkopf', + supported_features: [ + 'card_accounts', + 'payments', + 'pending_transactions', + ], + supported_payments: { + 'single-payment': ['SCT', 'ISCT'], + }, + transaction_total_days: '360', + }, + mask: '6789', + name: 'Sichteinlagen (XXX 6789) EUR', + official_name: 'Sichteinlagen', + type: 'checking', + }); + }); + }); + + const transactionsRaw = [ + { + transactionId: 'fefa0b605ac14a7eb14f4c8ab6a6af55', + bookingDate: '2023-12-29', + valueDate: '2023-12-29', + transactionAmount: { + amount: '-40.00', + currency: 'EUR', + }, + creditorName: 'JET Tankstelle', + remittanceInformationStructured: 'AUTORISATION 28.12. 18:30', + proprietaryBankTransactionCode: 'NSTO+000+0000+000-AA', + internalTransactionId: '761660c052ed48e78c2be39775f08da9', + date: '2023-12-29', + }, + { + transactionId: '1a8e5d0df259472694f13132001af0a6', + bookingDate: '2023-12-28', + valueDate: '2023-12-28', + transactionAmount: { + amount: '-1242.47', + currency: 'EUR', + }, + creditorName: 'Peter Muster', + remittanceInformationStructured: 'Miete 12/2023', + proprietaryBankTransactionCode: 'NSTO+111+1111+111-BB', + internalTransactionId: '5a20ac78b146401e940b6fee30ee404b', + date: '2023-12-28', + }, + { + transactionId: '166983e65ec54000a361a952e6161f33', + bookingDate: '2023-12-27', + valueDate: '2023-12-27', + transactionAmount: { + amount: '1541.23', + currency: 'EUR', + }, + debtorName: 'Arbeitgeber AG', + remittanceInformationStructured: 'Lohn/Gehalt 12/2023', + proprietaryBankTransactionCode: 'NSTO+222+2222+222-CC', + internalTransactionId: '51630dda877f45f186d315b8058d891a', + date: '2023-12-27', + }, + { + transactionId: '4dd9f4c9968a45739c0705ebc675b54b', + bookingDate: '2023-12-26', + valueDate: '2023-12-26', + transactionAmount: { + amount: '-8.00', + currency: 'EUR', + }, + remittanceInformationStructuredArray: [ + 'Entgeltabrechnung', + 'siehe Anlage', + ], + proprietaryBankTransactionCode: 'NSTO+333+3333+333-DD', + internalTransactionId: '9c58c87c2d1644e4a5e149c837c16bbb', + date: '2023-12-26', + }, + ]; + + describe('#normalizeTransaction', () => { + it('fallbacks to remittanceInformationStructured when remittanceInformationUnstructed is not set', () => { + const transaction = { + transactionId: 'fefa0b605ac14a7eb14f4c8ab6a6af55', + bookingDate: '2023-12-29', + valueDate: '2023-12-29', + transactionAmount: { + amount: '-40.00', + currency: 'EUR', + }, + creditorName: 'JET Tankstelle', + remittanceInformationStructured: 'AUTORISATION 28.12. 18:30', + proprietaryBankTransactionCode: 'NSTO+000+0000+000-AA', + internalTransactionId: '761660c052ed48e78c2be39775f08da9', + date: '2023-12-29', + }; + + expect( + SpkMarburgBiedenkopfHeladef1mar.normalizeTransaction(transaction, true) + .remittanceInformationUnstructured, + ).toEqual('AUTORISATION 28.12. 18:30'); + }); + + it('fallbacks to remittanceInformationStructuredArray when remittanceInformationUnstructed and remittanceInformationStructured is not set', () => { + const transaction = { + transactionId: '4dd9f4c9968a45739c0705ebc675b54b', + bookingDate: '2023-12-26', + valueDate: '2023-12-26', + transactionAmount: { + amount: '-8.00', + currency: 'EUR', + }, + remittanceInformationStructuredArray: [ + 'Entgeltabrechnung', + 'siehe Anlage', + ], + proprietaryBankTransactionCode: 'NSTO+333+3333+333-DD', + internalTransactionId: '9c58c87c2d1644e4a5e149c837c16bbb', + date: '2023-12-26', + }; + + expect( + SpkMarburgBiedenkopfHeladef1mar.normalizeTransaction(transaction, true) + .remittanceInformationUnstructured, + ).toEqual('Entgeltabrechnung siehe Anlage'); + }); + }); + + describe('#sortTransactions', () => { + it('handles empty arrays', () => { + const transactions = []; + const sortedTransactions = + SpkMarburgBiedenkopfHeladef1mar.sortTransactions(transactions); + expect(sortedTransactions).toEqual([]); + }); + + it('returns empty array for undefined input', () => { + const sortedTransactions = + SpkMarburgBiedenkopfHeladef1mar.sortTransactions(undefined); + expect(sortedTransactions).toEqual([]); + }); + + it('returns sorted array for unsorted inputs', () => { + const normalizeTransactions = transactionsRaw.map((tx) => + SpkMarburgBiedenkopfHeladef1mar.normalizeTransaction(tx, true), + ); + const originalOrder = Array.from(normalizeTransactions); + const swap = (a, b) => { + const swap = normalizeTransactions[a]; + normalizeTransactions[a] = normalizeTransactions[b]; + normalizeTransactions[b] = swap; + }; + swap(1, 4); + swap(3, 6); + swap(0, 7); + const sortedTransactions = + SpkMarburgBiedenkopfHeladef1mar.sortTransactions(normalizeTransactions); + expect(sortedTransactions).toEqual(originalOrder); + }); + }); + + describe('#countStartingBalance', () => { + /** @type {import('../../gocardless-node.types.js').Balance[]} */ + const balances = [ + { + balanceAmount: { amount: '3596.87', currency: 'EUR' }, + balanceType: 'closingBooked', + referenceDate: '2023-12-29', + }, + ]; + + it('should return 0 when no transactions or balances are provided', () => { + const startingBalance = + SpkMarburgBiedenkopfHeladef1mar.calculateStartingBalance([], []); + expect(startingBalance).toEqual(0); + }); + + it('should calculate the starting balance correctly', () => { + const normalizeTransactions = transactionsRaw.map((tx) => + SpkMarburgBiedenkopfHeladef1mar.normalizeTransaction(tx, true), + ); + const sortedTransactions = + SpkMarburgBiedenkopfHeladef1mar.sortTransactions(normalizeTransactions); + + const startingBalance = + SpkMarburgBiedenkopfHeladef1mar.calculateStartingBalance( + sortedTransactions, + balances, + ); + + expect(startingBalance).toEqual(334611); + }); + + it('returns the same balance amount when no transactions', () => { + const transactions = []; + + expect( + SpkMarburgBiedenkopfHeladef1mar.calculateStartingBalance( + transactions, + balances, + ), + ).toEqual(359687); + }); + }); +}); diff --git a/packages/sync-server/src/app-gocardless/banks/tests/virgin_nrnbgb22.spec.js b/packages/sync-server/src/app-gocardless/banks/tests/virgin_nrnbgb22.spec.js new file mode 100644 index 00000000000..c6cb6b26cc3 --- /dev/null +++ b/packages/sync-server/src/app-gocardless/banks/tests/virgin_nrnbgb22.spec.js @@ -0,0 +1,65 @@ +import Virgin from '../virgin_nrnbgb22.js'; +import { mockTransactionAmount } from '../../services/tests/fixtures.js'; + +describe('Virgin', () => { + describe('#normalizeTransaction', () => { + it('does not alter simple payee information', () => { + const transaction = { + bookingDate: '2024-01-01T00:00:00Z', + remittanceInformationUnstructured: 'DIRECT DEBIT PAYMENT', + transactionAmount: mockTransactionAmount, + }; + + const normalizedTransaction = Virgin.normalizeTransaction( + transaction, + true, + ); + + expect(normalizedTransaction.creditorName).toEqual( + 'DIRECT DEBIT PAYMENT', + ); + expect(normalizedTransaction.debtorName).toEqual('DIRECT DEBIT PAYMENT'); + expect(normalizedTransaction.remittanceInformationUnstructured).toEqual( + 'DIRECT DEBIT PAYMENT', + ); + }); + + it('formats bank transfer payee and references', () => { + const transaction = { + bookingDate: '2024-01-01T00:00:00Z', + remittanceInformationUnstructured: 'FPS, Joe Bloggs, Food', + transactionAmount: mockTransactionAmount, + }; + + const normalizedTransaction = Virgin.normalizeTransaction( + transaction, + true, + ); + + expect(normalizedTransaction.creditorName).toEqual('Joe Bloggs'); + expect(normalizedTransaction.debtorName).toEqual('Joe Bloggs'); + expect(normalizedTransaction.remittanceInformationUnstructured).toEqual( + 'Food', + ); + }); + + it('removes method information from payee name', () => { + const transaction = { + bookingDate: '2024-01-01T00:00:00Z', + remittanceInformationUnstructured: 'Card 99, Tesco Express', + transactionAmount: mockTransactionAmount, + }; + + const normalizedTransaction = Virgin.normalizeTransaction( + transaction, + true, + ); + + expect(normalizedTransaction.creditorName).toEqual('Tesco Express'); + expect(normalizedTransaction.debtorName).toEqual('Tesco Express'); + expect(normalizedTransaction.remittanceInformationUnstructured).toEqual( + 'Card 99, Tesco Express', + ); + }); + }); +}); diff --git a/packages/sync-server/src/app-gocardless/banks/util/extract-payeeName-from-remittanceInfo.js b/packages/sync-server/src/app-gocardless/banks/util/extract-payeeName-from-remittanceInfo.js new file mode 100644 index 00000000000..4dcb7fa2dd2 --- /dev/null +++ b/packages/sync-server/src/app-gocardless/banks/util/extract-payeeName-from-remittanceInfo.js @@ -0,0 +1,36 @@ +/** +/** + * Extracts the payee name from the unstructured remittance information string based on pattern detection. + * + * This function scans the `remittanceInformationUnstructured` string for the presence of + * any of the specified patterns and removes the substring from the position of the last + * occurrence of the most relevant pattern. If no patterns are found, it returns the original string. + * + * @param {string} [remittanceInformationUnstructured=''] - The unstructured remittance information from which to extract the payee name. + * @param {string[]} [patterns=[]] - An array of patterns to look for within the remittance information. + * These patterns are used to identify and remove unwanted parts of the remittance information. + * @returns {string} - The extracted payee name, cleaned of any matched patterns, or the original + * remittance information if no patterns are found. + * + * @example + * const remittanceInfo = 'John Doe Paiement Maestro par Carte de débit CBC 05-09-2024 à 15.43 heures 6703 19XX XXXX X...'; + * const patterns = ['Paiement', 'Domiciliation', 'Transfert', 'Ordre permanent']; + * const payeeName = extractPayeeNameFromRemittanceInfo(remittanceInfo, patterns); // --> 'John Doe' + */ +export function extractPayeeNameFromRemittanceInfo( + remittanceInformationUnstructured, + patterns, +) { + if (!remittanceInformationUnstructured || !patterns.length) { + return remittanceInformationUnstructured; + } + + const indexForRemoval = patterns.reduce((maxIndex, pattern) => { + const index = remittanceInformationUnstructured.lastIndexOf(pattern); + return index > maxIndex ? index : maxIndex; + }, -1); + + return indexForRemoval > -1 + ? remittanceInformationUnstructured.substring(0, indexForRemoval).trim() + : remittanceInformationUnstructured; +} diff --git a/packages/sync-server/src/app-gocardless/banks/virgin_nrnbgb22.js b/packages/sync-server/src/app-gocardless/banks/virgin_nrnbgb22.js new file mode 100644 index 00000000000..7340cdeb118 --- /dev/null +++ b/packages/sync-server/src/app-gocardless/banks/virgin_nrnbgb22.js @@ -0,0 +1,39 @@ +import Fallback from './integration-bank.js'; + +/** @type {import('./bank.interface.js').IBank} */ +export default { + ...Fallback, + + institutionIds: ['VIRGIN_NRNBGB22'], + + accessValidForDays: 90, + + normalizeTransaction(transaction, booked) { + const transferPrefixes = ['MOB', 'FPS']; + const methodRegex = /^(Card|WLT)\s\d+/; + + const parts = transaction.remittanceInformationUnstructured.split(', '); + + if (transferPrefixes.includes(parts[0])) { + // Transfer remittance information begins with either "MOB" or "FPS" + // the second field contains the payee and the third contains the + // reference + + transaction.creditorName = parts[1]; + transaction.debtorName = parts[1]; + transaction.remittanceInformationUnstructured = parts[2]; + } else if (parts[0].match(methodRegex)) { + // The payee is prefixed with the payment method, eg "Card 11, {payee}" + + transaction.creditorName = parts[1]; + transaction.debtorName = parts[1]; + } else { + // Simple payee name + + transaction.creditorName = transaction.remittanceInformationUnstructured; + transaction.debtorName = transaction.remittanceInformationUnstructured; + } + + return Fallback.normalizeTransaction(transaction, booked); + }, +}; diff --git a/packages/sync-server/src/app-gocardless/errors.js b/packages/sync-server/src/app-gocardless/errors.js new file mode 100644 index 00000000000..3a77360bfa0 --- /dev/null +++ b/packages/sync-server/src/app-gocardless/errors.js @@ -0,0 +1,84 @@ +export class RequisitionNotLinked extends Error { + constructor(params = {}) { + super('Requisition not linked yet'); + this.details = params; + } +} + +export class AccountNotLinkedToRequisition extends Error { + constructor(accountId, requisitionId) { + super('Provided account id is not linked to given requisition'); + this.details = { + accountId, + requisitionId, + }; + } +} + +export class GenericGoCardlessError extends Error { + constructor(data = {}) { + super('GoCardless returned error'); + this.details = data; + } +} + +export class GoCardlessClientError extends Error { + constructor(message, details) { + super(message); + this.details = details; + } +} + +export class InvalidInputDataError extends GoCardlessClientError { + constructor(response) { + super('Invalid provided parameters', response); + } +} + +export class InvalidGoCardlessTokenError extends GoCardlessClientError { + constructor(response) { + super('Token is invalid or expired', response); + } +} + +export class AccessDeniedError extends GoCardlessClientError { + constructor(response) { + super('IP address access denied', response); + } +} + +export class NotFoundError extends GoCardlessClientError { + constructor(response) { + super('Resource not found', response); + } +} + +export class ResourceSuspended extends GoCardlessClientError { + constructor(response) { + super( + 'Resource was suspended due to numerous errors that occurred while accessing it', + response, + ); + } +} + +export class RateLimitError extends GoCardlessClientError { + constructor(response) { + super( + 'Daily request limit set by the Institution has been exceeded', + response, + ); + } +} + +export class UnknownError extends GoCardlessClientError { + constructor(response) { + super('Request to Institution returned an error', response); + } +} + +export class ServiceError extends GoCardlessClientError { + constructor(response) { + super('Institution service unavailable', response); + } +} diff --git a/packages/sync-server/src/app-gocardless/gocardless-node.types.ts b/packages/sync-server/src/app-gocardless/gocardless-node.types.ts new file mode 100644 index 00000000000..539c91e4c59 --- /dev/null +++ b/packages/sync-server/src/app-gocardless/gocardless-node.types.ts @@ -0,0 +1,487 @@ +type RequisitionStatus = + | 'CR' + | 'ID' + | 'LN' + | 'RJ' + | 'ER' + | 'SU' + | 'EX' + | 'GC' + | 'UA' + | 'GA' + | 'SA'; + +export type Requisition = { + /** + * option to enable account selection view for the end user + */ + account_selection: boolean; + + /** + * array of account IDs retrieved within a scope of this requisition + */ + accounts: string[]; + + /** + * EUA associated with this requisition + */ + agreement: string; + + /** + * The date & time at which the requisition was created. + */ + created: string; + + /** + * The unique ID of the requisition + */ + id: string; + + /** + * an Institution ID for this Requisition + */ + institution_id: string; + + /** + * link to initiate authorization with Institution + */ + link: string; + + /** + * redirect URL to your application after end-user authorization with ASPSP + */ + redirect: string; + + /** + * enable redirect back to the client after account list received + */ + redirect_immediate: boolean; + + /** + * additional ID to identify the end user + */ + reference: string; + + /** + * optional SSN field to verify ownership of the account + */ + ssn: string; + + /** + * status of this requisition + */ + status: RequisitionStatus; + + /** + * A two-letter country code (ISO 639-1) + */ + user_language: string; +}; + +/** + * Object representing GoCardless account details + * Account details will be returned in Berlin Group PSD2 format. + */ +export type GoCardlessAccountDetails = { + /** + * Resource id of the account + */ + resourceId?: string; + + /** + * BBAN of the account. This data element is used for payment accounts which have no IBAN + */ + bban?: string; + + /** + * BIC associated to the account + */ + bic?: string; + + /** + * External Cash Account Type 1 Code from ISO 20022 + */ + cashAccountType?: string; + + /** + * Currency of the account + */ + currency: string; + + /** + * Specifications that might be provided by the financial institution, including + * - Characteristics of the account + * - Characteristics of the relevant card + */ + details?: string; + + /** + * Name of the account as defined by the end user within online channels + */ + displayName?: string; + + /** + * IBAN of the account + */ + iban?: string; + + /** + * This data attribute is a field where a financial institution can name a cash account associated with pending card transactions + */ + linkedAccounts?: string; + + /** + * Alias to a payment account via a registered mobile phone number + */ + msisdn?: string; + + /** + * Name of the account, as assigned by the financial institution + */ + name?: string; + + /** + * Address of the legal account owner + */ + ownerAddressUnstructured?: string[]; + + /** + * Name of the legal account owner. If there is more than one owner, then two names might be noted here. For a corporate account, the corporate name is used for this attribute. + */ + ownerName?: string; + + /** + * Product Name of the Bank for this account, proprietary definition + */ + product?: string; + + /** + * Account status. The value is one of the following: + * - "enabled": account is available + * - "deleted": account is terminated + * - "blocked": account is blocked, e.g. for legal reasons + * + * If this field is not used, then the account is considered available according to the specification. + */ + status?: 'enabled' | 'deleted' | 'blocked'; + + /** + * Specifies the usage of the account: + * - PRIV: private personal account + * - ORGA: professional account + */ + usage?: 'PRIV' | 'ORGA'; +}; + +/** + * Representation of the GoCardless account metadata + */ +export type GoCardlessAccountMetadata = { + /** + * ID of the GoCardless account metadata + */ + id: string; + /** + * Date when the GoCardless account metadata was created + */ + created: string; + /** + * Date of the last access to the GoCardless account metadata + */ + last_accessed: string; + /** + * IBAN of the GoCardless account metadata + */ + iban: string; + /** + * ID of the institution associated with the GoCardless account metadata + */ + institution_id: string; + /** + * Status of the GoCardless account + * DISCOVERED: User has successfully authenticated and account is discovered + * PROCESSING: Account is being processed by the Institution + * ERROR: An error was encountered when processing account + * EXPIRED: Access to account has expired as set in End User Agreement + * READY: Account has been successfully processed + * SUSPENDED: Account has been suspended (more than 10 consecutive failed attempts to access the account) + */ + status: + | 'DISCOVERED' + | 'PROCESSING' + | 'ERROR' + | 'EXPIRED' + | 'READY' + | 'SUSPENDED'; + /** + * Name of the owner of the GoCardless account metadata + */ + owner_name: string; +}; + +/** + * Information about the Institution + */ +export type Institution = { + /** + * The id of the institution, for example "N26_NTSBDEB1" + */ + id: string; + + /** + * The name of the institution, for example "N26 Bank" + */ + name: string; + + /** + * The BIC of the institution, for example "NTSBDEB1" + */ + bic: string; + + /** + * The total number of days of transactions available, for example "90" + */ + transaction_total_days: string; + + /** + * The countries where the institution operates, for example `["PL"]` + */ + countries: string[]; + + /** + * The logo URL of the institution, for example "https://cdn.nordigen.com/ais/N26_SANDBOX_NTSBDEB1.png" + */ + logo: string; + + supported_payments?: object; + supported_features?: string[]; +}; + +/** + * An object containing information about a balance + */ +export type Balance = { + /** + * An object containing the balance amount and currency + */ + balanceAmount: Amount; + /** + * The type of balance + */ + balanceType: + | 'closingBooked' + | 'expected' + | 'forwardAvailable' + | 'interimAvailable' + | 'interimBooked' + | 'nonInvoiced' + | 'openingBooked'; + /** + * A flag indicating if the credit limit of the corresponding account is included in the calculation of the balance (if applicable) + */ + creditLimitIncluded?: boolean; + /** + * The date and time of the last change to the balance + */ + lastChangeDateTime?: string; + /** + * The reference of the last committed transaction to support the TPP in identifying whether all end users transactions are already known + */ + lastCommittedTransaction?: string; + /** + * The date of the balance + */ + referenceDate?: string; +}; + +/** + * An object representing the amount of a transaction + */ +export type Amount = { + /** + * The amount of the transaction + */ + amount: string; + + /** + * The currency of the transaction + */ + currency: string; +}; + +/** + * An object representing a financial transaction + */ +export type Transaction = { + /** + * Might be used by the financial institution to transport additional transaction-related information. + */ + additionalInformation?: string; + + /** + * Is used if and only if the bookingStatus entry equals "information". + */ + bookingStatus?: string; + + /** + * The balance after this transaction. Recommended balance type is interimBooked. + */ + balanceAfterTransaction?: Pick<Balance, 'balanceType' | 'balanceAmount'>; + + /** + * Bank transaction code as used by the financial institution and using the sub elements of this structured code defined by ISO20022. For standing order reports the following codes are applicable: + * "PMNT-ICDT-STDO" for credit transfers, + * "PMNT-IRCT-STDO" for instant credit transfers, + * "PMNT-ICDT-XBST" for cross-border credit transfers, + * "PMNT-IRCT-XBST" for cross-border real-time credit transfers, + * "PMNT-MCOP-OTHR" for specific standing orders which have a dynamic amount to move left funds e.g. on month end to a saving account + */ + bankTransactionCode?: string; + + /** + * The date when an entry is posted to an account on the financial institution's books. + */ + bookingDate?: string; + + /** + * The date and time when an entry is posted to an account on the financial institution's books. + */ + bookingDateTime?: string; + + /** + * Identification of a cheque + */ + checkId?: string; + + /** + * Account reference, conditional + */ + creditorAccount?: string; + + /** + * BICFI + */ + creditorAgent?: string; + + /** + * Identification of creditors, e.g. a SEPA Creditor ID + */ + creditorId?: string; + + /** + * Name of the creditor if a "debited" transaction + */ + creditorName?: string; + + /** + * Array of report exchange rates + */ + currencyExchange?: string[]; + + /** + * Account reference, conditional + */ + debtorAccount?: { + iban: string; + }; + + /** + * BICFI + */ + debtorAgent?: string; + + /** + * Name of the debtor if a "credited" transaction + */ + debtorName?: string; + + /** + * Unique end-to-end ID + */ + endToEndId?: string; + + /** + * The identification of the transaction as used for reference given by the financial institution. + */ + entryReference?: string; + + /** + * Transaction identifier given by GoCardless + */ + internalTransactionId?: string; + + /** + * Identification of Mandates, e.g. a SEPA Mandate ID + */ + mandateId?: string; + + /** + * Merchant category code as defined by card issuer + */ + merchantCategoryCode?: string; + + /** + * Proprietary bank transaction code as used within a community or within an financial institution + */ + proprietaryBankTransactionCode?: string; + + /** + * Conditional + */ + purposeCode?: string; + + /** + * Reference as contained in the structured remittance reference structure + */ + remittanceInformationStructured?: string; + + /** + * Reference as contained in the structured remittance array reference structure + */ + remittanceInformationStructuredArray?: string[]; + + /** + * Reference as contained in the unstructured remittance reference structure + */ + remittanceInformationUnstructured?: string; + + /** + * Reference as contained in the unstructured remittance array reference structure + */ + remittanceInformationUnstructuredArray?: string[]; + + /** + * The amount of the transaction as billed to the account + */ + transactionAmount: Amount; + + /** + * Unique transaction identifier given by financial institution + */ + transactionId?: string; + + /** + * + */ + ultimateCreditor?: string; + + /** + * + */ + ultimateDebtor?: string; + + /** + * The Date at which assets become available to the account owner in case of a credit + */ + valueDate?: string; + + /** + * The date and time at which assets become available to the account owner in case of a credit + */ + valueDateTime?: string; +}; + +export type Transactions = { + booked: Transaction[]; + pending: Transaction[]; +}; diff --git a/packages/sync-server/src/app-gocardless/gocardless.types.ts b/packages/sync-server/src/app-gocardless/gocardless.types.ts new file mode 100644 index 00000000000..373749d915d --- /dev/null +++ b/packages/sync-server/src/app-gocardless/gocardless.types.ts @@ -0,0 +1,93 @@ +import { + GoCardlessAccountMetadata, + GoCardlessAccountDetails, + Institution, + Transactions, + Balance, + Transaction, +} from './gocardless-node.types.js'; + +export type DetailedAccount = Omit<GoCardlessAccountDetails, 'status'> & + GoCardlessAccountMetadata; +export type DetailedAccountWithInstitution = DetailedAccount & { + institution: Institution; +}; +export type TransactionWithBookedStatus = Transaction & { booked: boolean }; + +export type NormalizedAccountDetails = { + /** + * Id of the account + */ + account_id: string; + + /** + * Institution of account + */ + institution: Institution; + + /** + * last 4 digits from the account iban + */ + mask: string; + + /** + * the account iban + */ + iban: string; + + /** + * Name displayed on the UI of Actual app + */ + name: string; + + /** + * name of the product in the institution + */ + official_name: string; + + /** + * type of account + */ + type: string; +}; + +export type GetTransactionsParams = { + /** + * Id of the institution from GoCardless + */ + institutionId: string; + + /** + * Id of account from the GoCardless app + */ + accountId: string; + + /** + * Begin date of the period from which we want to download transactions + */ + startDate: string; + + /** + * End date of the period from which we want to download transactions + */ + endDate?: string; +}; + +export type GetTransactionsResponse = { + status_code?: number; + detail?: string; + transactions: Transactions; +}; + +export type CreateRequisitionParams = { + institutionId: string; + + /** + * Host of your frontend app - on this host you will be redirected after linking with bank + */ + host: string; +}; + +export type GetBalances = { + balances: Balance[]; +}; diff --git a/packages/sync-server/src/app-gocardless/link.html b/packages/sync-server/src/app-gocardless/link.html new file mode 100644 index 00000000000..cd3155849ed --- /dev/null +++ b/packages/sync-server/src/app-gocardless/link.html @@ -0,0 +1,18 @@ +<!DOCTYPE html> +<html lang="en"> + <head> + <meta charset="utf-8" /> + <title>Actual + + + + +

Please wait...

+

+ The window should close automatically. If nothing happened you can close + this window or tab. +

+ + diff --git a/packages/sync-server/src/app-gocardless/services/gocardless-service.js b/packages/sync-server/src/app-gocardless/services/gocardless-service.js new file mode 100644 index 00000000000..754e36d1183 --- /dev/null +++ b/packages/sync-server/src/app-gocardless/services/gocardless-service.js @@ -0,0 +1,621 @@ +import BankFactory, { BANKS_WITH_LIMITED_HISTORY } from '../bank-factory.js'; +import { + AccessDeniedError, + AccountNotLinkedToRequisition, + GenericGoCardlessError, + InvalidInputDataError, + InvalidGoCardlessTokenError, + NotFoundError, + RateLimitError, + ResourceSuspended, + RequisitionNotLinked, + ServiceError, + UnknownError, +} from '../errors.js'; +import * as nordigenNode from 'nordigen-node'; +import * as uuid from 'uuid'; +import jwt from 'jws'; +import { SecretName, secretsService } from '../../services/secrets-service.js'; + +const GoCardlessClient = nordigenNode.default; + +const clients = new Map(); + +const getGocardlessClient = () => { + const secrets = { + secretId: secretsService.get(SecretName.gocardless_secretId), + secretKey: secretsService.get(SecretName.gocardless_secretKey), + }; + + const hash = JSON.stringify(secrets); + + if (!clients.has(hash)) { + clients.set(hash, new GoCardlessClient(secrets)); + } + + return clients.get(hash); +}; + +export const handleGoCardlessError = (error) => { + const status = error?.response?.status; + + switch (status) { + case 400: + throw new InvalidInputDataError(error); + case 401: + throw new InvalidGoCardlessTokenError(error); + case 403: + throw new AccessDeniedError(error); + case 404: + throw new NotFoundError(error); + case 409: + throw new ResourceSuspended(error); + case 429: + throw new RateLimitError(error); + case 500: + throw new UnknownError(error); + case 503: + throw new ServiceError(error); + default: + throw new GenericGoCardlessError(error); + } +}; + +export const goCardlessService = { + /** + * Check if the GoCardless service is configured to be used. + * @returns {boolean} + */ + isConfigured: () => { + return !!( + getGocardlessClient().secretId && getGocardlessClient().secretKey + ); + }, + + /** + * + * @returns {Promise} + */ + setToken: async () => { + const isExpiredJwtToken = (token) => { + const decodedToken = jwt.decode(token); + if (!decodedToken) { + return true; + } + const payload = decodedToken.payload; + const clockTimestamp = Math.floor(Date.now() / 1000); + return clockTimestamp >= payload.exp; + }; + + if (isExpiredJwtToken(getGocardlessClient().token)) { + // Generate new access token. Token is valid for 24 hours + // Note: access_token is automatically injected to other requests after you successfully obtain it + try { + await client.generateToken(); + } catch (error) { + handleGoCardlessError(error); + } + } + }, + + /** + * + * @param requisitionId + * @throws {RequisitionNotLinked} Will throw an error if requisition is not in Linked + * @throws {InvalidInputDataError} + * @throws {InvalidGoCardlessTokenError} + * @throws {AccessDeniedError} + * @throws {NotFoundError} + * @throws {ResourceSuspended} + * @throws {RateLimitError} + * @throws {UnknownError} + * @throws {ServiceError} + * @returns {Promise} + */ + getLinkedRequisition: async (requisitionId) => { + const requisition = await goCardlessService.getRequisition(requisitionId); + + const { status } = requisition; + + // Continue only if status of requisition is "LN" what does + // mean that account has been successfully linked to requisition + if (status !== 'LN') { + throw new RequisitionNotLinked({ requisitionStatus: status }); + } + + return requisition; + }, + + /** + * Returns requisition and all linked accounts in their Bank format. + * Each account object is extended about details of the institution + * @param requisitionId + * @throws {RequisitionNotLinked} Will throw an error if requisition is not in Linked + * @throws {InvalidInputDataError} + * @throws {InvalidGoCardlessTokenError} + * @throws {AccessDeniedError} + * @throws {NotFoundError} + * @throws {ResourceSuspended} + * @throws {RateLimitError} + * @throws {UnknownError} + * @throws {ServiceError} + * @returns {Promise<{requisition: import('../gocardless-node.types.js').Requisition, accounts: Array}>} + */ + getRequisitionWithAccounts: async (requisitionId) => { + const requisition = await goCardlessService.getLinkedRequisition( + requisitionId, + ); + + let institutionIdSet = new Set(); + const detailedAccounts = await Promise.all( + requisition.accounts.map(async (accountId) => { + const account = await goCardlessService.getDetailedAccount(accountId); + institutionIdSet.add(account.institution_id); + return account; + }), + ); + + const institutions = await Promise.all( + Array.from(institutionIdSet).map(async (institutionId) => { + return await goCardlessService.getInstitution(institutionId); + }), + ); + + const extendedAccounts = + await goCardlessService.extendAccountsAboutInstitutions({ + accounts: detailedAccounts, + institutions, + }); + + const normalizedAccounts = extendedAccounts.map((account) => { + const bankAccount = BankFactory(account.institution_id); + return bankAccount.normalizeAccount(account); + }); + + return { requisition, accounts: normalizedAccounts }; + }, + + /** + * + * @param requisitionId + * @param accountId + * @param startDate + * @param endDate + * @throws {AccountNotLinkedToRequisition} Will throw an error if requisition not includes provided account id + * @throws {RequisitionNotLinked} Will throw an error if requisition is not in Linked + * @throws {InvalidInputDataError} + * @throws {InvalidGoCardlessTokenError} + * @throws {AccessDeniedError} + * @throws {NotFoundError} + * @throws {ResourceSuspended} + * @throws {RateLimitError} + * @throws {UnknownError} + * @throws {ServiceError} + * @returns {Promise<{balances: Array, institutionId: string, transactions: {booked: Array, pending: Array, all: Array}, startingBalance: number}>} + */ + getTransactionsWithBalance: async ( + requisitionId, + accountId, + startDate, + endDate, + ) => { + const { institution_id, accounts: accountIds } = + await goCardlessService.getLinkedRequisition(requisitionId); + + if (!accountIds.includes(accountId)) { + throw new AccountNotLinkedToRequisition(accountId, requisitionId); + } + + const [normalizedTransactions, accountBalance] = await Promise.all([ + goCardlessService.getNormalizedTransactions( + requisitionId, + accountId, + startDate, + endDate, + ), + goCardlessService.getBalances(accountId), + ]); + + const transactions = normalizedTransactions.transactions; + + const bank = BankFactory(institution_id); + + const startingBalance = bank.calculateStartingBalance( + transactions.booked, + accountBalance.balances, + ); + + return { + balances: accountBalance.balances, + institutionId: institution_id, + startingBalance, + transactions, + }; + }, + + /** + * + * @param requisitionId + * @param accountId + * @param startDate + * @param endDate + * @throws {AccountNotLinkedToRequisition} Will throw an error if requisition not includes provided account id + * @throws {RequisitionNotLinked} Will throw an error if requisition is not in Linked + * @throws {InvalidInputDataError} + * @throws {InvalidGoCardlessTokenError} + * @throws {AccessDeniedError} + * @throws {NotFoundError} + * @throws {ResourceSuspended} + * @throws {RateLimitError} + * @throws {UnknownError} + * @throws {ServiceError} + * @returns {Promise<{institutionId: string, transactions: {booked: Array, pending: Array, all: Array}}>} + */ + getNormalizedTransactions: async ( + requisitionId, + accountId, + startDate, + endDate, + ) => { + const { institution_id, accounts: accountIds } = + await goCardlessService.getLinkedRequisition(requisitionId); + + if (!accountIds.includes(accountId)) { + throw new AccountNotLinkedToRequisition(accountId, requisitionId); + } + + const transactions = await goCardlessService.getTransactions({ + institutionId: institution_id, + accountId, + startDate, + endDate, + }); + + const bank = BankFactory(institution_id); + const sortedBookedTransactions = bank.sortTransactions( + transactions.transactions?.booked, + ); + const sortedPendingTransactions = bank.sortTransactions( + transactions.transactions?.pending, + ); + const allTransactions = sortedBookedTransactions.map((t) => { + return { ...t, booked: true }; + }); + sortedPendingTransactions.forEach((t) => + allTransactions.push({ ...t, booked: false }), + ); + const sortedAllTransactions = bank.sortTransactions(allTransactions); + + return { + institutionId: institution_id, + transactions: { + booked: sortedBookedTransactions, + pending: sortedPendingTransactions, + all: sortedAllTransactions, + }, + }; + }, + + /** + * + * @param {import('../gocardless.types.js').CreateRequisitionParams} params + * @throws {InvalidInputDataError} + * @throws {InvalidGoCardlessTokenError} + * @throws {AccessDeniedError} + * @throws {NotFoundError} + * @throws {ResourceSuspended} + * @throws {RateLimitError} + * @throws {UnknownError} + * @throws {ServiceError} + * @returns {Promise<{requisitionId, link}>} + */ + createRequisition: async ({ institutionId, host }) => { + await goCardlessService.setToken(); + + const institution = await goCardlessService.getInstitution(institutionId); + const bank = BankFactory(institutionId); + + let response; + try { + response = await client.initSession({ + redirectUrl: host + '/gocardless/link', + institutionId, + referenceId: uuid.v4(), + accessValidForDays: bank.accessValidForDays, + maxHistoricalDays: BANKS_WITH_LIMITED_HISTORY.includes(institutionId) + ? Number(institution.transaction_total_days) >= 90 + ? '89' + : institution.transaction_total_days + : institution.transaction_total_days, + userLanguage: 'en', + ssn: null, + redirectImmediate: false, + accountSelection: false, + }); + } catch (error) { + handleGoCardlessError(error); + } + + const { link, id: requisitionId } = response; + + return { + link, + requisitionId, + }; + }, + + /** + * Deletes requisition by provided ID + * @param requisitionId + * @throws {InvalidInputDataError} + * @throws {InvalidGoCardlessTokenError} + * @throws {AccessDeniedError} + * @throws {NotFoundError} + * @throws {ResourceSuspended} + * @throws {RateLimitError} + * @throws {UnknownError} + * @throws {ServiceError} + * @returns {Promise<{summary: string, detail: string}>} + */ + deleteRequisition: async (requisitionId) => { + await goCardlessService.getRequisition(requisitionId); + + let response; + try { + response = client.deleteRequisition(requisitionId); + } catch (error) { + handleGoCardlessError(error); + } + + return response; + }, + + /** + * Retrieve a requisition by ID + * https://nordigen.com/en/docs/account-information/integration/parameters-and-responses/#/requisitions/requisition%20by%20id + * @param { string } requisitionId + * @throws {InvalidInputDataError} + * @throws {InvalidGoCardlessTokenError} + * @throws {AccessDeniedError} + * @throws {NotFoundError} + * @throws {ResourceSuspended} + * @throws {RateLimitError} + * @throws {UnknownError} + * @throws {ServiceError} + * @returns { Promise } + */ + getRequisition: async (requisitionId) => { + await goCardlessService.setToken(); + + let response; + try { + response = client.getRequisitionById(requisitionId); + } catch (error) { + handleGoCardlessError(error); + } + + return response; + }, + + /** + * Retrieve an detailed account by account id + * @param accountId + * @returns {Promise} + */ + getDetailedAccount: async (accountId) => { + let detailedAccount, metadataAccount; + try { + [detailedAccount, metadataAccount] = await Promise.all([ + client.getDetails(accountId), + client.getMetadata(accountId), + ]); + } catch (error) { + handleGoCardlessError(error); + } + + return { + ...detailedAccount.account, + ...metadataAccount, + }; + }, + + /** + * Retrieve account metadata by account id + * + * Unlike getDetailedAccount, this method is not affected by institution rate-limits. + * + * @param accountId + * @returns {Promise} + */ + getAccountMetadata: async (accountId) => { + let response; + try { + response = await client.getMetadata(accountId); + } catch (error) { + handleGoCardlessError(error); + } + + return response; + }, + + /** + * Retrieve details about all Institutions in a specific country + * @param country + * @throws {InvalidInputDataError} + * @throws {InvalidGoCardlessTokenError} + * @throws {AccessDeniedError} + * @throws {NotFoundError} + * @throws {ResourceSuspended} + * @throws {RateLimitError} + * @throws {UnknownError} + * @throws {ServiceError} + * @returns {Promise>} + */ + getInstitutions: async (country) => { + let response; + try { + response = await client.getInstitutions(country); + } catch (error) { + handleGoCardlessError(error); + } + + return response; + }, + + /** + * Retrieve details about a specific Institution + * @param institutionId + * @throws {InvalidInputDataError} + * @throws {InvalidGoCardlessTokenError} + * @throws {AccessDeniedError} + * @throws {NotFoundError} + * @throws {ResourceSuspended} + * @throws {RateLimitError} + * @throws {UnknownError} + * @throws {ServiceError} + * @returns {Promise} + */ + getInstitution: async (institutionId) => { + let response; + try { + response = await client.getInstitutionById(institutionId); + } catch (error) { + handleGoCardlessError(error); + } + + return response; + }, + + /** + * Extends provided accounts about details of their institution + * @param {{accounts: Array, institutions: Array}} params + * @returns {Promise>} + */ + extendAccountsAboutInstitutions: async ({ accounts, institutions }) => { + const institutionsById = institutions.reduce((acc, institution) => { + acc[institution.id] = institution; + return acc; + }, {}); + + return accounts.map((account) => { + const institution = institutionsById[account.institution_id] || null; + return { + ...account, + institution, + }; + }); + }, + + /** + * Returns account transaction in provided dates + * @param {import('../gocardless.types.js').GetTransactionsParams} params + * @throws {InvalidInputDataError} + * @throws {InvalidGoCardlessTokenError} + * @throws {AccessDeniedError} + * @throws {NotFoundError} + * @throws {ResourceSuspended} + * @throws {RateLimitError} + * @throws {UnknownError} + * @throws {ServiceError} + * @returns {Promise} + */ + getTransactions: async ({ institutionId, accountId, startDate, endDate }) => { + let response; + try { + response = await client.getTransactions({ + accountId, + dateFrom: startDate, + dateTo: endDate, + }); + } catch (error) { + handleGoCardlessError(error); + } + + const bank = BankFactory(institutionId); + response.transactions.booked = response.transactions.booked + .map((transaction) => bank.normalizeTransaction(transaction, true)) + .filter((transaction) => transaction); + response.transactions.pending = response.transactions.pending + .map((transaction) => bank.normalizeTransaction(transaction, false)) + .filter((transaction) => transaction); + + return response; + }, + + /** + * Returns account available balances + * @param accountId + * @throws {InvalidInputDataError} + * @throws {InvalidGoCardlessTokenError} + * @throws {AccessDeniedError} + * @throws {NotFoundError} + * @throws {ResourceSuspended} + * @throws {RateLimitError} + * @throws {UnknownError} + * @throws {ServiceError} + * @returns {Promise} + */ + getBalances: async (accountId) => { + let response; + try { + response = await client.getBalances(accountId); + } catch (error) { + handleGoCardlessError(error); + } + + return response; + }, +}; + +/** + * All executions of goCardlessClient should be here for testing purposes, + * as the nordigen-node library is not written in a way that is conducive to testing. + * In that way we can mock the `client` const instead of nordigen library + */ +export const client = { + getBalances: async (accountId) => + await getGocardlessClient().account(accountId).getBalances(), + getTransactions: async ({ accountId, dateFrom, dateTo }) => + await getGocardlessClient().account(accountId).getTransactions({ + dateFrom, + dateTo, + country: undefined, + }), + getInstitutions: async (country) => + await getGocardlessClient().institution.getInstitutions({ country }), + getInstitutionById: async (institutionId) => + await getGocardlessClient().institution.getInstitutionById(institutionId), + getDetails: async (accountId) => + await getGocardlessClient().account(accountId).getDetails(), + getMetadata: async (accountId) => + await getGocardlessClient().account(accountId).getMetadata(), + getRequisitionById: async (requisitionId) => + await getGocardlessClient().requisition.getRequisitionById(requisitionId), + deleteRequisition: async (requisitionId) => + await getGocardlessClient().requisition.deleteRequisition(requisitionId), + initSession: async ({ + redirectUrl, + institutionId, + referenceId, + accessValidForDays, + maxHistoricalDays, + userLanguage, + ssn, + redirectImmediate, + accountSelection, + }) => + await getGocardlessClient().initSession({ + redirectUrl, + institutionId, + referenceId, + accessValidForDays, + maxHistoricalDays, + userLanguage, + ssn, + redirectImmediate, + accountSelection, + }), + generateToken: async () => await getGocardlessClient().generateToken(), + exchangeToken: async ({ refreshToken }) => + await getGocardlessClient().exchangeToken({ refreshToken }), +}; diff --git a/packages/sync-server/src/app-gocardless/services/tests/fixtures.js b/packages/sync-server/src/app-gocardless/services/tests/fixtures.js new file mode 100644 index 00000000000..5e373e5a34d --- /dev/null +++ b/packages/sync-server/src/app-gocardless/services/tests/fixtures.js @@ -0,0 +1,180 @@ +/** @type {{balances: import('../../gocardless-node.types.js').Balance[]}} */ +export const mockedBalances = { + balances: [ + { + balanceAmount: { + amount: '657.49', + currency: 'string', + }, + balanceType: 'interimAvailable', + referenceDate: '2021-11-22', + }, + { + balanceAmount: { + amount: '185.67', + currency: 'string', + }, + balanceType: 'interimAvailable', + referenceDate: '2021-11-19', + }, + ], +}; + +/** @type {{transactions: import('../../gocardless-node.types.js').Transactions}} */ +export const mockTransactions = { + transactions: { + booked: [ + { + transactionId: 'string', + debtorName: 'string', + debtorAccount: { + iban: 'string', + }, + transactionAmount: { + currency: 'EUR', + amount: '328.18', + }, + bankTransactionCode: 'string', + bookingDate: 'date', + valueDate: 'date', + }, + { + transactionId: 'string', + transactionAmount: { + currency: 'EUR', + amount: '947.26', + }, + bankTransactionCode: 'string', + bookingDate: 'date', + valueDate: 'date', + }, + ], + pending: [ + { + transactionAmount: { + currency: 'EUR', + amount: '947.26', + }, + valueDate: 'date', + }, + ], + }, +}; + +export const mockUnknownError = { + summary: "Couldn't update account balances", + detail: 'Request to Institution returned an error', + type: 'UnknownRequestError', + status_code: 500, +}; + +/** @type {{account: import('../../gocardless-node.types.js').GoCardlessAccountDetails}} */ +export const mockAccountDetails = { + account: { + resourceId: 'PL00000000000000000987654321', + iban: 'PL00000000000000000987654321', + currency: 'PLN', + ownerName: 'JOHN EXAMPLE', + product: 'Savings Account for Individuals (Retail)', + bic: 'INGBPLPW', + ownerAddressUnstructured: ['EXAMPLE STREET 100/001', '00-000 EXAMPLE CITY'], + }, +}; + +/** @type {import('../../gocardless-node.types.js').GoCardlessAccountMetadata} */ +export const mockAccountMetaData = { + id: 'f0e49aa6-f6db-48fc-94ca-4a62372fadf4', + created: '2022-07-24T20:45:47.847062Z', + last_accessed: '2023-01-25T22:12:27.814618Z', + iban: 'PL00000000000000000987654321', + institution_id: 'SANDBOXFINANCE_SFIN0000', + status: 'READY', + owner_name: 'JOHN EXAMPLE', +}; + +/** @type {import('../../gocardless.types.js').DetailedAccount} */ +export const mockDetailedAccount = { + ...mockAccountDetails.account, + ...mockAccountMetaData, +}; + +/** @type {import('../../gocardless-node.types.js').Institution} */ +export const mockInstitution = { + id: 'N26_NTSBDEB1', + name: 'N26 Bank', + bic: 'NTSBDEB1', + transaction_total_days: '90', + countries: ['GB', 'NO', 'SE'], + logo: 'https://cdn.nordigen.com/ais/N26_SANDBOX_NTSBDEB1.png', +}; + +/** @type {import('../../gocardless-node.types.js').Requisition} */ +export const mockRequisition = { + id: '3fa85f64-5717-4562-b3fc-2c963f66afa6', + created: '2023-01-31T18:15:50.172Z', + redirect: 'string', + status: 'LN', + institution_id: 'string', + agreement: '3fa85f64-5717-4562-b3fc-2c963f66afa6', + reference: 'string', + accounts: ['f0e49aa6-f6db-48fc-94ca-4a62372fadf4'], + user_language: 'string', + link: 'https://ob.nordigen.com/psd2/start/3fa85f64-5717-4562-b3fc-2c963f66afa6/{$INSTITUTION_ID}', + ssn: 'string', + account_selection: false, + redirect_immediate: false, +}; + +export const mockDeleteRequisition = { + summary: 'Requisition deleted', + detail: + "Requisition '$REQUISITION_ID' deleted with all its End User Agreements", +}; + +export const mockCreateRequisition = { + id: '3fa85f64-5717-4562-b3fc-2c963f66afa6', + created: '2023-02-01T15:53:29.481Z', + redirect: 'string', + status: 'CR', + institution_id: 'string', + agreement: '3fa85f64-5717-4562-b3fc-2c963f66afa6', + reference: 'string', + accounts: [], + user_language: 'string', + link: 'https://ob.nordigen.com/psd2/start/3fa85f64-5717-4562-b3fc-2c963f66afa6/{$INSTITUTION_ID}', + ssn: 'string', + account_selection: false, + redirect_immediate: false, +}; + +/** @type {import('../../gocardless.types.js').DetailedAccount} */ +export const mockDetailedAccountExample1 = { + ...mockDetailedAccount, + name: 'account-example-one', +}; + +/** @type {import('../../gocardless.types.js').DetailedAccount} */ +export const mockDetailedAccountExample2 = { + ...mockDetailedAccount, + name: 'account-example-two', +}; + +/** @type {import('../../gocardless.types.js').DetailedAccountWithInstitution[]} */ +export const mockExtendAccountsAboutInstitutions = [ + { + ...mockDetailedAccountExample1, + institution: mockInstitution, + }, + { + ...mockDetailedAccountExample2, + institution: mockInstitution, + }, +]; + +export const mockRequisitionWithExampleAccounts = { + ...mockRequisition, + + accounts: [mockDetailedAccountExample1.id, mockDetailedAccountExample2.id], +}; + +export const mockTransactionAmount = { amount: '100', currency: 'EUR' }; diff --git a/packages/sync-server/src/app-gocardless/services/tests/gocardless-service.spec.js b/packages/sync-server/src/app-gocardless/services/tests/gocardless-service.spec.js new file mode 100644 index 00000000000..40758ac85d9 --- /dev/null +++ b/packages/sync-server/src/app-gocardless/services/tests/gocardless-service.spec.js @@ -0,0 +1,528 @@ +import { jest } from '@jest/globals'; +import { + AccessDeniedError, + AccountNotLinkedToRequisition, + GenericGoCardlessError, + InvalidInputDataError, + InvalidGoCardlessTokenError, + NotFoundError, + RateLimitError, + ResourceSuspended, + RequisitionNotLinked, + ServiceError, + UnknownError, +} from '../../errors.js'; + +import { + mockedBalances, + mockTransactions, + mockDetailedAccount, + mockInstitution, + mockAccountMetaData, + mockAccountDetails, + mockRequisition, + mockDeleteRequisition, + mockCreateRequisition, + mockRequisitionWithExampleAccounts, + mockDetailedAccountExample1, + mockDetailedAccountExample2, + mockExtendAccountsAboutInstitutions, +} from './fixtures.js'; + +import { + goCardlessService, + handleGoCardlessError, + client, +} from '../gocardless-service.js'; + +describe('goCardlessService', () => { + const accountId = mockAccountMetaData.id; + const requisitionId = mockRequisition.id; + + let getBalancesSpy; + let getTransactionsSpy; + let getDetailsSpy; + let getMetadataSpy; + let getInstitutionsSpy; + let getInstitutionSpy; + let getRequisitionsSpy; + let deleteRequisitionsSpy; + let createRequisitionSpy; + let setTokenSpy; + + beforeEach(() => { + getInstitutionsSpy = jest.spyOn(client, 'getInstitutions'); + getInstitutionSpy = jest.spyOn(client, 'getInstitutionById'); + getRequisitionsSpy = jest.spyOn(client, 'getRequisitionById'); + deleteRequisitionsSpy = jest.spyOn(client, 'deleteRequisition'); + createRequisitionSpy = jest.spyOn(client, 'initSession'); + getBalancesSpy = jest.spyOn(client, 'getBalances'); + getTransactionsSpy = jest.spyOn(client, 'getTransactions'); + getDetailsSpy = jest.spyOn(client, 'getDetails'); + getMetadataSpy = jest.spyOn(client, 'getMetadata'); + setTokenSpy = jest.spyOn(goCardlessService, 'setToken'); + }); + + afterEach(() => { + jest.resetAllMocks(); + }); + + describe('#getLinkedRequisition', () => { + it('returns requisition', async () => { + setTokenSpy.mockResolvedValue(); + + jest + .spyOn(goCardlessService, 'getRequisition') + .mockResolvedValue(mockRequisition); + + expect( + await goCardlessService.getLinkedRequisition(requisitionId), + ).toEqual(mockRequisition); + }); + + it('throws RequisitionNotLinked error if requisition status is different than LN', async () => { + setTokenSpy.mockResolvedValue(); + + jest + .spyOn(goCardlessService, 'getRequisition') + .mockResolvedValue({ ...mockRequisition, status: 'ER' }); + + await expect(() => + goCardlessService.getLinkedRequisition(requisitionId), + ).rejects.toThrow(RequisitionNotLinked); + }); + }); + + describe('#getRequisitionWithAccounts', () => { + it('returns combined data', async () => { + jest + .spyOn(goCardlessService, 'getRequisition') + .mockResolvedValue(mockRequisitionWithExampleAccounts); + jest + .spyOn(goCardlessService, 'getDetailedAccount') + .mockResolvedValueOnce(mockDetailedAccountExample1); + jest + .spyOn(goCardlessService, 'getDetailedAccount') + .mockResolvedValueOnce(mockDetailedAccountExample2); + jest + .spyOn(goCardlessService, 'getInstitution') + .mockResolvedValue(mockInstitution); + jest + .spyOn(goCardlessService, 'extendAccountsAboutInstitutions') + .mockResolvedValue([ + { + ...mockExtendAccountsAboutInstitutions[0], + institution_id: 'NEWONE', + }, + { + ...mockExtendAccountsAboutInstitutions[1], + institution_id: 'NEWONE', + }, + ]); + + const response = await goCardlessService.getRequisitionWithAccounts( + mockRequisitionWithExampleAccounts.id, + ); + + expect(response.accounts.length).toEqual(2); + expect(response.accounts).toMatchObject( + expect.arrayContaining([ + expect.objectContaining({ + account_id: mockDetailedAccountExample1.id, + institution: mockInstitution, + official_name: expect.stringContaining('integration-'), // It comes from IntegrationBank + }), + expect.objectContaining({ + account_id: mockDetailedAccountExample2.id, + institution: mockInstitution, + official_name: expect.stringContaining('integration-'), // It comes from IntegrationBank + }), + ]), + ); + expect(response.requisition).toEqual(mockRequisitionWithExampleAccounts); + }); + }); + + describe('#getTransactionsWithBalance', () => { + const requisitionId = mockRequisition.id; + it('returns transaction with starting balance', async () => { + jest + .spyOn(goCardlessService, 'getLinkedRequisition') + .mockResolvedValue(mockRequisition); + jest + .spyOn(goCardlessService, 'getAccountMetadata') + .mockResolvedValue(mockAccountMetaData); + jest + .spyOn(goCardlessService, 'getTransactions') + .mockResolvedValue(mockTransactions); + jest + .spyOn(goCardlessService, 'getBalances') + .mockResolvedValue(mockedBalances); + + expect( + await goCardlessService.getTransactionsWithBalance( + requisitionId, + accountId, + undefined, + undefined, + ), + ).toEqual( + expect.objectContaining({ + balances: mockedBalances.balances, + institutionId: mockRequisition.institution_id, + startingBalance: expect.any(Number), + transactions: { + all: expect.arrayContaining([ + expect.objectContaining({ + bookingDate: expect.any(String), + transactionAmount: { + amount: expect.any(String), + currency: 'EUR', + }, + transactionId: expect.any(String), + valueDate: expect.any(String), + }), + expect.objectContaining({ + transactionAmount: { + amount: expect.any(String), + currency: 'EUR', + }, + valueDate: expect.any(String), + }), + ]), + booked: expect.arrayContaining([ + expect.objectContaining({ + bookingDate: expect.any(String), + transactionAmount: { + amount: expect.any(String), + currency: 'EUR', + }, + transactionId: expect.any(String), + valueDate: expect.any(String), + }), + ]), + pending: expect.arrayContaining([ + expect.objectContaining({ + transactionAmount: { + amount: expect.any(String), + currency: 'EUR', + }, + valueDate: expect.any(String), + }), + ]), + }, + }), + ); + }); + + it('throws AccountNotLinkedToRequisition error if requisition accounts not includes requested account', async () => { + jest + .spyOn(goCardlessService, 'getLinkedRequisition') + .mockResolvedValue(mockRequisition); + + await expect(() => + goCardlessService.getTransactionsWithBalance({ + requisitionId, + accountId: 'some-unknown-account-id', + startDate: undefined, + endDate: undefined, + }), + ).rejects.toThrow(AccountNotLinkedToRequisition); + }); + }); + + describe('#createRequisition', () => { + const institutionId = 'some-institution-id'; + const params = { + host: 'https://exemple.com', + institutionId, + accessValidForDays: 90, + }; + + it('calls goCardlessClient and delete requisition', async () => { + setTokenSpy.mockResolvedValue(); + getInstitutionSpy.mockResolvedValue(mockInstitution); + + createRequisitionSpy.mockResolvedValue(mockCreateRequisition); + + expect(await goCardlessService.createRequisition(params)).toEqual({ + link: expect.any(String), + requisitionId: expect.any(String), + }); + + expect(createRequisitionSpy).toBeCalledTimes(1); + }); + }); + + describe('#deleteRequisition', () => { + const requisitionId = 'some-requisition-id'; + + it('calls goCardlessClient and delete requisition', async () => { + setTokenSpy.mockResolvedValue(); + + getRequisitionsSpy.mockResolvedValue(mockRequisition); + deleteRequisitionsSpy.mockResolvedValue(mockDeleteRequisition); + + expect(await goCardlessService.deleteRequisition(requisitionId)).toEqual( + mockDeleteRequisition, + ); + + expect(getRequisitionsSpy).toBeCalledTimes(1); + expect(deleteRequisitionsSpy).toBeCalledTimes(1); + }); + }); + + describe('#getRequisition', () => { + const requisitionId = 'some-requisition-id'; + + it('calls goCardlessClient and fetch requisition', async () => { + setTokenSpy.mockResolvedValue(); + getRequisitionsSpy.mockResolvedValue(mockRequisition); + + expect(await goCardlessService.getRequisition(requisitionId)).toEqual( + mockRequisition, + ); + + expect(setTokenSpy).toBeCalledTimes(1); + expect(getRequisitionsSpy).toBeCalledTimes(1); + }); + }); + + describe('#getDetailedAccount', () => { + it('returns merged object', async () => { + getDetailsSpy.mockResolvedValue(mockAccountDetails); + getMetadataSpy.mockResolvedValue(mockAccountMetaData); + + expect(await goCardlessService.getDetailedAccount(accountId)).toEqual({ + ...mockAccountMetaData, + ...mockAccountDetails.account, + }); + expect(getDetailsSpy).toBeCalledTimes(1); + expect(getMetadataSpy).toBeCalledTimes(1); + }); + }); + + describe('#getInstitutions', () => { + const country = 'IE'; + it('calls goCardlessClient and fetch institution details', async () => { + getInstitutionsSpy.mockResolvedValue([mockInstitution]); + + expect(await goCardlessService.getInstitutions({ country })).toEqual([ + mockInstitution, + ]); + expect(getInstitutionsSpy).toBeCalledTimes(1); + }); + }); + + describe('#getInstitution', () => { + const institutionId = 'fake-institution-id'; + it('calls goCardlessClient and fetch institution details', async () => { + getInstitutionSpy.mockResolvedValue(mockInstitution); + + expect(await goCardlessService.getInstitution(institutionId)).toEqual( + mockInstitution, + ); + expect(getInstitutionSpy).toBeCalledTimes(1); + }); + }); + + describe('#extendAccountsAboutInstitutions', () => { + it('extends accounts with the corresponding institution', async () => { + const institutionA = { ...mockInstitution, id: 'INSTITUTION_A' }; + const institutionB = { ...mockInstitution, id: 'INSTITUTION_B' }; + const accountAA = { + ...mockDetailedAccount, + id: 'AA', + institution_id: 'INSTITUTION_A', + }; + const accountBB = { + ...mockDetailedAccount, + id: 'BB', + institution_id: 'INSTITUTION_B', + }; + + const accounts = [accountAA, accountBB]; + const institutions = [institutionA, institutionB]; + + const expected = [ + { + ...accountAA, + institution: institutionA, + }, + { + ...accountBB, + institution: institutionB, + }, + ]; + + const result = await goCardlessService.extendAccountsAboutInstitutions({ + accounts, + institutions, + }); + + expect(result).toEqual(expected); + }); + + it('returns accounts with missing institutions as null', async () => { + const accountAA = { + ...mockDetailedAccount, + id: 'AA', + institution_id: 'INSTITUTION_A', + }; + const accountBB = { + ...mockDetailedAccount, + id: 'BB', + institution_id: 'INSTITUTION_B', + }; + + const accounts = [accountAA, accountBB]; + + const institutionA = { ...mockInstitution, id: 'INSTITUTION_A' }; + const institutions = [institutionA]; + + const expected = [ + { + ...accountAA, + institution: institutionA, + }, + { + ...accountBB, + institution: null, + }, + ]; + + const result = await goCardlessService.extendAccountsAboutInstitutions({ + accounts, + institutions, + }); + + expect(result).toEqual(expected); + }); + }); + + describe('#getTransactions', () => { + it('calls goCardlessClient and fetch transactions for provided accountId', async () => { + getTransactionsSpy.mockResolvedValue(mockTransactions); + + expect( + await goCardlessService.getTransactions({ + institutionId: 'SANDBOXFINANCE_SFIN0000', + accountId, + startDate: '', + endDate: '', + }), + ).toMatchInlineSnapshot(` + { + "transactions": { + "booked": [ + { + "bankTransactionCode": "string", + "bookingDate": "date", + "date": "date", + "debtorAccount": { + "iban": "string", + }, + "debtorName": "string", + "payeeName": "String (stri XXX ring)", + "transactionAmount": { + "amount": "328.18", + "currency": "EUR", + }, + "transactionId": "string", + "valueDate": "date", + }, + { + "bankTransactionCode": "string", + "bookingDate": "date", + "date": "date", + "payeeName": "", + "transactionAmount": { + "amount": "947.26", + "currency": "EUR", + }, + "transactionId": "string", + "valueDate": "date", + }, + ], + "pending": [ + { + "date": "date", + "payeeName": "", + "transactionAmount": { + "amount": "947.26", + "currency": "EUR", + }, + "valueDate": "date", + }, + ], + }, + } + `); + expect(getTransactionsSpy).toBeCalledTimes(1); + }); + }); + + describe('#getBalances', () => { + it('calls goCardlessClient and fetch balances for provided accountId', async () => { + getBalancesSpy.mockResolvedValue(mockedBalances); + + expect(await goCardlessService.getBalances(accountId)).toEqual( + mockedBalances, + ); + expect(getBalancesSpy).toBeCalledTimes(1); + }); + }); +}); + +describe('#handleGoCardlessError', () => { + it('throws InvalidInputDataError for status code 400', () => { + const response = { response: { status: 400 } }; + expect(() => handleGoCardlessError(response)).toThrow( + InvalidInputDataError, + ); + }); + + it('throws InvalidGoCardlessTokenError for status code 401', () => { + const response = { response: { status: 401 } }; + expect(() => handleGoCardlessError(response)).toThrow( + InvalidGoCardlessTokenError, + ); + }); + + it('throws AccessDeniedError for status code 403', () => { + const response = { response: { status: 403 } }; + expect(() => handleGoCardlessError(response)).toThrow(AccessDeniedError); + }); + + it('throws NotFoundError for status code 404', () => { + const response = { response: { status: 404 } }; + expect(() => handleGoCardlessError(response)).toThrow(NotFoundError); + }); + + it('throws ResourceSuspended for status code 409', () => { + const response = { response: { status: 409 } }; + expect(() => handleGoCardlessError(response)).toThrow(ResourceSuspended); + }); + + it('throws RateLimitError for status code 429', () => { + const response = { response: { status: 429 } }; + expect(() => handleGoCardlessError(response)).toThrow(RateLimitError); + }); + + it('throws UnknownError for status code 500', () => { + const response = { response: { status: 500 } }; + expect(() => handleGoCardlessError(response)).toThrow(UnknownError); + }); + + it('throws ServiceError for status code 503', () => { + const response = { response: { status: 503 } }; + expect(() => handleGoCardlessError(response)).toThrow(ServiceError); + }); + + it('throws a generic error when the status code is not recognised', () => { + const response = { response: { status: 0 } }; + expect(() => handleGoCardlessError(response)).toThrow( + GenericGoCardlessError, + ); + }); +}); diff --git a/packages/sync-server/src/app-gocardless/tests/bank-factory.spec.js b/packages/sync-server/src/app-gocardless/tests/bank-factory.spec.js new file mode 100644 index 00000000000..61dec1ddbf1 --- /dev/null +++ b/packages/sync-server/src/app-gocardless/tests/bank-factory.spec.js @@ -0,0 +1,21 @@ +import BankFactory from '../bank-factory.js'; +import { banks } from '../bank-factory.js'; +import IntegrationBank from '../banks/integration-bank.js'; + +describe('BankFactory', () => { + it.each(banks.flatMap((bank) => bank.institutionIds))( + `should return same institutionId`, + (institutionId) => { + const result = BankFactory(institutionId); + + expect(result.institutionIds).toContain(institutionId); + }, + ); + + it('should return IntegrationBank when institutionId is not found', () => { + const institutionId = IntegrationBank.institutionIds[0]; + const result = BankFactory('fake-id-not-found'); + + expect(result.institutionIds).toContain(institutionId); + }); +}); diff --git a/packages/sync-server/src/app-gocardless/tests/utils.spec.js b/packages/sync-server/src/app-gocardless/tests/utils.spec.js new file mode 100644 index 00000000000..352525b9da8 --- /dev/null +++ b/packages/sync-server/src/app-gocardless/tests/utils.spec.js @@ -0,0 +1,37 @@ +import { mockTransactionAmount } from '../services/tests/fixtures.js'; +import { sortByBookingDateOrValueDate } from '../utils.js'; + +describe('utils', () => { + describe('#sortByBookingDate', () => { + it('sorts transactions by bookingDate field from newest to oldest', () => { + const transactions = [ + { + bookingDate: '2023-01-01', + transactionAmount: mockTransactionAmount, + }, + { + bookingDate: '2023-01-20', + transactionAmount: mockTransactionAmount, + }, + { + bookingDate: '2023-01-10', + transactionAmount: mockTransactionAmount, + }, + ]; + expect(sortByBookingDateOrValueDate(transactions)).toEqual([ + { + bookingDate: '2023-01-20', + transactionAmount: mockTransactionAmount, + }, + { + bookingDate: '2023-01-10', + transactionAmount: mockTransactionAmount, + }, + { + bookingDate: '2023-01-01', + transactionAmount: mockTransactionAmount, + }, + ]); + }); + }); +}); diff --git a/packages/sync-server/src/app-gocardless/util/handle-error.js b/packages/sync-server/src/app-gocardless/util/handle-error.js new file mode 100644 index 00000000000..7aa4519273e --- /dev/null +++ b/packages/sync-server/src/app-gocardless/util/handle-error.js @@ -0,0 +1,16 @@ +import { inspect } from 'util'; + +export function handleError(func) { + return (req, res) => { + func(req, res).catch((err) => { + console.log('Error', req.originalUrl, inspect(err, { depth: null })); + res.send({ + status: 'ok', + data: { + error_code: 'INTERNAL_ERROR', + error_type: err.message ? err.message : 'internal-error', + }, + }); + }); + }; +} diff --git a/packages/sync-server/src/app-gocardless/utils.js b/packages/sync-server/src/app-gocardless/utils.js new file mode 100644 index 00000000000..7a3987b7756 --- /dev/null +++ b/packages/sync-server/src/app-gocardless/utils.js @@ -0,0 +1,16 @@ +export const printIban = (account) => { + if (account.iban) { + return '(XXX ' + account.iban.slice(-4) + ')'; + } else { + return ''; + } +}; + +export const sortByBookingDateOrValueDate = (transactions = []) => + transactions.sort( + (a, b) => + +new Date(b.bookingDate || b.valueDate) - + +new Date(a.bookingDate || a.valueDate), + ); + +export const amountToInteger = (n) => Math.round(n * 100); diff --git a/packages/sync-server/src/app-secrets.js b/packages/sync-server/src/app-secrets.js new file mode 100644 index 00000000000..3f842d8cb72 --- /dev/null +++ b/packages/sync-server/src/app-secrets.js @@ -0,0 +1,32 @@ +import express from 'express'; +import { secretsService } from './services/secrets-service.js'; +import { + requestLoggerMiddleware, + validateUserMiddleware, +} from './util/middlewares.js'; + +const app = express(); + +export { app as handlers }; +app.use(express.json()); +app.use(requestLoggerMiddleware); + +app.use(validateUserMiddleware); + +app.post('/', async (req, res) => { + const { name, value } = req.body; + + secretsService.set(name, value); + + res.status(200).send({ status: 'ok' }); +}); + +app.get('/:name', async (req, res) => { + const name = req.params.name; + const keyExists = secretsService.exists(name); + if (keyExists) { + res.sendStatus(204); + } else { + res.status(404).send('key not found'); + } +}); diff --git a/packages/sync-server/src/app-simplefin/app-simplefin.js b/packages/sync-server/src/app-simplefin/app-simplefin.js new file mode 100644 index 00000000000..7d27c1bd7d2 --- /dev/null +++ b/packages/sync-server/src/app-simplefin/app-simplefin.js @@ -0,0 +1,371 @@ +import express from 'express'; +import https from 'https'; +import { SecretName, secretsService } from '../services/secrets-service.js'; +import { handleError } from '../app-gocardless/util/handle-error.js'; +import { requestLoggerMiddleware } from '../util/middlewares.js'; + +const app = express(); +export { app as handlers }; +app.use(express.json()); +app.use(requestLoggerMiddleware); + +app.post( + '/status', + handleError(async (req, res) => { + const token = secretsService.get(SecretName.simplefin_token); + const configured = token != null && token !== 'Forbidden'; + + res.send({ + status: 'ok', + data: { + configured: configured, + }, + }); + }), +); + +app.post( + '/accounts', + handleError(async (req, res) => { + let accessKey = secretsService.get(SecretName.simplefin_accessKey); + + try { + if (accessKey == null || accessKey === 'Forbidden') { + const token = secretsService.get(SecretName.simplefin_token); + if (token == null || token === 'Forbidden') { + throw new Error('No token'); + } else { + accessKey = await getAccessKey(token); + secretsService.set(SecretName.simplefin_accessKey, accessKey); + if (accessKey == null || accessKey === 'Forbidden') { + throw new Error('No access key'); + } + } + } + } catch (error) { + invalidToken(res); + return; + } + + const now = new Date(); + const startDate = new Date(now.getFullYear(), now.getMonth(), 1); + const endDate = new Date(now.getFullYear(), now.getMonth() + 1, 1); + + try { + const accounts = await getAccounts(accessKey, startDate, endDate); + + res.send({ + status: 'ok', + data: { + accounts: accounts.accounts, + }, + }); + } catch (e) { + serverDown(e, res); + return; + } + }), +); + +app.post( + '/transactions', + handleError(async (req, res) => { + const { accountId, startDate } = req.body; + + const accessKey = secretsService.get(SecretName.simplefin_accessKey); + + if (accessKey == null || accessKey === 'Forbidden') { + invalidToken(res); + return; + } + + if (Array.isArray(accountId) != Array.isArray(startDate)) { + console.log(accountId, startDate); + throw new Error( + 'accountId and startDate must either both be arrays or both be strings', + ); + } + if (Array.isArray(accountId) && accountId.length !== startDate.length) { + console.log(accountId, startDate); + throw new Error('accountId and startDate arrays must be the same length'); + } + + const earliestStartDate = Array.isArray(startDate) + ? startDate.reduce((a, b) => (a < b ? a : b)) + : startDate; + let results; + try { + results = await getTransactions(accessKey, new Date(earliestStartDate)); + } catch (e) { + if (e.message === 'Forbidden') { + invalidToken(res); + } else { + serverDown(e, res); + } + return; + } + + let response = {}; + if (Array.isArray(accountId)) { + for (let i = 0; i < accountId.length; i++) { + const id = accountId[i]; + response[id] = getAccountResponse(results, id, new Date(startDate[i])); + } + } else { + response = getAccountResponse(results, accountId, new Date(startDate)); + } + + if (results.hasError) { + res.send({ + status: 'ok', + data: !Array.isArray(accountId) + ? results.errors[accountId][0] + : { + ...response, + errors: results.errors, + }, + }); + return; + } + + res.send({ + status: 'ok', + data: response, + }); + }), +); + +function logAccountError(results, accountId, data) { + const errors = results.errors[accountId] || []; + errors.push(data); + results.errors[accountId] = errors; + results.hasError = true; +} + +function getAccountResponse(results, accountId, startDate) { + const account = + !results?.accounts || results.accounts.find((a) => a.id === accountId); + if (!account) { + console.log( + `The account "${accountId}" was not found. Here were the accounts returned:`, + ); + if (results?.accounts) + results.accounts.forEach((a) => console.log(`${a.id} - ${a.org.name}`)); + logAccountError(results, accountId, { + error_type: 'ACCOUNT_MISSING', + error_code: 'ACCOUNT_MISSING', + reason: `The account "${accountId}" was not found. Try unlinking and relinking the account.`, + }); + return; + } + + const needsAttention = results.sferrors.find( + (e) => e === `Connection to ${account.org.name} may need attention`, + ); + if (needsAttention) { + logAccountError(results, accountId, { + error_type: 'ACCOUNT_NEEDS_ATTENTION', + error_code: 'ACCOUNT_NEEDS_ATTENTION', + reason: + 'The account needs your attention at SimpleFIN.', + }); + } + + const startingBalance = parseInt(account.balance.replace('.', '')); + const date = getDate(new Date(account['balance-date'] * 1000)); + + const balances = [ + { + balanceAmount: { + amount: account.balance, + currency: account.currency, + }, + balanceType: 'expected', + referenceDate: date, + }, + { + balanceAmount: { + amount: account.balance, + currency: account.currency, + }, + balanceType: 'interimAvailable', + referenceDate: date, + }, + ]; + + const all = []; + const booked = []; + const pending = []; + + for (const trans of account.transactions) { + const newTrans = {}; + + let dateToUse = 0; + + if (trans.posted == 0) { + newTrans.booked = false; + dateToUse = trans.transacted_at; + } else { + newTrans.booked = true; + dateToUse = trans.posted; + } + + newTrans.bookingDate = getDate(new Date(dateToUse * 1000)); + if (newTrans.bookingDate < startDate) { + continue; + } + + newTrans.date = newTrans.bookingDate; + newTrans.payeeName = trans.payee; + newTrans.remittanceInformationUnstructured = trans.description; + newTrans.transactionAmount = { amount: trans.amount, currency: 'USD' }; + newTrans.transactionId = trans.id; + newTrans.valueDate = newTrans.bookingDate; + + if (newTrans.booked) { + booked.push(newTrans); + } else { + pending.push(newTrans); + } + all.push(newTrans); + } + + return { balances, startingBalance, transactions: { all, booked, pending } }; +} + +function invalidToken(res) { + res.send({ + status: 'ok', + data: { + error_type: 'INVALID_ACCESS_TOKEN', + error_code: 'INVALID_ACCESS_TOKEN', + status: 'rejected', + reason: + 'Invalid SimpleFIN access token. Reset the token and re-link any broken accounts.', + }, + }); +} + +function serverDown(e, res) { + console.log(e); + res.send({ + status: 'ok', + data: { + error_type: 'SERVER_DOWN', + error_code: 'SERVER_DOWN', + status: 'rejected', + reason: 'There was an error communicating with SimpleFIN.', + }, + }); +} + +function parseAccessKey(accessKey) { + let scheme = null; + let rest = null; + let auth = null; + let username = null; + let password = null; + let baseUrl = null; + [scheme, rest] = accessKey.split('//'); + [auth, rest] = rest.split('@'); + [username, password] = auth.split(':'); + baseUrl = `${scheme}//${rest}`; + return { + baseUrl: baseUrl, + username: username, + password: password, + }; +} + +async function getAccessKey(base64Token) { + const token = Buffer.from(base64Token, 'base64').toString(); + const options = { + method: 'POST', + port: 443, + headers: { 'Content-Length': 0 }, + }; + return new Promise((resolve, reject) => { + const req = https.request(new URL(token), options, (res) => { + res.on('data', (d) => { + resolve(d.toString()); + }); + }); + req.on('error', (e) => { + reject(e); + }); + req.end(); + }); +} + +async function getTransactions(accessKey, startDate, endDate) { + const now = new Date(); + startDate = startDate || new Date(now.getFullYear(), now.getMonth(), 1); + endDate = endDate || new Date(now.getFullYear(), now.getMonth() + 1, 1); + console.log(`${getDate(startDate)} - ${getDate(endDate)}`); + return await getAccounts(accessKey, startDate, endDate); +} + +function getDate(date) { + return date.toISOString().split('T')[0]; +} + +function normalizeDate(date) { + return (date.valueOf() - date.getTimezoneOffset() * 60 * 1000) / 1000; +} + +async function getAccounts(accessKey, startDate, endDate) { + const sfin = parseAccessKey(accessKey); + const options = { + headers: { + Authorization: `Basic ${Buffer.from( + `${sfin.username}:${sfin.password}`, + ).toString('base64')}`, + }, + }; + const params = []; + let queryString = ''; + if (startDate) { + params.push(`start-date=${normalizeDate(startDate)}`); + } + if (endDate) { + params.push(`end-date=${normalizeDate(endDate)}`); + } + + params.push(`pending=1`); + + if (params.length > 0) { + queryString += '?' + params.join('&'); + } + return new Promise((resolve, reject) => { + const req = https.request( + new URL(`${sfin.baseUrl}/accounts${queryString}`), + options, + (res) => { + let data = ''; + res.on('data', (d) => { + data += d; + }); + res.on('end', () => { + if (res.statusCode === 403) { + reject(new Error('Forbidden')); + } else { + try { + const results = JSON.parse(data); + results.sferrors = results.errors; + results.hasError = false; + results.errors = {}; + resolve(results); + } catch (e) { + console.log(`Error parsing JSON response: ${data}`); + reject(e); + } + } + }); + }, + ); + req.on('error', (e) => { + reject(e); + }); + req.end(); + }); +} diff --git a/packages/sync-server/src/app-sync.js b/packages/sync-server/src/app-sync.js new file mode 100644 index 00000000000..447f9d96611 --- /dev/null +++ b/packages/sync-server/src/app-sync.js @@ -0,0 +1,394 @@ +import fs from 'node:fs/promises'; +import { Buffer } from 'node:buffer'; +import express from 'express'; +import * as uuid from 'uuid'; +import { + errorMiddleware, + requestLoggerMiddleware, + validateUserMiddleware, +} from './util/middlewares.js'; +import getAccountDb from './account-db.js'; +import { getPathForUserFile, getPathForGroupFile } from './util/paths.js'; + +import * as simpleSync from './sync-simple.js'; + +import { SyncProtoBuf } from '@actual-app/crdt'; + +const app = express(); +app.use(errorMiddleware); +app.use(requestLoggerMiddleware); +app.use(express.raw({ type: 'application/actual-sync' })); +app.use(express.raw({ type: 'application/encrypted-file' })); +app.use(express.json()); + +app.use(validateUserMiddleware); +export { app as handlers }; + +const OK_RESPONSE = { status: 'ok' }; + +// This is a version representing the internal format of sync +// messages. When this changes, all sync files need to be reset. We +// will check this version when syncing and notify the user if they +// need to reset. +const SYNC_FORMAT_VERSION = 2; + +app.post('/sync', async (req, res) => { + let requestPb; + try { + requestPb = SyncProtoBuf.SyncRequest.deserializeBinary(req.body); + } catch (e) { + console.log('Error parsing sync request', e); + res.status(500); + res.send({ status: 'error', reason: 'internal-error' }); + return; + } + + let accountDb = getAccountDb(); + let file_id = requestPb.getFileid() || null; + let group_id = requestPb.getGroupid() || null; + let key_id = requestPb.getKeyid() || null; + let since = requestPb.getSince() || null; + let messages = requestPb.getMessagesList(); + + if (!since) { + return res.status(422).send({ + details: 'since-required', + reason: 'unprocessable-entity', + status: 'error', + }); + } + + let currentFiles = accountDb.all( + 'SELECT group_id, encrypt_keyid, encrypt_meta, sync_version FROM files WHERE id = ?', + [file_id], + ); + + if (currentFiles.length === 0) { + res.status(400); + res.send('file-not-found'); + return; + } + + let currentFile = currentFiles[0]; + + if ( + currentFile.sync_version == null || + currentFile.sync_version < SYNC_FORMAT_VERSION + ) { + res.status(400); + res.send('file-old-version'); + return; + } + + // When resetting sync state, something went wrong. There is no + // group id and it's awaiting a file to be uploaded. + if (currentFile.group_id == null) { + res.status(400); + res.send('file-needs-upload'); + return; + } + + // Check to make sure the uploaded file is valid and has been + // encrypted with the same key it is registered with (this might + // be wrong if there was an error during the key creation + // process) + let uploadedKeyId = currentFile.encrypt_meta + ? JSON.parse(currentFile.encrypt_meta).keyId + : null; + if (uploadedKeyId !== currentFile.encrypt_keyid) { + res.status(400); + res.send('file-key-mismatch'); + return; + } + + // The changes being synced are part of an old group, which + // means the file has been reset. User needs to re-download. + if (group_id !== currentFile.group_id) { + res.status(400); + res.send('file-has-reset'); + return; + } + + // The data is encrypted with a different key which is + // unacceptable. We can't accept these changes. Reject them and + // tell the user that they need to generate the correct key + // (which necessitates a sync reset so they need to re-download). + if (key_id !== currentFile.encrypt_keyid) { + res.status(400); + res.send('file-has-new-key'); + return false; + } + + let { trie, newMessages } = simpleSync.sync(messages, since, group_id); + + // encode it back... + let responsePb = new SyncProtoBuf.SyncResponse(); + responsePb.setMerkle(JSON.stringify(trie)); + newMessages.forEach((msg) => responsePb.addMessages(msg)); + + res.set('Content-Type', 'application/actual-sync'); + res.set('X-ACTUAL-SYNC-METHOD', 'simple'); + res.send(Buffer.from(responsePb.serializeBinary())); +}); + +app.post('/user-get-key', (req, res) => { + let accountDb = getAccountDb(); + let { fileId } = req.body; + + let rows = accountDb.all( + 'SELECT encrypt_salt, encrypt_keyid, encrypt_test FROM files WHERE id = ?', + [fileId], + ); + if (rows.length === 0) { + res.status(400).send('file-not-found'); + return; + } + let { encrypt_salt, encrypt_keyid, encrypt_test } = rows[0]; + + res.send({ + status: 'ok', + data: { id: encrypt_keyid, salt: encrypt_salt, test: encrypt_test }, + }); +}); + +app.post('/user-create-key', (req, res) => { + let accountDb = getAccountDb(); + let { fileId, keyId, keySalt, testContent } = req.body; + + accountDb.mutate( + 'UPDATE files SET encrypt_salt = ?, encrypt_keyid = ?, encrypt_test = ? WHERE id = ?', + [keySalt, keyId, testContent, fileId], + ); + + res.send(OK_RESPONSE); +}); + +app.post('/reset-user-file', async (req, res) => { + let accountDb = getAccountDb(); + let { fileId } = req.body; + + let files = accountDb.all('SELECT group_id FROM files WHERE id = ?', [ + fileId, + ]); + if (files.length === 0) { + res.status(400).send('User or file not found'); + return; + } + let { group_id } = files[0]; + + accountDb.mutate('UPDATE files SET group_id = NULL WHERE id = ?', [fileId]); + + if (group_id) { + try { + await fs.unlink(getPathForGroupFile(group_id)); + } catch (e) { + console.log(`Unable to delete sync data for group "${group_id}"`); + } + } + + res.send(OK_RESPONSE); +}); + +app.post('/upload-user-file', async (req, res) => { + let accountDb = getAccountDb(); + if (typeof req.headers['x-actual-name'] !== 'string') { + // FIXME: Not sure how this cannot be a string when the header is + // set. + res.status(400).send('single x-actual-name is required'); + return; + } + + let name = decodeURIComponent(req.headers['x-actual-name']); + let fileId = req.headers['x-actual-file-id']; + + if (!fileId || typeof fileId !== 'string') { + res.status(400).send('fileId is required'); + return; + } + + let groupId = req.headers['x-actual-group-id'] || null; + let encryptMeta = req.headers['x-actual-encrypt-meta'] || null; + let syncFormatVersion = req.headers['x-actual-format'] || null; + + let keyId = + encryptMeta && typeof encryptMeta === 'string' + ? JSON.parse(encryptMeta).keyId + : null; + + let currentFiles = accountDb.all( + 'SELECT group_id, encrypt_keyid, encrypt_meta FROM files WHERE id = ?', + [fileId], + ); + if (currentFiles.length > 0) { + let currentFile = currentFiles[0]; + + // The uploading file is part of an old group, so reject + // it. All of its internal sync state is invalid because its + // old. The sync state has been reset, so user needs to + // either reset again or download from the current group. + if (groupId !== currentFile.group_id) { + res.status(400); + res.send('file-has-reset'); + return; + } + + // The key that the file is encrypted with is different than + // the current registered key. All data must always be + // encrypted with the registered key for consistency. Key + // changes always necessitate a sync reset, which means this + // upload is trying to overwrite another reset. That might + // be be fine, but since we definitely cannot accept a file + // encrypted with the wrong key, we bail and suggest the + // user download the latest file. + if (keyId !== currentFile.encrypt_keyid) { + res.status(400); + res.send('file-has-new-key'); + return; + } + } + + try { + await fs.writeFile(getPathForUserFile(fileId), req.body); + } catch (err) { + console.log('Error writing file', err); + res.status(500).send({ status: 'error' }); + return; + } + + let rows = accountDb.all('SELECT id FROM files WHERE id = ?', [fileId]); + if (rows.length === 0) { + // it's new + groupId = uuid.v4(); + accountDb.mutate( + 'INSERT INTO files (id, group_id, sync_version, name, encrypt_meta) VALUES (?, ?, ?, ?, ?)', + [fileId, groupId, syncFormatVersion, name, encryptMeta], + ); + res.send({ status: 'ok', groupId }); + } else { + if (!groupId) { + // sync state was reset, create new group + groupId = uuid.v4(); + accountDb.mutate('UPDATE files SET group_id = ? WHERE id = ?', [ + groupId, + fileId, + ]); + } + + // Regardless, update some properties + accountDb.mutate( + 'UPDATE files SET sync_version = ?, encrypt_meta = ?, name = ? WHERE id = ?', + [syncFormatVersion, encryptMeta, name, fileId], + ); + + res.send({ status: 'ok', groupId }); + } +}); + +app.get('/download-user-file', async (req, res) => { + let accountDb = getAccountDb(); + let fileId = req.headers['x-actual-file-id']; + if (typeof fileId !== 'string') { + // FIXME: Not sure how this cannot be a string when the header is + // set. + res.status(400).send('Single file ID is required'); + return; + } + + // Do some authentication + let rows = accountDb.all( + 'SELECT id FROM files WHERE id = ? AND deleted = FALSE', + [fileId], + ); + if (rows.length === 0) { + res.status(400).send('User or file not found'); + return; + } + + res.setHeader('Content-Disposition', `attachment;filename=${fileId}`); + res.sendFile(getPathForUserFile(fileId)); +}); + +app.post('/update-user-filename', (req, res) => { + let accountDb = getAccountDb(); + let { fileId, name } = req.body; + + // Do some authentication + let rows = accountDb.all( + 'SELECT id FROM files WHERE id = ? AND deleted = FALSE', + [fileId], + ); + if (rows.length === 0) { + res.status(400).send('file not found'); + return; + } + + accountDb.mutate('UPDATE files SET name = ? WHERE id = ?', [name, fileId]); + + res.send(OK_RESPONSE); +}); + +app.get('/list-user-files', (req, res) => { + let accountDb = getAccountDb(); + let rows = accountDb.all('SELECT * FROM files'); + + res.send({ + status: 'ok', + data: rows.map((row) => ({ + deleted: row.deleted, + fileId: row.id, + groupId: row.group_id, + name: row.name, + encryptKeyId: row.encrypt_keyid, + })), + }); +}); + +app.get('/get-user-file-info', (req, res) => { + let accountDb = getAccountDb(); + let fileId = req.headers['x-actual-file-id']; + + let rows = accountDb.all( + 'SELECT * FROM files WHERE id = ? AND deleted = FALSE', + [fileId], + ); + + if (rows.length === 0) { + res.status(400).send({ status: 'error', reason: 'file-not-found' }); + return; + } + + let row = rows[0]; + + res.send({ + status: 'ok', + data: { + deleted: row.deleted, + fileId: row.id, + groupId: row.group_id, + name: row.name, + encryptMeta: row.encrypt_meta ? JSON.parse(row.encrypt_meta) : null, + }, + }); +}); + +app.post('/delete-user-file', (req, res) => { + let accountDb = getAccountDb(); + let { fileId } = req.body; + + if (!fileId) { + return res.status(422).send({ + details: 'fileId-required', + reason: 'unprocessable-entity', + status: 'error', + }); + } + + let rows = accountDb.all('SELECT * FROM files WHERE id = ?', [fileId]); + + if (rows.length === 0) { + return res.status(400).send('file-not-found'); + } + + accountDb.mutate('UPDATE files SET deleted = TRUE WHERE id = ?', [fileId]); + res.send(OK_RESPONSE); +}); diff --git a/packages/sync-server/src/app-sync.test.js b/packages/sync-server/src/app-sync.test.js new file mode 100644 index 00000000000..4c3328abd54 --- /dev/null +++ b/packages/sync-server/src/app-sync.test.js @@ -0,0 +1,811 @@ +import fs from 'node:fs'; +import request from 'supertest'; +import { handlers as app } from './app-sync.js'; +import getAccountDb from './account-db.js'; +import { getPathForUserFile } from './util/paths.js'; +import { SyncProtoBuf } from '@actual-app/crdt'; +import crypto from 'node:crypto'; + +describe('/user-get-key', () => { + it('returns 401 if the user is not authenticated', async () => { + const res = await request(app).post('/user-get-key'); + + expect(res.statusCode).toEqual(401); + expect(res.body).toEqual({ + details: 'token-not-found', + reason: 'unauthorized', + status: 'error', + }); + }); + + it('returns encryption key details for a given fileId', async () => { + const fileId = crypto.randomBytes(16).toString('hex'); + const encrypt_salt = 'test-salt'; + const encrypt_keyid = 'test-key-id'; + const encrypt_test = 'test-encrypt-test'; + + getAccountDb().mutate( + 'INSERT INTO files (id, encrypt_salt, encrypt_keyid, encrypt_test) VALUES (?, ?, ?, ?)', + [fileId, encrypt_salt, encrypt_keyid, encrypt_test], + ); + + const res = await request(app) + .post('/user-get-key') + .set('x-actual-token', 'valid-token') + .send({ fileId }); + + expect(res.statusCode).toEqual(200); + expect(res.body).toEqual({ + status: 'ok', + data: { + id: encrypt_keyid, + salt: encrypt_salt, + test: encrypt_test, + }, + }); + }); + + it('returns 400 if the file is not found', async () => { + const res = await request(app) + .post('/user-get-key') + .set('x-actual-token', 'valid-token') + .send({ fileId: 'non-existent-file-id' }); + + expect(res.statusCode).toEqual(400); + expect(res.text).toBe('file-not-found'); + }); +}); + +describe('/user-create-key', () => { + it('returns 401 if the user is not authenticated', async () => { + const res = await request(app).post('/user-create-key'); + + expect(res.statusCode).toEqual(401); + expect(res.body).toEqual({ + details: 'token-not-found', + reason: 'unauthorized', + status: 'error', + }); + }); +}); + +describe('/reset-user-file', () => { + it('returns 401 if the user is not authenticated', async () => { + const res = await request(app).post('/reset-user-file'); + + expect(res.statusCode).toEqual(401); + expect(res.body).toEqual({ + details: 'token-not-found', + reason: 'unauthorized', + status: 'error', + }); + }); + + it('resets the user file and deletes the group file', async () => { + const fileId = crypto.randomBytes(16).toString('hex'); + const groupId = 'test-group-id'; + + // Use addMockFile to insert a mock file into the database + getAccountDb().mutate( + 'INSERT INTO files (id, group_id, deleted) VALUES (?, ?, FALSE)', + [fileId, groupId], + ); + + const res = await request(app) + .post('/reset-user-file') + .set('x-actual-token', 'valid-token') + .send({ fileId }); + + expect(res.statusCode).toEqual(200); + expect(res.body).toEqual({ status: 'ok' }); + + // Verify that the file is marked as deleted + const rows = getAccountDb().all('SELECT group_id FROM files WHERE id = ?', [ + fileId, + ]); + + expect(rows[0].group_id).toBeNull; + }); + + it('returns 400 if the file is not found', async () => { + const res = await request(app) + .post('/reset-user-file') + .set('x-actual-token', 'valid-token') + .send({ fileId: 'non-existent-file-id' }); + + expect(res.statusCode).toEqual(400); + expect(res.text).toBe('User or file not found'); + }); +}); + +describe('/upload-user-file', () => { + it('returns 401 if the user is not authenticated', async () => { + const res = await request(app).post('/upload-user-file'); + + expect(res.statusCode).toEqual(401); + expect(res.body).toEqual({ + details: 'token-not-found', + reason: 'unauthorized', + status: 'error', + }); + }); + + it('returns 400 if x-actual-name header is missing', async () => { + const res = await request(app) + .post('/upload-user-file') + .set('x-actual-token', 'valid-token') + .set('x-actual-file-id', 'test-file-id') + .send('file content'); + + expect(res.statusCode).toEqual(400); + expect(res.text).toBe('single x-actual-name is required'); + }); + + it('returns 400 if fileId is missing', async () => { + const content = Buffer.from('file content'); + const res = await request(app) + .post('/upload-user-file') + .set('Content-Type', 'application/encrypted-file') + .set('x-actual-token', 'valid-token') + .set('x-actual-name', 'test-file') + .send(content); + + expect(res.statusCode).toEqual(400); + expect(res.text).toBe('fileId is required'); + }); + + it('uploads a new file successfully', async () => { + const fileId = crypto.randomBytes(16).toString('hex'); + const fileName = 'test-file.txt'; + const fileContent = 'test file content'; + const fileContentBuffer = Buffer.from(fileContent); + const syncVersion = 2; + const encryptMeta = JSON.stringify({ keyId: 'key-id' }); + + // Verify that the file does not exist before upload + const rowsBefore = getAccountDb().all('SELECT * FROM files WHERE id = ?', [ + fileId, + ]); + + expect(rowsBefore.length).toBe(0); + + const res = await request(app) + .post('/upload-user-file') + .set('Content-Type', 'application/encrypted-file') + .set('x-actual-token', 'valid-token') + .set('x-actual-name', fileName) + .set('x-actual-file-id', fileId) + .set('x-actual-format', syncVersion.toString()) + .set('x-actual-encrypt-meta', encryptMeta) + .send(fileContentBuffer); + + expect(res.statusCode).toEqual(200); + expect(res.body).toEqual({ status: 'ok', groupId: expect.any(String) }); + + const receivedGroupid = res.body.groupId; + // Verify that the file exists in the accountDb + const rowsAfter = getAccountDb().all('SELECT * FROM files WHERE id = ?', [ + fileId, + ]); + expect(rowsAfter.length).toBe(1); + expect(rowsAfter[0].id).toEqual(fileId); + expect(rowsAfter[0].group_id).toEqual(receivedGroupid); + expect(rowsAfter[0].sync_version).toEqual(syncVersion); + expect(rowsAfter[0].name).toEqual(fileName); + expect(rowsAfter[0].encrypt_meta).toEqual(encryptMeta); + + // Verify that the file was written to the file system + const filePath = getPathForUserFile(fileId); + const writtenContent = await fs.promises.readFile(filePath, 'utf8'); + expect(writtenContent).toEqual(fileContent); + + // Clean up the file + await fs.promises.unlink(filePath); + }); + + it('uploads and updates an existing file successfully', async () => { + const fileId = crypto.randomBytes(16).toString('hex'); + const oldGroupId = null; //sync state was reset + const oldFileName = 'old-test-file.txt'; + const newFileName = 'new-test-file.txt'; + const oldFileContent = 'old file content'; + const newFileContent = 'new file content'; + const oldSyncVersion = 1; + const newSyncVersion = 2; + const oldKeyId = 'old-key-id'; + const oldEncryptMeta = JSON.stringify({ keyId: oldKeyId }); + const newEncryptMeta = JSON.stringify({ + keyId: oldKeyId, + sentinelValue: 1, + }); //keep the same key, but change other things + + // Create the old file version + getAccountDb().mutate( + 'INSERT INTO files (id, group_id, sync_version, name, encrypt_meta, encrypt_keyid) VALUES (?, ?, ?, ?, ?, ?)', + [ + fileId, + oldGroupId, + oldSyncVersion, + oldFileName, + oldEncryptMeta, + oldKeyId, + ], + ); + + await fs.writeFile(getPathForUserFile(fileId), oldFileContent, (err) => { + if (err) throw err; + }); + + const res = await request(app) + .post('/upload-user-file') + .set('Content-Type', 'application/encrypted-file') + .set('x-actual-token', 'valid-token') + .set('x-actual-file-id', fileId) + .set('x-actual-name', newFileName) + .set('x-actual-format', newSyncVersion.toString()) + .set('x-actual-encrypt-meta', newEncryptMeta) + .send(Buffer.from(newFileContent)); + + expect(res.statusCode).toEqual(200); + expect(res.body).toEqual({ status: 'ok', groupId: expect.any(String) }); + + const receivedGroupid = res.body.groupId; + + // Verify that the file was updated in the accountDb + const rowsAfter = getAccountDb().all('SELECT * FROM files WHERE id = ?', [ + fileId, + ]); + expect(rowsAfter.length).toBe(1); + expect(rowsAfter[0].id).toEqual(fileId); + expect(rowsAfter[0].group_id).toEqual(receivedGroupid); + expect(rowsAfter[0].sync_version).toEqual(newSyncVersion); + expect(rowsAfter[0].name).toEqual(newFileName); + expect(rowsAfter[0].encrypt_meta).toEqual(newEncryptMeta); + + // Verify that the file was written to the file system + const filePath = getPathForUserFile(fileId); + const writtenContent = await fs.promises.readFile(filePath, 'utf8'); + expect(writtenContent).toEqual(newFileContent); + + // Clean up the file + await fs.promises.unlink(filePath); + }); + + it('returns 400 if the file is part of an old group', async () => { + const fileId = crypto.randomBytes(16).toString('hex'); + const groupId = 'old-group-id'; + const fileName = 'test-file.txt'; + const keyId = 'key-id'; + const syncVersion = 2; + + // Add a mock file with the old group ID + addMockFile( + fileId, + 'current-group-id', + keyId, + JSON.stringify({ keyId }), + syncVersion, + ); + + const res = await request(app) + .post('/upload-user-file') + .set('Content-Type', 'application/encrypted-file') + .set('x-actual-token', 'valid-token') + .set('x-actual-file-id', fileId) + .set('x-actual-group-id', groupId) + .set('x-actual-name', fileName); + + expect(res.statusCode).toEqual(400); + expect(res.text).toEqual('file-has-reset'); + }); + + it('returns 400 if the file has a new encryption key', async () => { + const fileId = crypto.randomBytes(16).toString('hex'); + const groupId = 'group-id'; + const fileName = 'test-file.txt'; + const oldKeyId = 'old-key-id'; + const newKeyId = 'new-key-id'; + const syncVersion = 2; + + // Add a mock file with the new key + addMockFile( + fileId, + groupId, + newKeyId, + JSON.stringify({ newKeyId }), + syncVersion, + ); + + const res = await request(app) + .post('/upload-user-file') + .set('Content-Type', 'application/encrypted-file') + .set('x-actual-token', 'valid-token') + .set('x-actual-file-id', fileId) + .set('x-actual-group-id', groupId) + .set('x-actual-name', fileName) + .set('x-actual-encrypt-meta', JSON.stringify({ keyId: oldKeyId })); + + expect(res.statusCode).toEqual(400); + expect(res.text).toEqual('file-has-new-key'); + }); +}); + +describe('/download-user-file', () => { + describe('default version', () => { + it('returns 401 if the user is not authenticated', async () => { + const res = await request(app).get('/download-user-file'); + + expect(res.statusCode).toEqual(401); + expect(res.body).toEqual({ + details: 'token-not-found', + reason: 'unauthorized', + status: 'error', + }); + }); + + it('returns 401 if the user is invalid', async () => { + const res = await request(app) + .get('/download-user-file') + .set('x-actual-token', 'invalid-token'); + + expect(res.statusCode).toEqual(401); + expect(res.body).toEqual({ + details: 'token-not-found', + reason: 'unauthorized', + status: 'error', + }); + }); + + it('returns 400 error if the file does not exist in the database', async () => { + const res = await request(app) + .get('/download-user-file') + .set('x-actual-token', 'valid-token') + .set('x-actual-file-id', 'non-existing-file-id'); + + expect(res.statusCode).toEqual(400); + expect(res.text).toBe('User or file not found'); + }); + + it('returns 500 error if the file does not exist on the filesystem', async () => { + getAccountDb().mutate( + 'INSERT INTO files (id, deleted) VALUES (?, FALSE)', + ['missing-fs-file'], + ); + + const res = await request(app) + .get('/download-user-file') + .set('x-actual-token', 'valid-token') + .set('x-actual-file-id', 'missing-fs-file'); + + expect(res.statusCode).toEqual(404); + }); + + it('returns an attachment file', async () => { + const fileContent = 'content'; + fs.writeFileSync(getPathForUserFile('file-id'), fileContent); + getAccountDb().mutate( + 'INSERT INTO files (id, deleted) VALUES (?, FALSE)', + ['file-id'], + ); + + const res = await request(app) + .get('/download-user-file') + .set('x-actual-token', 'valid-token') + .set('x-actual-file-id', 'file-id'); + + expect(res.statusCode).toEqual(200); + expect(res.headers).toEqual( + expect.objectContaining({ + 'content-disposition': 'attachment;filename=file-id', + 'content-type': 'application/octet-stream', + }), + ); + + expect(res.body).toBeInstanceOf(Buffer); + expect(res.body.toString('utf8')).toEqual(fileContent); + }); + }); +}); + +describe('/update-user-filename', () => { + it('returns 401 if the user is not authenticated', async () => { + const res = await request(app).post('/update-user-filename'); + + expect(res.statusCode).toEqual(401); + expect(res.body).toEqual({ + details: 'token-not-found', + reason: 'unauthorized', + status: 'error', + }); + }); + + it('returns 400 if the file is not found', async () => { + const res = await request(app) + .post('/update-user-filename') + .set('x-actual-token', 'valid-token') + .send({ fileId: 'non-existent-file-id', name: 'new-filename' }); + + expect(res.statusCode).toEqual(400); + expect(res.text).toBe('file not found'); + }); + + it('successfully updates the filename', async () => { + const fileId = crypto.randomBytes(16).toString('hex'); + const oldName = 'old-filename'; + const newName = 'new-filename'; + + // Insert a mock file into the database + getAccountDb().mutate( + 'INSERT INTO files (id, name, deleted) VALUES (?, ?, FALSE)', + [fileId, oldName], + ); + + const res = await request(app) + .post('/update-user-filename') + .set('x-actual-token', 'valid-token') + .send({ fileId, name: newName }); + + expect(res.statusCode).toEqual(200); + expect(res.body).toEqual({ status: 'ok' }); + + // Verify that the filename was updated + const rows = getAccountDb().all('SELECT name FROM files WHERE id = ?', [ + fileId, + ]); + + expect(rows[0].name).toEqual(newName); + }); +}); + +describe('/list-user-files', () => { + it('returns 401 if the user is not authenticated', async () => { + const res = await request(app).get('/list-user-files'); + + expect(res.statusCode).toEqual(401); + expect(res.body).toEqual({ + details: 'token-not-found', + reason: 'unauthorized', + status: 'error', + }); + }); + + it('returns a list of user files for an authenticated user', async () => { + const fileId1 = crypto.randomBytes(16).toString('hex'); + const fileId2 = crypto.randomBytes(16).toString('hex'); + const fileName1 = 'file1.txt'; + const fileName2 = 'file2.txt'; + + // Insert mock files into the database + getAccountDb().mutate( + 'INSERT INTO files (id, name, deleted) VALUES (?, ?, FALSE)', + [fileId1, fileName1], + ); + getAccountDb().mutate( + 'INSERT INTO files (id, name, deleted) VALUES (?, ?, FALSE)', + [fileId2, fileName2], + ); + + const res = await request(app) + .get('/list-user-files') + .set('x-actual-token', 'valid-token'); + + expect(res.statusCode).toEqual(200); + expect(res.body).toEqual( + expect.objectContaining({ + status: 'ok', + data: expect.arrayContaining([ + expect.objectContaining({ + deleted: 0, + fileId: fileId1, + groupId: null, + name: fileName1, + encryptKeyId: null, + }), + expect.objectContaining({ + deleted: 0, + fileId: fileId2, + groupId: null, + name: fileName2, + encryptKeyId: null, + }), + ]), + }), + ); + }); +}); + +describe('/get-user-file-info', () => { + it('returns file info for a valid fileId', async () => { + const fileId = crypto.randomBytes(16).toString('hex'); + const groupId = 'test-group-id'; + const fileInfo = { + id: fileId, + group_id: groupId, + name: 'test-file', + encrypt_meta: JSON.stringify({ key: 'value' }), + deleted: 0, + }; + + getAccountDb().mutate( + 'INSERT INTO files (id, group_id, name, encrypt_meta, deleted) VALUES (?, ?, ?, ?, ?)', + [ + fileInfo.id, + fileInfo.group_id, + fileInfo.name, + fileInfo.encrypt_meta, + fileInfo.deleted, + ], + ); + + const res = await request(app) + .get('/get-user-file-info') + .set('x-actual-token', 'valid-token') + .set('x-actual-file-id', fileId) + .send(); + + expect(res.statusCode).toEqual(200); + + expect(res.body).toEqual({ + status: 'ok', + data: { + deleted: fileInfo.deleted, + fileId: fileInfo.id, + groupId: fileInfo.group_id, + name: fileInfo.name, + encryptMeta: { key: 'value' }, + }, + }); + }); + + it('returns error if the file is not found', async () => { + const fileId = 'non-existent-file-id'; + + const res = await request(app) + .get('/get-user-file-info') + .set('x-actual-token', 'valid-token') + .set('x-actual-file-id', fileId); + + expect(res.statusCode).toEqual(400); + expect(res.body).toEqual({ status: 'error', reason: 'file-not-found' }); + }); + + it('returns error if the user is not authenticated', async () => { + // Simulate an unauthenticated request by not setting the necessary headers + const res = await request(app).get('/get-user-file-info'); + + expect(res.statusCode).toEqual(401); + expect(res.body).toEqual({ + status: 'error', + reason: 'unauthorized', + details: 'token-not-found', + }); + }); +}); + +describe('/delete-user-file', () => { + it('returns 401 if the user is not authenticated', async () => { + const res = await request(app).post('/delete-user-file'); + + expect(res.statusCode).toEqual(401); + expect(res.body).toEqual({ + details: 'token-not-found', + reason: 'unauthorized', + status: 'error', + }); + }); + + // it returns 422 if the fileId is not provided + it('returns 422 if the fileId is not provided', async () => { + const res = await request(app) + .post('/delete-user-file') + .set('x-actual-token', 'valid-token'); + + expect(res.statusCode).toEqual(422); + expect(res.body).toEqual({ + details: 'fileId-required', + reason: 'unprocessable-entity', + status: 'error', + }); + }); + + it('returns 400 if the file does not exist', async () => { + const res = await request(app) + .post('/delete-user-file') + .set('x-actual-token', 'valid-token') + .send({ fileId: 'non-existing-file-id' }); + + expect(res.statusCode).toEqual(400); + expect(res.text).toEqual('file-not-found'); + }); + + it('marks the file as deleted', async () => { + const accountDb = getAccountDb(); + const fileId = crypto.randomBytes(16).toString('hex'); + + // Insert a file into the database + accountDb.mutate( + 'INSERT OR IGNORE INTO files (id, deleted) VALUES (?, FALSE)', + [fileId], + ); + + const res = await request(app) + .post('/delete-user-file') + .set('x-actual-token', 'valid-token') + .send({ fileId }); + + expect(res.statusCode).toEqual(200); + expect(res.body).toEqual({ status: 'ok' }); + + // Verify that the file is marked as deleted + const rows = accountDb.all('SELECT deleted FROM files WHERE id = ?', [ + fileId, + ]); + expect(rows[0].deleted).toBe(1); + }); +}); + +describe('/sync', () => { + it('returns 401 if the user is not authenticated', async () => { + const res = await request(app).post('/sync'); + + expect(res.statusCode).toEqual(401); + expect(res.body).toEqual({ + details: 'token-not-found', + reason: 'unauthorized', + status: 'error', + }); + }); + + it('returns 200 and syncs successfully with correct file attributes', async () => { + const fileId = crypto.randomBytes(16).toString('hex'); + const groupId = 'group-id'; + const keyId = 'key-id'; + const syncVersion = 2; + const encryptMeta = JSON.stringify({ keyId }); + + addMockFile(fileId, groupId, keyId, encryptMeta, syncVersion); + + const syncRequest = createMinimalSyncRequest(fileId, groupId, keyId); + + const res = await sendSyncRequest(syncRequest); + + expect(res.statusCode).toEqual(200); + expect(res.headers['content-type']).toEqual('application/actual-sync'); + expect(res.headers['x-actual-sync-method']).toEqual('simple'); + }); + + it('returns 500 if the request body is invalid', async () => { + const res = await request(app) + .post('/sync') + .set('x-actual-token', 'valid-token') + // Content-Type is set correctly, but the body cannot be deserialized + .set('Content-Type', 'application/actual-sync') + .send('invalid-body'); + + expect(res.statusCode).toEqual(500); + expect(res.body).toEqual({ + status: 'error', + reason: 'internal-error', + }); + }); + + it('returns 422 if since is not provided', async () => { + const syncRequest = createMinimalSyncRequest( + 'file-id', + 'group-id', + 'key-id', + ); + syncRequest.setSince(undefined); + + const res = await sendSyncRequest(syncRequest); + + expect(res.statusCode).toEqual(422); + expect(res.body).toEqual({ + status: 'error', + reason: 'unprocessable-entity', + details: 'since-required', + }); + }); + + it('returns 400 if the file does not exist in the database', async () => { + const syncRequest = createMinimalSyncRequest( + 'non-existant-file-id', + 'group-id', + 'key-id', + ); + + // We do not insert the file into the database, so it does not exist + + const res = await sendSyncRequest(syncRequest); + + expect(res.statusCode).toEqual(400); + expect(res.text).toEqual('file-not-found'); + }); + + it('returns 400 if the file sync version is old', async () => { + const fileId = crypto.randomBytes(16).toString('hex'); + const groupId = 'group-id'; + const keyId = 'key-id'; + const oldSyncVersion = 1; // Assuming SYNC_FORMAT_VERSION is 2 + + // Add a mock file with an old sync version + addMockFile( + fileId, + groupId, + keyId, + JSON.stringify({ keyId }), + oldSyncVersion, + ); + + const syncRequest = createMinimalSyncRequest(fileId, groupId, keyId); + + const res = await sendSyncRequest(syncRequest); + + expect(res.statusCode).toEqual(400); + expect(res.text).toEqual('file-old-version'); + }); + + it('returns 400 if the file needs to be uploaded (no group_id)', async () => { + const fileId = crypto.randomBytes(16).toString('hex'); + const groupId = null; // No group ID + const keyId = 'key-id'; + const syncVersion = 2; + + addMockFile(fileId, groupId, keyId, JSON.stringify({ keyId }), syncVersion); + + const syncRequest = createMinimalSyncRequest(fileId, groupId, keyId); + + const res = await sendSyncRequest(syncRequest); + + expect(res.statusCode).toEqual(400); + expect(res.text).toEqual('file-needs-upload'); + }); + + it('returns 400 if the file has a new encryption key', async () => { + const fileId = crypto.randomBytes(16).toString('hex'); + const groupId = 'group-id'; + const keyId = 'old-key-id'; + const newKeyId = 'new-key-id'; + const syncVersion = 2; + + // Add a mock file with the old key + addMockFile(fileId, groupId, keyId, JSON.stringify({ keyId }), syncVersion); + + // Create a sync request with the new key + const syncRequest = createMinimalSyncRequest(fileId, groupId, newKeyId); + const res = await sendSyncRequest(syncRequest); + + expect(res.statusCode).toEqual(400); + expect(res.text).toEqual('file-has-new-key'); + }); +}); + +function addMockFile(fileId, groupId, keyId, encryptMeta, syncVersion) { + getAccountDb().mutate( + 'INSERT INTO files (id, group_id, encrypt_keyid, encrypt_meta, sync_version) VALUES (?, ?, ?,?, ?)', + [fileId, groupId, keyId, encryptMeta, syncVersion], + ); +} + +function createMinimalSyncRequest(fileId, groupId, keyId) { + const syncRequest = new SyncProtoBuf.SyncRequest(); + syncRequest.setFileid(fileId); + syncRequest.setGroupid(groupId); + syncRequest.setKeyid(keyId); + syncRequest.setSince('2024-01-01T00:00:00.000Z'); + syncRequest.setMessagesList([]); + return syncRequest; +} + +async function sendSyncRequest(syncRequest) { + const serializedRequest = syncRequest.serializeBinary(); + // Convert Uint8Array to Buffer + const bufferRequest = Buffer.from(serializedRequest); + + const res = await request(app) + .post('/sync') + .set('x-actual-token', 'valid-token') + .set('Content-Type', 'application/actual-sync') + .send(bufferRequest); + return res; +} diff --git a/packages/sync-server/src/app.js b/packages/sync-server/src/app.js new file mode 100644 index 00000000000..6dbf3506d3e --- /dev/null +++ b/packages/sync-server/src/app.js @@ -0,0 +1,87 @@ +import fs from 'node:fs'; +import express from 'express'; +import actuator from 'express-actuator'; +import bodyParser from 'body-parser'; +import cors from 'cors'; +import config from './load-config.js'; +import rateLimit from 'express-rate-limit'; + +import * as accountApp from './app-account.js'; +import * as syncApp from './app-sync.js'; +import * as goCardlessApp from './app-gocardless/app-gocardless.js'; +import * as simpleFinApp from './app-simplefin/app-simplefin.js'; +import * as secretApp from './app-secrets.js'; + +const app = express(); + +process.on('unhandledRejection', (reason) => { + console.log('Rejection:', reason); +}); + +app.disable('x-powered-by'); +app.use(cors()); +app.use( + rateLimit({ + windowMs: 60 * 1000, + max: 500, + legacyHeaders: false, + standardHeaders: true, + }), +); +app.use(bodyParser.json({ limit: `${config.upload.fileSizeLimitMB}mb` })); +app.use( + bodyParser.raw({ + type: 'application/actual-sync', + limit: `${config.upload.fileSizeSyncLimitMB}mb`, + }), +); +app.use( + bodyParser.raw({ + type: 'application/encrypted-file', + limit: `${config.upload.syncEncryptedFileSizeLimitMB}mb`, + }), +); + +app.use('/sync', syncApp.handlers); +app.use('/account', accountApp.handlers); +app.use('/gocardless', goCardlessApp.handlers); +app.use('/simplefin', simpleFinApp.handlers); +app.use('/secret', secretApp.handlers); + +app.get('/mode', (req, res) => { + res.send(config.mode); +}); + +app.use(actuator()); // Provides /health, /metrics, /info + +// The web frontend +app.use((req, res, next) => { + res.set('Cross-Origin-Opener-Policy', 'same-origin'); + res.set('Cross-Origin-Embedder-Policy', 'require-corp'); + next(); +}); +app.use(express.static(config.webRoot, { index: false })); + +app.get('/*', (req, res) => res.sendFile(config.webRoot + '/index.html')); + +function parseHTTPSConfig(value) { + if (value.startsWith('-----BEGIN')) { + return value; + } + return fs.readFileSync(value); +} + +export default async function run() { + if (config.https) { + const https = await import('node:https'); + const httpsOptions = { + ...config.https, + key: parseHTTPSConfig(config.https.key), + cert: parseHTTPSConfig(config.https.cert), + }; + https.createServer(httpsOptions, app).listen(config.port, config.hostname); + } else { + app.listen(config.port, config.hostname); + } + console.log('Listening on ' + config.hostname + ':' + config.port + '...'); +} diff --git a/packages/sync-server/src/config-types.ts b/packages/sync-server/src/config-types.ts new file mode 100644 index 00000000000..8be7ba49de2 --- /dev/null +++ b/packages/sync-server/src/config-types.ts @@ -0,0 +1,23 @@ +import { ServerOptions } from 'https'; + +export interface Config { + mode: 'test' | 'development'; + loginMethod: 'password' | 'header'; + trustedProxies: string[]; + dataDir: string; + projectRoot: string; + port: number; + hostname: string; + serverFiles: string; + userFiles: string; + webRoot: string; + https?: { + key: string; + cert: string; + } & ServerOptions; + upload?: { + fileSizeSyncLimitMB: number; + syncEncryptedFileSizeLimitMB: number; + fileSizeLimitMB: number; + }; +} diff --git a/packages/sync-server/src/db.js b/packages/sync-server/src/db.js new file mode 100644 index 00000000000..a4d57a6a9c8 --- /dev/null +++ b/packages/sync-server/src/db.js @@ -0,0 +1,58 @@ +import Database from 'better-sqlite3'; + +class WrappedDatabase { + constructor(db) { + this.db = db; + } + + /** + * @param {string} sql + * @param {string[]} params + */ + all(sql, params = []) { + let stmt = this.db.prepare(sql); + return stmt.all(...params); + } + + /** + * @param {string} sql + * @param {string[]} params + */ + first(sql, params = []) { + let rows = this.all(sql, params); + return rows.length === 0 ? null : rows[0]; + } + + /** + * @param {string} sql + */ + exec(sql) { + return this.db.exec(sql); + } + + /** + * @param {string} sql + * @param {string[]} params + */ + mutate(sql, params = []) { + let stmt = this.db.prepare(sql); + let info = stmt.run(...params); + return { changes: info.changes, insertId: info.lastInsertRowid }; + } + + /** + * @param {() => void} fn + */ + transaction(fn) { + return this.db.transaction(fn)(); + } + + close() { + this.db.close(); + } +} + +/** @param {string} filename */ +export default function openDatabase(filename) { + return new WrappedDatabase(new Database(filename)); +} diff --git a/packages/sync-server/src/load-config.js b/packages/sync-server/src/load-config.js new file mode 100644 index 00000000000..d99ce421180 --- /dev/null +++ b/packages/sync-server/src/load-config.js @@ -0,0 +1,163 @@ +import fs from 'node:fs'; +import path from 'node:path'; +import { fileURLToPath } from 'node:url'; +import createDebug from 'debug'; + +const debug = createDebug('actual:config'); +const debugSensitive = createDebug('actual-sensitive:config'); + +const projectRoot = path.dirname(path.dirname(fileURLToPath(import.meta.url))); +debug(`project root: '${projectRoot}'`); +export const sqlDir = path.join(projectRoot, 'src', 'sql'); + +let defaultDataDir = fs.existsSync('/data') ? '/data' : projectRoot; +debug(`default data directory: '${defaultDataDir}'`); + +function parseJSON(path, allowMissing = false) { + let text; + try { + text = fs.readFileSync(path, 'utf8'); + } catch (e) { + if (allowMissing) { + debug(`config file '${path}' not found, ignoring.`); + return {}; + } + throw e; + } + return JSON.parse(text); +} + +let userConfig; +if (process.env.ACTUAL_CONFIG_PATH) { + debug( + `loading config from ACTUAL_CONFIG_PATH: '${process.env.ACTUAL_CONFIG_PATH}'`, + ); + userConfig = parseJSON(process.env.ACTUAL_CONFIG_PATH); + + defaultDataDir = userConfig.dataDir ?? defaultDataDir; +} else { + let configFile = path.join(projectRoot, 'config.json'); + + if (!fs.existsSync(configFile)) { + configFile = path.join(defaultDataDir, 'config.json'); + } + + debug(`loading config from default path: '${configFile}'`); + userConfig = parseJSON(configFile, true); +} + +/** @type {Omit} */ +let defaultConfig = { + loginMethod: 'password', + // assume local networks are trusted for header authentication + trustedProxies: [ + '10.0.0.0/8', + '172.16.0.0/12', + '192.168.0.0/16', + 'fc00::/7', + '::1/128', + ], + port: 5006, + hostname: '::', + webRoot: path.join( + projectRoot, + 'node_modules', + '@actual-app', + 'web', + 'build', + ), + upload: { + fileSizeSyncLimitMB: 20, + syncEncryptedFileSizeLimitMB: 50, + fileSizeLimitMB: 20, + }, + projectRoot, +}; + +/** @type {import('./config-types.js').Config} */ +let config; +if (process.env.NODE_ENV === 'test') { + config = { + mode: 'test', + dataDir: projectRoot, + serverFiles: path.join(projectRoot, 'test-server-files'), + userFiles: path.join(projectRoot, 'test-user-files'), + ...defaultConfig, + }; +} else { + config = { + mode: 'development', + ...defaultConfig, + dataDir: defaultDataDir, + serverFiles: path.join(defaultDataDir, 'server-files'), + userFiles: path.join(defaultDataDir, 'user-files'), + ...(userConfig || {}), + }; +} + +const finalConfig = { + ...config, + loginMethod: process.env.ACTUAL_LOGIN_METHOD + ? process.env.ACTUAL_LOGIN_METHOD.toLowerCase() + : config.loginMethod, + trustedProxies: process.env.ACTUAL_TRUSTED_PROXIES + ? process.env.ACTUAL_TRUSTED_PROXIES.split(',').map((q) => q.trim()) + : config.trustedProxies, + port: +process.env.ACTUAL_PORT || +process.env.PORT || config.port, + hostname: process.env.ACTUAL_HOSTNAME || config.hostname, + serverFiles: process.env.ACTUAL_SERVER_FILES || config.serverFiles, + userFiles: process.env.ACTUAL_USER_FILES || config.userFiles, + webRoot: process.env.ACTUAL_WEB_ROOT || config.webRoot, + https: + process.env.ACTUAL_HTTPS_KEY && process.env.ACTUAL_HTTPS_CERT + ? { + key: process.env.ACTUAL_HTTPS_KEY.replace(/\\n/g, '\n'), + cert: process.env.ACTUAL_HTTPS_CERT.replace(/\\n/g, '\n'), + ...(config.https || {}), + } + : config.https, + upload: + process.env.ACTUAL_UPLOAD_FILE_SYNC_SIZE_LIMIT_MB || + process.env.ACTUAL_UPLOAD_SYNC_ENCRYPTED_FILE_SYNC_SIZE_LIMIT_MB || + process.env.ACTUAL_UPLOAD_FILE_SIZE_LIMIT_MB + ? { + fileSizeSyncLimitMB: + +process.env.ACTUAL_UPLOAD_FILE_SYNC_SIZE_LIMIT_MB || + +process.env.ACTUAL_UPLOAD_FILE_SIZE_LIMIT_MB || + config.upload.fileSizeSyncLimitMB, + syncEncryptedFileSizeLimitMB: + +process.env.ACTUAL_UPLOAD_SYNC_ENCRYPTED_FILE_SYNC_SIZE_LIMIT_MB || + +process.env.ACTUAL_UPLOAD_FILE_SIZE_LIMIT_MB || + config.upload.syncEncryptedFileSizeLimitMB, + fileSizeLimitMB: + +process.env.ACTUAL_UPLOAD_FILE_SIZE_LIMIT_MB || + config.upload.fileSizeLimitMB, + } + : config.upload, +}; + +debug(`using port ${finalConfig.port}`); +debug(`using hostname ${finalConfig.hostname}`); +debug(`using data directory ${finalConfig.dataDir}`); +debug(`using server files directory ${finalConfig.serverFiles}`); +debug(`using user files directory ${finalConfig.userFiles}`); +debug(`using web root directory ${finalConfig.webRoot}`); +debug(`using login method ${finalConfig.loginMethod}`); +debug(`using trusted proxies ${finalConfig.trustedProxies.join(', ')}`); + +if (finalConfig.https) { + debug(`using https key: ${'*'.repeat(finalConfig.https.key.length)}`); + debugSensitive(`using https key ${finalConfig.https.key}`); + debug(`using https cert: ${'*'.repeat(finalConfig.https.cert.length)}`); + debugSensitive(`using https cert ${finalConfig.https.cert}`); +} + +if (finalConfig.upload) { + debug(`using file sync limit ${finalConfig.upload.fileSizeSyncLimitMB}mb`); + debug( + `using sync encrypted file limit ${finalConfig.upload.syncEncryptedFileSizeLimitMB}mb`, + ); + debug(`using file limit ${finalConfig.upload.fileSizeLimitMB}mb`); +} + +export default finalConfig; diff --git a/packages/sync-server/src/migrations.js b/packages/sync-server/src/migrations.js new file mode 100644 index 00000000000..cba7db0fd75 --- /dev/null +++ b/packages/sync-server/src/migrations.js @@ -0,0 +1,34 @@ +import migrate from 'migrate'; +import path from 'node:path'; +import config from './load-config.js'; + +export default function run(direction = 'up') { + console.log( + `Checking if there are any migrations to run for direction "${direction}"...`, + ); + + return new Promise((resolve) => + migrate.load( + { + stateStore: `${path.join(config.dataDir, '.migrate')}${ + config.mode === 'test' ? '-test' : '' + }`, + migrationsDirectory: `${path.join(config.projectRoot, 'migrations')}`, + }, + (err, set) => { + if (err) { + throw err; + } + + set[direction]((err) => { + if (err) { + throw err; + } + + console.log('Migrations: DONE'); + resolve(); + }); + }, + ), + ); +} diff --git a/packages/sync-server/src/run-migrations.js b/packages/sync-server/src/run-migrations.js new file mode 100644 index 00000000000..b5ed269401a --- /dev/null +++ b/packages/sync-server/src/run-migrations.js @@ -0,0 +1,8 @@ +import run from './migrations.js'; + +const direction = process.argv[2] || 'up'; + +run(direction).catch((err) => { + console.error('Migration failed:', err); + process.exit(1); +}); diff --git a/packages/sync-server/src/scripts/health-check.js b/packages/sync-server/src/scripts/health-check.js new file mode 100644 index 00000000000..6cce6d1352d --- /dev/null +++ b/packages/sync-server/src/scripts/health-check.js @@ -0,0 +1,20 @@ +import fetch from 'node-fetch'; +import config from '../load-config.js'; + +let protocol = config.https ? 'https' : 'http'; +let hostname = config.hostname === '::' ? 'localhost' : config.hostname; + +fetch(`${protocol}://${hostname}:${config.port}/health`) + .then((res) => res.json()) + .then((res) => { + if (res.status !== 'UP') { + throw new Error( + 'Health check failed: Server responded to health check with status ' + + res.status, + ); + } + }) + .catch((err) => { + console.log('Health check failed:', err); + process.exit(1); + }); diff --git a/packages/sync-server/src/scripts/reset-password.js b/packages/sync-server/src/scripts/reset-password.js new file mode 100644 index 00000000000..26d5b163871 --- /dev/null +++ b/packages/sync-server/src/scripts/reset-password.js @@ -0,0 +1,36 @@ +import { needsBootstrap, bootstrap, changePassword } from '../account-db.js'; +import { promptPassword } from '../util/prompt.js'; + +if (needsBootstrap()) { + console.log( + 'It looks like you don’t have a password set yet. Let’s set one up now!', + ); + + promptPassword().then((password) => { + let { error } = bootstrap(password); + if (error) { + console.log('Error setting password:', error); + console.log( + 'Please report this as an issue: https://github.com/actualbudget/actual-server/issues', + ); + } else { + console.log('Password set!'); + } + }); +} else { + console.log('It looks like you already have a password set. Let’s reset it!'); + promptPassword().then((password) => { + let { error } = changePassword(password); + if (error) { + console.log('Error changing password:', error); + console.log( + 'Please report this as an issue: https://github.com/actualbudget/actual-server/issues', + ); + } else { + console.log('Password changed!'); + console.log( + 'Note: you will need to log in with the new password on any browsers or devices that are currently logged in.', + ); + } + }); +} diff --git a/packages/sync-server/src/secrets.test.js b/packages/sync-server/src/secrets.test.js new file mode 100644 index 00000000000..34a34ed083d --- /dev/null +++ b/packages/sync-server/src/secrets.test.js @@ -0,0 +1,82 @@ +import { secretsService } from './services/secrets-service.js'; +import request from 'supertest'; +import { handlers as app } from './app-secrets.js'; +describe('secretsService', () => { + const testSecretName = 'testSecret'; + const testSecretValue = 'testValue'; + + it('should set a secret', () => { + const result = secretsService.set(testSecretName, testSecretValue); + expect(result).toBeDefined(); + expect(result.changes).toBe(1); + }); + + it('should get a secret', () => { + const result = secretsService.get(testSecretName); + expect(result).toBeDefined(); + expect(result).toBe(testSecretValue); + }); + + it('should check if a secret exists', () => { + const exists = secretsService.exists(testSecretName); + expect(exists).toBe(true); + + const nonExistent = secretsService.exists('nonExistentSecret'); + expect(nonExistent).toBe(false); + }); + + it('should update a secret', () => { + const newValue = 'newValue'; + const setResult = secretsService.set(testSecretName, newValue); + expect(setResult).toBeDefined(); + expect(setResult.changes).toBe(1); + + const getResult = secretsService.get(testSecretName); + expect(getResult).toBeDefined(); + expect(getResult).toBe(newValue); + }); + + describe('secrets api', () => { + it('returns 401 if the user is not authenticated', async () => { + secretsService.set(testSecretName, testSecretValue); + const res = await request(app).get(`/${testSecretName}`); + + expect(res.statusCode).toEqual(401); + expect(res.body).toEqual({ + details: 'token-not-found', + reason: 'unauthorized', + status: 'error', + }); + }); + + it('returns 404 if secret does not exist', async () => { + const res = await request(app) + .get(`/thiskeydoesnotexist`) + .set('x-actual-token', 'valid-token'); + + expect(res.statusCode).toEqual(404); + }); + + it('returns 204 if secret exists', async () => { + secretsService.set(testSecretName, testSecretValue); + const res = await request(app) + .get(`/${testSecretName}`) + .set('x-actual-token', 'valid-token'); + + expect(res.statusCode).toEqual(204); + }); + + it('returns 200 if secret was set', async () => { + secretsService.set(testSecretName, testSecretValue); + const res = await request(app) + .post(`/`) + .set('x-actual-token', 'valid-token') + .send({ name: testSecretName, value: testSecretValue }); + + expect(res.statusCode).toEqual(200); + expect(res.body).toEqual({ + status: 'ok', + }); + }); + }); +}); diff --git a/packages/sync-server/src/services/secrets-service.js b/packages/sync-server/src/services/secrets-service.js new file mode 100644 index 00000000000..fb56825fe2f --- /dev/null +++ b/packages/sync-server/src/services/secrets-service.js @@ -0,0 +1,90 @@ +import createDebug from 'debug'; +import getAccountDb from '../account-db.js'; + +/** + * An enum of valid secret names. + * @readonly + * @enum {string} + */ +export const SecretName = { + gocardless_secretId: 'gocardless_secretId', + gocardless_secretKey: 'gocardless_secretKey', + simplefin_token: 'simplefin_token', + simplefin_accessKey: 'simplefin_accessKey', +}; + +class SecretsDb { + constructor() { + this.debug = createDebug('actual:secrets-db'); + this.db = null; + } + + open() { + return getAccountDb(); + } + + set(name, value) { + if (!this.db) { + this.db = this.open(); + } + + this.debug(`setting secret '${name}' to '${value}'`); + const result = this.db.mutate( + `INSERT OR REPLACE INTO secrets (name, value) VALUES (?,?)`, + [name, value], + ); + return result; + } + + get(name) { + if (!this.db) { + this.db = this.open(); + } + + this.debug(`getting secret '${name}'`); + const result = this.db.first(`SELECT value FROM secrets WHERE name =?`, [ + name, + ]); + return result; + } +} + +const secretsDb = new SecretsDb(); +const _cachedSecrets = new Map(); +/** + * A service for managing secrets stored in `secretsDb`. + */ +export const secretsService = { + /** + * Retrieves the value of a secret by name. + * @param {SecretName} name - The name of the secret to retrieve. + * @returns {string|null} The value of the secret, or null if the secret does not exist. + */ + get: (name) => { + return _cachedSecrets.get(name) ?? secretsDb.get(name)?.value ?? null; + }, + + /** + * Sets the value of a secret by name. + * @param {SecretName} name - The name of the secret to set. + * @param {string} value - The value to set for the secret. + * @returns {Object} + */ + set: (name, value) => { + const result = secretsDb.set(name, value); + + if (result.changes === 1) { + _cachedSecrets.set(name, value); + } + return result; + }, + + /** + * Determines whether a secret with the given name exists. + * @param {SecretName} name - The name of the secret to check for existence. + * @returns {boolean} True if a secret with the given name exists, false otherwise. + */ + exists: (name) => { + return Boolean(secretsService.get(name)); + }, +}; diff --git a/packages/sync-server/src/sql/messages.sql b/packages/sync-server/src/sql/messages.sql new file mode 100644 index 00000000000..64d35cb7ec4 --- /dev/null +++ b/packages/sync-server/src/sql/messages.sql @@ -0,0 +1,9 @@ + +CREATE TABLE messages_binary + (timestamp TEXT PRIMARY KEY, + is_encrypted BOOLEAN, + content bytea); + +CREATE TABLE messages_merkles + (id INTEGER PRIMARY KEY, + merkle TEXT); diff --git a/packages/sync-server/src/sync-simple.js b/packages/sync-server/src/sync-simple.js new file mode 100644 index 00000000000..af0f99f93e1 --- /dev/null +++ b/packages/sync-server/src/sync-simple.js @@ -0,0 +1,95 @@ +import { existsSync, readFileSync } from 'node:fs'; +import { join } from 'node:path'; +import openDatabase from './db.js'; +import { getPathForGroupFile } from './util/paths.js'; + +import { sqlDir } from './load-config.js'; + +import { merkle, SyncProtoBuf, Timestamp } from '@actual-app/crdt'; + +function getGroupDb(groupId) { + let path = getPathForGroupFile(groupId); + let needsInit = !existsSync(path); + + let db = openDatabase(path); + + if (needsInit) { + let sql = readFileSync(join(sqlDir, 'messages.sql'), 'utf8'); + db.exec(sql); + } + + return db; +} + +function addMessages(db, messages) { + let returnValue; + db.transaction(() => { + let trie = getMerkle(db); + + if (messages.length > 0) { + for (let msg of messages) { + let info = db.mutate( + `INSERT OR IGNORE INTO messages_binary (timestamp, is_encrypted, content) + VALUES (?, ?, ?)`, + [ + msg.getTimestamp(), + msg.getIsencrypted() ? 1 : 0, + Buffer.from(msg.getContent()), + ], + ); + + if (info.changes > 0) { + trie = merkle.insert(trie, Timestamp.parse(msg.getTimestamp())); + } + } + } + + trie = merkle.prune(trie); + + db.mutate( + 'INSERT INTO messages_merkles (id, merkle) VALUES (1, ?) ON CONFLICT (id) DO UPDATE SET merkle = ?', + [JSON.stringify(trie), JSON.stringify(trie)], + ); + + returnValue = trie; + }); + + return returnValue; +} + +function getMerkle(db) { + let rows = db.all('SELECT * FROM messages_merkles'); + + if (rows.length > 0) { + return JSON.parse(rows[0].merkle); + } else { + // No merkle trie exists yet (first sync of the app), so create a + // default one. + return {}; + } +} + +export function sync(messages, since, groupId) { + let db = getGroupDb(groupId); + let newMessages = db.all( + `SELECT * FROM messages_binary + WHERE timestamp > ? + ORDER BY timestamp`, + [since], + ); + + let trie = addMessages(db, messages); + + db.close(); + + return { + trie, + newMessages: newMessages.map((msg) => { + const envelopePb = new SyncProtoBuf.MessageEnvelope(); + envelopePb.setTimestamp(msg.timestamp); + envelopePb.setIsencrypted(msg.is_encrypted); + envelopePb.setContent(msg.content); + return envelopePb; + }), + }; +} diff --git a/packages/sync-server/src/util/hash.js b/packages/sync-server/src/util/hash.js new file mode 100644 index 00000000000..e8f14f3e4e0 --- /dev/null +++ b/packages/sync-server/src/util/hash.js @@ -0,0 +1,5 @@ +import crypto from 'crypto'; + +export async function sha256String(str) { + return crypto.createHash('sha256').update(str).digest('base64'); +} diff --git a/packages/sync-server/src/util/middlewares.js b/packages/sync-server/src/util/middlewares.js new file mode 100644 index 00000000000..14e6e4dc14f --- /dev/null +++ b/packages/sync-server/src/util/middlewares.js @@ -0,0 +1,44 @@ +import validateUser from './validate-user.js'; + +import * as winston from 'winston'; +import * as expressWinston from 'express-winston'; + +/** + * @param {Error} err + * @param {import('express').Request} req + * @param {import('express').Response} res + * @param {import('express').NextFunction} _next + */ +async function errorMiddleware(err, req, res, _next) { + console.log('ERROR', err); + res.status(500).send({ status: 'error', reason: 'internal-error' }); +} + +/** + * @param {import('express').Request} req + * @param {import('express').Response} res + * @param {import('express').NextFunction} next + */ +const validateUserMiddleware = async (req, res, next) => { + let user = await validateUser(req, res); + if (!user) { + return; + } + next(); +}; + +const requestLoggerMiddleware = expressWinston.logger({ + transports: [new winston.transports.Console()], + format: winston.format.combine( + winston.format.colorize(), + winston.format.timestamp(), + winston.format.printf((args) => { + const { timestamp, level, meta } = args; + const { res, req } = meta; + + return `${timestamp} ${level}: ${req.method} ${res.statusCode} ${req.url}`; + }), + ), +}); + +export { validateUserMiddleware, errorMiddleware, requestLoggerMiddleware }; diff --git a/packages/sync-server/src/util/paths.js b/packages/sync-server/src/util/paths.js new file mode 100644 index 00000000000..971bcacd9f5 --- /dev/null +++ b/packages/sync-server/src/util/paths.js @@ -0,0 +1,12 @@ +import { join } from 'node:path'; +import config from '../load-config.js'; + +/** @param {string} fileId */ +export function getPathForUserFile(fileId) { + return join(config.userFiles, `file-${fileId}.blob`); +} + +/** @param {string} groupId */ +export function getPathForGroupFile(groupId) { + return join(config.userFiles, `group-${groupId}.sqlite`); +} diff --git a/packages/sync-server/src/util/payee-name.js b/packages/sync-server/src/util/payee-name.js new file mode 100644 index 00000000000..1967aa49504 --- /dev/null +++ b/packages/sync-server/src/util/payee-name.js @@ -0,0 +1,45 @@ +import { title } from './title/index.js'; + +function formatPayeeIban(iban) { + return '(' + iban.slice(0, 4) + ' XXX ' + iban.slice(-4) + ')'; +} + +export const formatPayeeName = (trans) => { + const amount = trans.transactionAmount.amount; + const nameParts = []; + + // get the correct name and account fields for the transaction amount + let name; + let account; + if (amount > 0 || Object.is(Number(amount), 0)) { + name = trans.debtorName; + account = trans.debtorAccount; + } else { + name = trans.creditorName; + account = trans.creditorAccount; + } + + // use the correct name field if it was found + // if not, use whatever we can find + + // if the primary name option is set, prevent the account from falling back + account = name ? account : trans.debtorAccount || trans.creditorAccount; + + name = + name || + trans.debtorName || + trans.creditorName || + trans.remittanceInformationUnstructured || + (trans.remittanceInformationUnstructuredArray || []).join(', ') || + trans.additionalInformation; + + if (name) { + nameParts.push(title(name)); + } + + if (account && account.iban) { + nameParts.push(formatPayeeIban(account.iban)); + } + + return nameParts.join(' '); +}; diff --git a/packages/sync-server/src/util/prompt.js b/packages/sync-server/src/util/prompt.js new file mode 100644 index 00000000000..f66f0f7605c --- /dev/null +++ b/packages/sync-server/src/util/prompt.js @@ -0,0 +1,88 @@ +import { createInterface, cursorTo } from 'node:readline'; + +export async function prompt(message) { + let rl = createInterface({ + input: process.stdin, + output: process.stdout, + }); + + let promise = new Promise((resolve) => { + rl.question(message, (answer) => { + resolve(answer); + rl.close(); + }); + }); + + let answer = await promise; + + return answer; +} + +export async function promptPassword() { + let password = await askForPassword('Enter a password, then press enter: '); + + if (password === '') { + console.log('Password cannot be empty.'); + return promptPassword(); + } + + let password2 = await askForPassword( + 'Enter the password again, then press enter: ', + ); + + if (password !== password2) { + console.log('Passwords do not match.'); + return promptPassword(); + } + + return password; +} + +async function askForPassword(prompt) { + let dataListener, endListener; + + let promise = new Promise((resolve) => { + let result = ''; + process.stdout.write(prompt); + process.stdin.setRawMode(true); + process.stdin.resume(); + dataListener = (key) => { + switch (key[0]) { + case 0x03: // ^C + process.exit(); + break; + case 0x0d: // Enter + process.stdin.setRawMode(false); + process.stdin.pause(); + resolve(result); + break; + case 0x7f: // Backspace + case 0x08: // Delete + if (result) { + result = result.slice(0, -1); + cursorTo(process.stdout, prompt.length + result.length); + process.stdout.write(' '); + cursorTo(process.stdout, prompt.length + result.length); + } + break; + default: + result += key; + process.stdout.write('*'); + break; + } + }; + process.stdin.on('data', dataListener); + + endListener = () => resolve(result); + process.stdin.on('end', endListener); + }); + + let answer = await promise; + + process.stdin.off('data', dataListener); + process.stdin.off('end', endListener); + + process.stdout.write('\n'); + + return answer; +} diff --git a/packages/sync-server/src/util/title/index.js b/packages/sync-server/src/util/title/index.js new file mode 100644 index 00000000000..a77f48a2583 --- /dev/null +++ b/packages/sync-server/src/util/title/index.js @@ -0,0 +1,59 @@ +// Utilities +import { lowerCaseSet } from './lower-case.js'; +import { specials } from './specials.js'; + +const character = + '[0-9\u0041-\u005A\u0061-\u007A\u00AA\u00B5\u00BA\u00C0-\u00D6\u00D8-\u00F6\u00F8-\u02C1\u02C6-\u02D1\u02E0-\u02E4\u02EC\u02EE\u0370-\u0374\u0376-\u0377\u037A-\u037D\u0386\u0388-\u038A\u038C\u038E-\u03A1\u03A3-\u03F5\u03F7-\u0481\u048A-\u0523\u0531-\u0556\u0559\u0561-\u0587\u05D0-\u05EA\u05F0-\u05F2\u0621-\u064A\u066E-\u066F\u0671-\u06D3\u06D5\u06E5-\u06E6\u06EE-\u06EF\u06FA-\u06FC\u06FF\u0710\u0712-\u072F\u074D-\u07A5\u07B1\u07CA-\u07EA\u07F4-\u07F5\u07FA\u0904-\u0939\u093D\u0950\u0958-\u0961\u0971-\u0972\u097B-\u097F\u0985-\u098C\u098F-\u0990\u0993-\u09A8\u09AA-\u09B0\u09B2\u09B6-\u09B9\u09BD\u09CE\u09DC-\u09DD\u09DF-\u09E1\u09F0-\u09F1\u0A05-\u0A0A\u0A0F-\u0A10\u0A13-\u0A28\u0A2A-\u0A30\u0A32-\u0A33\u0A35-\u0A36\u0A38-\u0A39\u0A59-\u0A5C\u0A5E\u0A72-\u0A74\u0A85-\u0A8D\u0A8F-\u0A91\u0A93-\u0AA8\u0AAA-\u0AB0\u0AB2-\u0AB3\u0AB5-\u0AB9\u0ABD\u0AD0\u0AE0-\u0AE1\u0B05-\u0B0C\u0B0F-\u0B10\u0B13-\u0B28\u0B2A-\u0B30\u0B32-\u0B33\u0B35-\u0B39\u0B3D\u0B5C-\u0B5D\u0B5F-\u0B61\u0B71\u0B83\u0B85-\u0B8A\u0B8E-\u0B90\u0B92-\u0B95\u0B99-\u0B9A\u0B9C\u0B9E-\u0B9F\u0BA3-\u0BA4\u0BA8-\u0BAA\u0BAE-\u0BB9\u0BD0\u0C05-\u0C0C\u0C0E-\u0C10\u0C12-\u0C28\u0C2A-\u0C33\u0C35-\u0C39\u0C3D\u0C58-\u0C59\u0C60-\u0C61\u0C85-\u0C8C\u0C8E-\u0C90\u0C92-\u0CA8\u0CAA-\u0CB3\u0CB5-\u0CB9\u0CBD\u0CDE\u0CE0-\u0CE1\u0D05-\u0D0C\u0D0E-\u0D10\u0D12-\u0D28\u0D2A-\u0D39\u0D3D\u0D60-\u0D61\u0D7A-\u0D7F\u0D85-\u0D96\u0D9A-\u0DB1\u0DB3-\u0DBB\u0DBD\u0DC0-\u0DC6\u0E01-\u0E30\u0E32-\u0E33\u0E40-\u0E46\u0E81-\u0E82\u0E84\u0E87-\u0E88\u0E8A\u0E8D\u0E94-\u0E97\u0E99-\u0E9F\u0EA1-\u0EA3\u0EA5\u0EA7\u0EAA-\u0EAB\u0EAD-\u0EB0\u0EB2-\u0EB3\u0EBD\u0EC0-\u0EC4\u0EC6\u0EDC-\u0EDD\u0F00\u0F40-\u0F47\u0F49-\u0F6C\u0F88-\u0F8B\u1000-\u102A\u103F\u1050-\u1055\u105A-\u105D\u1061\u1065-\u1066\u106E-\u1070\u1075-\u1081\u108E\u10A0-\u10C5\u10D0-\u10FA\u10FC\u1100-\u1159\u115F-\u11A2\u11A8-\u11F9\u1200-\u1248\u124A-\u124D\u1250-\u1256\u1258\u125A-\u125D\u1260-\u1288\u128A-\u128D\u1290-\u12B0\u12B2-\u12B5\u12B8-\u12BE\u12C0\u12C2-\u12C5\u12C8-\u12D6\u12D8-\u1310\u1312-\u1315\u1318-\u135A\u1380-\u138F\u13A0-\u13F4\u1401-\u166C\u166F-\u1676\u1681-\u169A\u16A0-\u16EA\u16EE-\u16F0\u1700-\u170C\u170E-\u1711\u1720-\u1731\u1740-\u1751\u1760-\u176C\u176E-\u1770\u1780-\u17B3\u17D7\u17DC\u1820-\u1877\u1880-\u18A8\u18AA\u1900-\u191C\u1950-\u196D\u1970-\u1974\u1980-\u19A9\u19C1-\u19C7\u1A00-\u1A16\u1B05-\u1B33\u1B45-\u1B4B\u1B83-\u1BA0\u1BAE-\u1BAF\u1C00-\u1C23\u1C4D-\u1C4F\u1C5A-\u1C7D\u1D00-\u1DBF\u1E00-\u1F15\u1F18-\u1F1D\u1F20-\u1F45\u1F48-\u1F4D\u1F50-\u1F57\u1F59\u1F5B\u1F5D\u1F5F-\u1F7D\u1F80-\u1FB4\u1FB6-\u1FBC\u1FBE\u1FC2-\u1FC4\u1FC6-\u1FCC\u1FD0-\u1FD3\u1FD6-\u1FDB\u1FE0-\u1FEC\u1FF2-\u1FF4\u1FF6-\u1FFC\u2071\u207F\u2090-\u2094\u2102\u2107\u210A-\u2113\u2115\u2119-\u211D\u2124\u2126\u2128\u212A-\u212D\u212F-\u2139\u213C-\u213F\u2145-\u2149\u214E\u2160-\u2188\u2C00-\u2C2E\u2C30-\u2C5E\u2C60-\u2C6F\u2C71-\u2C7D\u2C80-\u2CE4\u2D00-\u2D25\u2D30-\u2D65\u2D6F\u2D80-\u2D96\u2DA0-\u2DA6\u2DA8-\u2DAE\u2DB0-\u2DB6\u2DB8-\u2DBE\u2DC0-\u2DC6\u2DC8-\u2DCE\u2DD0-\u2DD6\u2DD8-\u2DDE\u2E2F\u3005-\u3007\u3021-\u3029\u3031-\u3035\u3038-\u303C\u3041-\u3096\u309D-\u309F\u30A1-\u30FA\u30FC-\u30FF\u3105-\u312D\u3131-\u318E\u31A0-\u31B7\u31F0-\u31FF\u3400\u4DB5\u4E00\u9FC3\uA000-\uA48C\uA500-\uA60C\uA610-\uA61F\uA62A-\uA62B\uA640-\uA65F\uA662-\uA66E\uA67F-\uA697\uA717-\uA71F\uA722-\uA788\uA78B-\uA78C\uA7FB-\uA801\uA803-\uA805\uA807-\uA80A\uA80C-\uA822\uA840-\uA873\uA882-\uA8B3\uA90A-\uA925\uA930-\uA946\uAA00-\uAA28\uAA40-\uAA42\uAA44-\uAA4B\uAC00\uD7A3\uF900-\uFA2D\uFA30-\uFA6A\uFA70-\uFAD9\uFB00-\uFB06\uFB13-\uFB17\uFB1D\uFB1F-\uFB28\uFB2A-\uFB36\uFB38-\uFB3C\uFB3E\uFB40-\uFB41\uFB43-\uFB44\uFB46-\uFBB1\uFBD3-\uFD3D\uFD50-\uFD8F\uFD92-\uFDC7\uFDF0-\uFDFB\uFE70-\uFE74\uFE76-\uFEFC\uFF21-\uFF3A\uFF41-\uFF5A\uFF66-\uFFBE\uFFC2-\uFFC7\uFFCA-\uFFCF\uFFD2-\uFFD7\uFFDA-\uFFDC]'; +const regex = new RegExp( + `(?:(?:(\\s?(?:^|[.\\(\\)!?;:"-])\\s*)(${character}))|(${character}))(${character}*[’']*${character}*)`, + 'g', +); + +const convertToRegExp = (specials) => + specials.map((s) => [new RegExp(`\\b${s}\\b`, 'gi'), s]); + +function parseMatch(match) { + const firstCharacter = match[0]; + + // test first character + if (/\s/.test(firstCharacter)) { + // if whitespace - trim and return + return match.substr(1); + } + if (/[()]/.test(firstCharacter)) { + // if parens - this shouldn't be replaced + return null; + } + + return match; +} + +export function title(str, options = { special: undefined }) { + str = str + .toLowerCase() + .replace(regex, (m, lead = '', forced, lower, rest) => { + const parsedMatch = parseMatch(m); + if (!parsedMatch) { + return m; + } + if (!forced) { + const fullLower = lower + rest; + + if (lowerCaseSet.has(fullLower)) { + return parsedMatch; + } + } + + return lead + (lower || forced).toUpperCase() + rest; + }); + + const customSpecials = options.special || []; + const replace = [...specials, ...customSpecials]; + const replaceRegExp = convertToRegExp(replace); + + replaceRegExp.forEach(([pattern, s]) => { + str = str.replace(pattern, s); + }); + + return str; +} diff --git a/packages/sync-server/src/util/title/lower-case.js b/packages/sync-server/src/util/title/lower-case.js new file mode 100644 index 00000000000..eaecf439e77 --- /dev/null +++ b/packages/sync-server/src/util/title/lower-case.js @@ -0,0 +1,93 @@ +const conjunctions = [ + 'for', // + 'and', + 'nor', + 'but', + 'or', + 'yet', + 'so', +]; + +const articles = [ + 'a', // + 'an', + 'the', +]; + +const prepositions = [ + 'aboard', + 'about', + 'above', + 'across', + 'after', + 'against', + 'along', + 'amid', + 'among', + 'anti', + 'around', + 'as', + 'at', + 'before', + 'behind', + 'below', + 'beneath', + 'beside', + 'besides', + 'between', + 'beyond', + 'but', + 'by', + 'concerning', + 'considering', + 'despite', + 'down', + 'during', + 'except', + 'excepting', + 'excluding', + 'following', + 'for', + 'from', + 'in', + 'inside', + 'into', + 'like', + 'minus', + 'near', + 'of', + 'off', + 'on', + 'onto', + 'opposite', + 'over', + 'past', + 'per', + 'plus', + 'regarding', + 'round', + 'save', + 'since', + 'than', + 'through', + 'to', + 'toward', + 'towards', + 'under', + 'underneath', + 'unlike', + 'until', + 'up', + 'upon', + 'versus', + 'via', + 'with', + 'within', + 'without', +]; + +export const lowerCaseSet = new Set([ + ...conjunctions, + ...articles, + ...prepositions, +]); diff --git a/packages/sync-server/src/util/title/specials.js b/packages/sync-server/src/util/title/specials.js new file mode 100644 index 00000000000..bf66c498c45 --- /dev/null +++ b/packages/sync-server/src/util/title/specials.js @@ -0,0 +1,21 @@ +export const specials = [ + 'CLI', + 'API', + 'HTTP', + 'HTTPS', + 'JSX', + 'DNS', + 'URL', + 'CI', + 'CDN', + 'GitHub', + 'CSS', + 'JS', + 'JavaScript', + 'TypeScript', + 'HTML', + 'WordPress', + 'JavaScript', + 'Next.js', + 'Node.js', +]; diff --git a/packages/sync-server/src/util/validate-user.js b/packages/sync-server/src/util/validate-user.js new file mode 100644 index 00000000000..117fb779ba1 --- /dev/null +++ b/packages/sync-server/src/util/validate-user.js @@ -0,0 +1,53 @@ +import { getSession } from '../account-db.js'; +import config from '../load-config.js'; +import proxyaddr from 'proxy-addr'; +import ipaddr from 'ipaddr.js'; + +/** + * @param {import('express').Request} req + * @param {import('express').Response} res + */ +export default function validateUser(req, res) { + let { token } = req.body || {}; + + if (!token) { + token = req.headers['x-actual-token']; + } + + let session = getSession(token); + + if (!session) { + res.status(401); + res.send({ + status: 'error', + reason: 'unauthorized', + details: 'token-not-found', + }); + return null; + } + + return session; +} + +export function validateAuthHeader(req) { + if (config.trustedProxies.length == 0) { + return true; + } + + let sender = proxyaddr(req, 'uniquelocal'); + let sender_ip = ipaddr.process(sender); + const rangeList = { + allowed_ips: config.trustedProxies.map((q) => ipaddr.parseCIDR(q)), + }; + /* eslint-disable @typescript-eslint/ban-ts-comment */ + // @ts-ignore : there is an error in the ts definition for the function, but this is valid + var matched = ipaddr.subnetMatch(sender_ip, rangeList, 'fail'); + /* eslint-enable @typescript-eslint/ban-ts-comment */ + if (matched == 'allowed_ips') { + console.info(`Header Auth Login permitted from ${sender}`); + return true; + } else { + console.warn(`Header Auth Login attempted from ${sender}`); + return false; + } +} diff --git a/packages/sync-server/tsconfig.json b/packages/sync-server/tsconfig.json new file mode 100644 index 00000000000..2a35116984d --- /dev/null +++ b/packages/sync-server/tsconfig.json @@ -0,0 +1,21 @@ +{ + "compilerOptions": { + "target": "ES2022", + // DOM for URL global in Node 16+ + "lib": ["ES2021"], + "allowSyntheticDefaultImports": true, + "esModuleInterop": true, + "experimentalDecorators": true, + "resolveJsonModule": true, + "downlevelIteration": true, + "skipLibCheck": true, + "jsx": "preserve", + // Check JS files too + "allowJs": true, + "checkJs": true, + "moduleResolution": "node16", + "module": "node16", + "outDir": "build" + }, + "exclude": ["node_modules", "build", "./app-plaid.js", "coverage"], +} diff --git a/packages/sync-server/upcoming-release-notes/474.md b/packages/sync-server/upcoming-release-notes/474.md new file mode 100644 index 00000000000..2e2e3623f92 --- /dev/null +++ b/packages/sync-server/upcoming-release-notes/474.md @@ -0,0 +1,6 @@ +--- +category: Bugfix +authors: [davidmartos96] +--- + +Fixes Sabadell Bank regression, by including the date field during normalization diff --git a/packages/sync-server/upcoming-release-notes/487.md b/packages/sync-server/upcoming-release-notes/487.md new file mode 100644 index 00000000000..c0c53da4646 --- /dev/null +++ b/packages/sync-server/upcoming-release-notes/487.md @@ -0,0 +1,6 @@ +--- +category: Bugfix +authors: [MikesGlitch] +--- + +Fix migrations not running properly on inital setup diff --git a/packages/sync-server/upcoming-release-notes/README.md b/packages/sync-server/upcoming-release-notes/README.md new file mode 100644 index 00000000000..988027c3dd3 --- /dev/null +++ b/packages/sync-server/upcoming-release-notes/README.md @@ -0,0 +1 @@ +See the [Writing Good Release Notes](https://actualbudget.org/docs/contributing/#writing-good-release-notes) section of the documentation for more information on how to create a release notes file. diff --git a/yarn.lock b/yarn.lock index 64825ee7264..85c0cdb87ca 100644 --- a/yarn.lock +++ b/yarn.lock @@ -38,7 +38,7 @@ __metadata: languageName: unknown linkType: soft -"@actual-app/crdt@npm:*, @actual-app/crdt@npm:2.1.0, @actual-app/crdt@workspace:^, @actual-app/crdt@workspace:packages/crdt": +"@actual-app/crdt@npm:*, @actual-app/crdt@workspace:^, @actual-app/crdt@workspace:packages/crdt": version: 0.0.0-use.local resolution: "@actual-app/crdt@workspace:packages/crdt" dependencies: @@ -55,14 +55,7 @@ __metadata: languageName: unknown linkType: soft -"@actual-app/web@npm:24.10.1": - version: 24.10.1 - resolution: "@actual-app/web@npm:24.10.1" - checksum: 10/ef5a3c4aa17a4274a7b3faecf7bee9d0845adcdf42a4fab4680224be8ddbf9a2b9e63624a71be5282deba926795b210e84be7fe5185aefeec7ee0c26829b04ea - languageName: node - linkType: hard - -"@actual-app/web@workspace:packages/desktop-client": +"@actual-app/web@npm:*, @actual-app/web@workspace:packages/desktop-client": version: 0.0.0-use.local resolution: "@actual-app/web@workspace:packages/desktop-client" dependencies: @@ -189,6 +182,17 @@ __metadata: languageName: node linkType: hard +"@babel/code-frame@npm:^7.25.9, @babel/code-frame@npm:^7.26.0": + version: 7.26.2 + resolution: "@babel/code-frame@npm:7.26.2" + dependencies: + "@babel/helper-validator-identifier": "npm:^7.25.9" + js-tokens: "npm:^4.0.0" + picocolors: "npm:^1.0.0" + checksum: 10/db2c2122af79d31ca916755331bb4bac96feb2b334cdaca5097a6b467fdd41963b89b14b6836a14f083de7ff887fc78fa1b3c10b14e743d33e12dbfe5ee3d223 + languageName: node + linkType: hard + "@babel/compat-data@npm:^7.22.6, @babel/compat-data@npm:^7.23.3, @babel/compat-data@npm:^7.23.5": version: 7.23.5 resolution: "@babel/compat-data@npm:7.23.5" @@ -196,6 +200,13 @@ __metadata: languageName: node linkType: hard +"@babel/compat-data@npm:^7.25.9": + version: 7.26.2 + resolution: "@babel/compat-data@npm:7.26.2" + checksum: 10/ed9eed6b62ce803ef4a320b1dac76b0302abbb29c49dddf96f3e3207d9717eb34e299a8651bb1582e9c3346ead74b6d595ffced5b3dae718afa08b18741f8402 + languageName: node + linkType: hard + "@babel/core@npm:^7.1.0, @babel/core@npm:^7.11.6, @babel/core@npm:^7.12.3, @babel/core@npm:^7.21.3, @babel/core@npm:^7.24.4, @babel/core@npm:^7.7.2, @babel/core@npm:^7.8.0": version: 7.24.5 resolution: "@babel/core@npm:7.24.5" @@ -219,6 +230,29 @@ __metadata: languageName: node linkType: hard +"@babel/core@npm:^7.23.9": + version: 7.26.0 + resolution: "@babel/core@npm:7.26.0" + dependencies: + "@ampproject/remapping": "npm:^2.2.0" + "@babel/code-frame": "npm:^7.26.0" + "@babel/generator": "npm:^7.26.0" + "@babel/helper-compilation-targets": "npm:^7.25.9" + "@babel/helper-module-transforms": "npm:^7.26.0" + "@babel/helpers": "npm:^7.26.0" + "@babel/parser": "npm:^7.26.0" + "@babel/template": "npm:^7.25.9" + "@babel/traverse": "npm:^7.25.9" + "@babel/types": "npm:^7.26.0" + convert-source-map: "npm:^2.0.0" + debug: "npm:^4.1.0" + gensync: "npm:^1.0.0-beta.2" + json5: "npm:^2.2.3" + semver: "npm:^6.3.1" + checksum: 10/65767bfdb1f02e80d3af4f138066670ef8fdd12293de85ef151758a901c191c797e86d2e99b11c4cdfca33c72385ecaf38bbd7fa692791ec44c77763496b9b93 + languageName: node + linkType: hard + "@babel/generator@npm:^7.24.5, @babel/generator@npm:^7.7.2": version: 7.24.5 resolution: "@babel/generator@npm:7.24.5" @@ -243,6 +277,19 @@ __metadata: languageName: node linkType: hard +"@babel/generator@npm:^7.25.9, @babel/generator@npm:^7.26.0": + version: 7.26.2 + resolution: "@babel/generator@npm:7.26.2" + dependencies: + "@babel/parser": "npm:^7.26.2" + "@babel/types": "npm:^7.26.0" + "@jridgewell/gen-mapping": "npm:^0.3.5" + "@jridgewell/trace-mapping": "npm:^0.3.25" + jsesc: "npm:^3.0.2" + checksum: 10/71ace82b5b07a554846a003624bfab93275ccf73cdb9f1a37a4c1094bf9dc94bb677c67e8b8c939dbd6c5f0eda2e8f268aa2b0d9c3b9511072565660e717e045 + languageName: node + linkType: hard + "@babel/helper-annotate-as-pure@npm:^7.22.5": version: 7.22.5 resolution: "@babel/helper-annotate-as-pure@npm:7.22.5" @@ -252,6 +299,15 @@ __metadata: languageName: node linkType: hard +"@babel/helper-annotate-as-pure@npm:^7.25.9": + version: 7.25.9 + resolution: "@babel/helper-annotate-as-pure@npm:7.25.9" + dependencies: + "@babel/types": "npm:^7.25.9" + checksum: 10/41edda10df1ae106a9b4fe617bf7c6df77db992992afd46192534f5cff29f9e49a303231733782dd65c5f9409714a529f215325569f14282046e9d3b7a1ffb6c + languageName: node + linkType: hard + "@babel/helper-builder-binary-assignment-operator-visitor@npm:^7.22.15": version: 7.22.15 resolution: "@babel/helper-builder-binary-assignment-operator-visitor@npm:7.22.15" @@ -274,6 +330,19 @@ __metadata: languageName: node linkType: hard +"@babel/helper-compilation-targets@npm:^7.25.9": + version: 7.25.9 + resolution: "@babel/helper-compilation-targets@npm:7.25.9" + dependencies: + "@babel/compat-data": "npm:^7.25.9" + "@babel/helper-validator-option": "npm:^7.25.9" + browserslist: "npm:^4.24.0" + lru-cache: "npm:^5.1.1" + semver: "npm:^6.3.1" + checksum: 10/8053fbfc21e8297ab55c8e7f9f119e4809fa7e505268691e1bedc2cf5e7a5a7de8c60ad13da2515378621b7601c42e101d2d679904da395fa3806a1edef6b92e + languageName: node + linkType: hard + "@babel/helper-create-class-features-plugin@npm:^7.22.15": version: 7.23.10 resolution: "@babel/helper-create-class-features-plugin@npm:7.23.10" @@ -293,6 +362,23 @@ __metadata: languageName: node linkType: hard +"@babel/helper-create-class-features-plugin@npm:^7.25.9": + version: 7.25.9 + resolution: "@babel/helper-create-class-features-plugin@npm:7.25.9" + dependencies: + "@babel/helper-annotate-as-pure": "npm:^7.25.9" + "@babel/helper-member-expression-to-functions": "npm:^7.25.9" + "@babel/helper-optimise-call-expression": "npm:^7.25.9" + "@babel/helper-replace-supers": "npm:^7.25.9" + "@babel/helper-skip-transparent-expression-wrappers": "npm:^7.25.9" + "@babel/traverse": "npm:^7.25.9" + semver: "npm:^6.3.1" + peerDependencies: + "@babel/core": ^7.0.0 + checksum: 10/d1d47a7b5fd317c6cb1446b0e4f4892c19ddaa69ea0229f04ba8bea5f273fc8168441e7114ad36ff919f2d310f97310cec51adc79002e22039a7e1640ccaf248 + languageName: node + linkType: hard + "@babel/helper-create-regexp-features-plugin@npm:^7.18.6, @babel/helper-create-regexp-features-plugin@npm:^7.22.15, @babel/helper-create-regexp-features-plugin@npm:^7.22.5": version: 7.22.15 resolution: "@babel/helper-create-regexp-features-plugin@npm:7.22.15" @@ -356,6 +442,16 @@ __metadata: languageName: node linkType: hard +"@babel/helper-member-expression-to-functions@npm:^7.25.9": + version: 7.25.9 + resolution: "@babel/helper-member-expression-to-functions@npm:7.25.9" + dependencies: + "@babel/traverse": "npm:^7.25.9" + "@babel/types": "npm:^7.25.9" + checksum: 10/ef8cc1c1e600b012b312315f843226545a1a89f25d2f474ce2503fd939ca3f8585180f291a3a13efc56cf13eddc1d41a3a040eae9a521838fd59a6d04cc82490 + languageName: node + linkType: hard + "@babel/helper-module-imports@npm:^7.10.4, @babel/helper-module-imports@npm:^7.22.15, @babel/helper-module-imports@npm:^7.24.3": version: 7.24.3 resolution: "@babel/helper-module-imports@npm:7.24.3" @@ -375,6 +471,16 @@ __metadata: languageName: node linkType: hard +"@babel/helper-module-imports@npm:^7.25.9": + version: 7.25.9 + resolution: "@babel/helper-module-imports@npm:7.25.9" + dependencies: + "@babel/traverse": "npm:^7.25.9" + "@babel/types": "npm:^7.25.9" + checksum: 10/e090be5dee94dda6cd769972231b21ddfae988acd76b703a480ac0c96f3334557d70a965bf41245d6ee43891e7571a8b400ccf2b2be5803351375d0f4e5bcf08 + languageName: node + linkType: hard + "@babel/helper-module-transforms@npm:^7.23.3, @babel/helper-module-transforms@npm:^7.24.5": version: 7.24.5 resolution: "@babel/helper-module-transforms@npm:7.24.5" @@ -390,6 +496,19 @@ __metadata: languageName: node linkType: hard +"@babel/helper-module-transforms@npm:^7.25.9, @babel/helper-module-transforms@npm:^7.26.0": + version: 7.26.0 + resolution: "@babel/helper-module-transforms@npm:7.26.0" + dependencies: + "@babel/helper-module-imports": "npm:^7.25.9" + "@babel/helper-validator-identifier": "npm:^7.25.9" + "@babel/traverse": "npm:^7.25.9" + peerDependencies: + "@babel/core": ^7.0.0 + checksum: 10/9841d2a62f61ad52b66a72d08264f23052d533afc4ce07aec2a6202adac0bfe43014c312f94feacb3291f4c5aafe681955610041ece2c276271adce3f570f2f5 + languageName: node + linkType: hard + "@babel/helper-optimise-call-expression@npm:^7.22.5": version: 7.22.5 resolution: "@babel/helper-optimise-call-expression@npm:7.22.5" @@ -399,6 +518,15 @@ __metadata: languageName: node linkType: hard +"@babel/helper-optimise-call-expression@npm:^7.25.9": + version: 7.25.9 + resolution: "@babel/helper-optimise-call-expression@npm:7.25.9" + dependencies: + "@babel/types": "npm:^7.25.9" + checksum: 10/f09d0ad60c0715b9a60c31841b3246b47d67650c512ce85bbe24a3124f1a4d66377df793af393273bc6e1015b0a9c799626c48e53747581c1582b99167cc65dc + languageName: node + linkType: hard + "@babel/helper-plugin-utils@npm:^7.0.0, @babel/helper-plugin-utils@npm:^7.10.4, @babel/helper-plugin-utils@npm:^7.12.13, @babel/helper-plugin-utils@npm:^7.14.5, @babel/helper-plugin-utils@npm:^7.18.6, @babel/helper-plugin-utils@npm:^7.22.5, @babel/helper-plugin-utils@npm:^7.8.0, @babel/helper-plugin-utils@npm:^7.8.3": version: 7.22.5 resolution: "@babel/helper-plugin-utils@npm:7.22.5" @@ -406,6 +534,13 @@ __metadata: languageName: node linkType: hard +"@babel/helper-plugin-utils@npm:^7.25.9": + version: 7.25.9 + resolution: "@babel/helper-plugin-utils@npm:7.25.9" + checksum: 10/e347d87728b1ab10b6976d46403941c8f9008c045ea6d99997a7ffca7b852dc34b6171380f7b17edf94410e0857ff26f3a53d8618f11d73744db86e8ca9b8c64 + languageName: node + linkType: hard + "@babel/helper-remap-async-to-generator@npm:^7.22.20": version: 7.22.20 resolution: "@babel/helper-remap-async-to-generator@npm:7.22.20" @@ -432,6 +567,19 @@ __metadata: languageName: node linkType: hard +"@babel/helper-replace-supers@npm:^7.25.9": + version: 7.25.9 + resolution: "@babel/helper-replace-supers@npm:7.25.9" + dependencies: + "@babel/helper-member-expression-to-functions": "npm:^7.25.9" + "@babel/helper-optimise-call-expression": "npm:^7.25.9" + "@babel/traverse": "npm:^7.25.9" + peerDependencies: + "@babel/core": ^7.0.0 + checksum: 10/8ebf787016953e4479b99007bac735c9c860822fafc51bc3db67bc53814539888797238c81fa8b948b6da897eb7b1c1d4f04df11e501a7f0596b356be02de2ab + languageName: node + linkType: hard + "@babel/helper-simple-access@npm:^7.22.5, @babel/helper-simple-access@npm:^7.24.5": version: 7.24.5 resolution: "@babel/helper-simple-access@npm:7.24.5" @@ -441,6 +589,16 @@ __metadata: languageName: node linkType: hard +"@babel/helper-simple-access@npm:^7.25.9": + version: 7.25.9 + resolution: "@babel/helper-simple-access@npm:7.25.9" + dependencies: + "@babel/traverse": "npm:^7.25.9" + "@babel/types": "npm:^7.25.9" + checksum: 10/a16a6cfa5e8ac7144e856bcdaaf0022cf5de028fc0c56ce21dd664a6e900999a4285c587a209f2acf9de438c0d60bfb497f5f34aa34cbaf29da3e2f8d8d7feb7 + languageName: node + linkType: hard + "@babel/helper-skip-transparent-expression-wrappers@npm:^7.22.5": version: 7.22.5 resolution: "@babel/helper-skip-transparent-expression-wrappers@npm:7.22.5" @@ -450,6 +608,16 @@ __metadata: languageName: node linkType: hard +"@babel/helper-skip-transparent-expression-wrappers@npm:^7.25.9": + version: 7.25.9 + resolution: "@babel/helper-skip-transparent-expression-wrappers@npm:7.25.9" + dependencies: + "@babel/traverse": "npm:^7.25.9" + "@babel/types": "npm:^7.25.9" + checksum: 10/fdbb5248932198bc26daa6abf0d2ac42cab9c2dbb75b7e9f40d425c8f28f09620b886d40e7f9e4e08ffc7aaa2cefe6fc2c44be7c20e81f7526634702fb615bdc + languageName: node + linkType: hard + "@babel/helper-split-export-declaration@npm:^7.22.6, @babel/helper-split-export-declaration@npm:^7.24.5": version: 7.24.5 resolution: "@babel/helper-split-export-declaration@npm:7.24.5" @@ -473,6 +641,13 @@ __metadata: languageName: node linkType: hard +"@babel/helper-string-parser@npm:^7.25.9": + version: 7.25.9 + resolution: "@babel/helper-string-parser@npm:7.25.9" + checksum: 10/c28656c52bd48e8c1d9f3e8e68ecafd09d949c57755b0d353739eb4eae7ba4f7e67e92e4036f1cd43378cc1397a2c943ed7bcaf5949b04ab48607def0258b775 + languageName: node + linkType: hard + "@babel/helper-validator-identifier@npm:^7.22.20, @babel/helper-validator-identifier@npm:^7.24.5, @babel/helper-validator-identifier@npm:^7.24.7": version: 7.24.7 resolution: "@babel/helper-validator-identifier@npm:7.24.7" @@ -480,6 +655,13 @@ __metadata: languageName: node linkType: hard +"@babel/helper-validator-identifier@npm:^7.25.9": + version: 7.25.9 + resolution: "@babel/helper-validator-identifier@npm:7.25.9" + checksum: 10/3f9b649be0c2fd457fa1957b694b4e69532a668866b8a0d81eabfa34ba16dbf3107b39e0e7144c55c3c652bf773ec816af8df4a61273a2bb4eb3145ca9cf478e + languageName: node + linkType: hard + "@babel/helper-validator-option@npm:^7.23.5": version: 7.23.5 resolution: "@babel/helper-validator-option@npm:7.23.5" @@ -487,6 +669,13 @@ __metadata: languageName: node linkType: hard +"@babel/helper-validator-option@npm:^7.25.9": + version: 7.25.9 + resolution: "@babel/helper-validator-option@npm:7.25.9" + checksum: 10/9491b2755948ebbdd68f87da907283698e663b5af2d2b1b02a2765761974b1120d5d8d49e9175b167f16f72748ffceec8c9cf62acfbee73f4904507b246e2b3d + languageName: node + linkType: hard + "@babel/helper-wrap-function@npm:^7.22.20": version: 7.22.20 resolution: "@babel/helper-wrap-function@npm:7.22.20" @@ -509,6 +698,16 @@ __metadata: languageName: node linkType: hard +"@babel/helpers@npm:^7.26.0": + version: 7.26.0 + resolution: "@babel/helpers@npm:7.26.0" + dependencies: + "@babel/template": "npm:^7.25.9" + "@babel/types": "npm:^7.26.0" + checksum: 10/fd4757f65d10b64cfdbf4b3adb7ea6ffff9497c53e0786452f495d1f7794da7e0898261b4db65e1c62bbb9a360d7d78a1085635c23dfc3af2ab6dcba06585f86 + languageName: node + linkType: hard + "@babel/highlight@npm:^7.24.2": version: 7.24.5 resolution: "@babel/highlight@npm:7.24.5" @@ -544,6 +743,17 @@ __metadata: languageName: node linkType: hard +"@babel/parser@npm:^7.23.9, @babel/parser@npm:^7.25.9, @babel/parser@npm:^7.26.0, @babel/parser@npm:^7.26.2": + version: 7.26.2 + resolution: "@babel/parser@npm:7.26.2" + dependencies: + "@babel/types": "npm:^7.26.0" + bin: + parser: ./bin/babel-parser.js + checksum: 10/8baee43752a3678ad9f9e360ec845065eeee806f1fdc8e0f348a8a0e13eef0959dabed4a197c978896c493ea205c804d0a1187cc52e4a1ba017c7935bab4983d + languageName: node + linkType: hard + "@babel/parser@npm:^7.24.0": version: 7.24.5 resolution: "@babel/parser@npm:7.24.5" @@ -708,6 +918,17 @@ __metadata: languageName: node linkType: hard +"@babel/plugin-syntax-jsx@npm:^7.25.9": + version: 7.25.9 + resolution: "@babel/plugin-syntax-jsx@npm:7.25.9" + dependencies: + "@babel/helper-plugin-utils": "npm:^7.25.9" + peerDependencies: + "@babel/core": ^7.0.0-0 + checksum: 10/bb609d1ffb50b58f0c1bac8810d0e46a4f6c922aa171c458f3a19d66ee545d36e782d3bffbbc1fed0dc65a558bdce1caf5279316583c0fff5a2c1658982a8563 + languageName: node + linkType: hard + "@babel/plugin-syntax-jsx@npm:^7.7.2": version: 7.22.5 resolution: "@babel/plugin-syntax-jsx@npm:7.22.5" @@ -807,6 +1028,17 @@ __metadata: languageName: node linkType: hard +"@babel/plugin-syntax-typescript@npm:^7.25.9": + version: 7.25.9 + resolution: "@babel/plugin-syntax-typescript@npm:7.25.9" + dependencies: + "@babel/helper-plugin-utils": "npm:^7.25.9" + peerDependencies: + "@babel/core": ^7.0.0-0 + checksum: 10/0e9821e8ba7d660c36c919654e4144a70546942ae184e85b8102f2322451eae102cbfadbcadd52ce077a2b44b400ee52394c616feab7b5b9f791b910e933fd33 + languageName: node + linkType: hard + "@babel/plugin-syntax-typescript@npm:^7.7.2": version: 7.22.5 resolution: "@babel/plugin-syntax-typescript@npm:7.22.5" @@ -1111,6 +1343,19 @@ __metadata: languageName: node linkType: hard +"@babel/plugin-transform-modules-commonjs@npm:^7.25.9": + version: 7.25.9 + resolution: "@babel/plugin-transform-modules-commonjs@npm:7.25.9" + dependencies: + "@babel/helper-module-transforms": "npm:^7.25.9" + "@babel/helper-plugin-utils": "npm:^7.25.9" + "@babel/helper-simple-access": "npm:^7.25.9" + peerDependencies: + "@babel/core": ^7.0.0-0 + checksum: 10/a7390ca999373ccdef91075f274d1ace3a5cb79f9b9118ed6f76e94867ed454cf798a6f312ce2c4cdc1e035a25d810d754e4cb2e4d866acb4219490f3585de60 + languageName: node + linkType: hard + "@babel/plugin-transform-modules-systemjs@npm:^7.23.9": version: 7.23.9 resolution: "@babel/plugin-transform-modules-systemjs@npm:7.23.9" @@ -1363,6 +1608,21 @@ __metadata: languageName: node linkType: hard +"@babel/plugin-transform-typescript@npm:^7.25.9": + version: 7.25.9 + resolution: "@babel/plugin-transform-typescript@npm:7.25.9" + dependencies: + "@babel/helper-annotate-as-pure": "npm:^7.25.9" + "@babel/helper-create-class-features-plugin": "npm:^7.25.9" + "@babel/helper-plugin-utils": "npm:^7.25.9" + "@babel/helper-skip-transparent-expression-wrappers": "npm:^7.25.9" + "@babel/plugin-syntax-typescript": "npm:^7.25.9" + peerDependencies: + "@babel/core": ^7.0.0-0 + checksum: 10/91e2ec805f89a813e0bf9cf42dffb767f798429e983af3e2f919885a2826b10f29223dd8b40ccc569eb61858d3273620e82e14431603a893e4a7f9b4c1a3a3cf + languageName: node + linkType: hard + "@babel/plugin-transform-unicode-escapes@npm:^7.23.3": version: 7.23.3 resolution: "@babel/plugin-transform-unicode-escapes@npm:7.23.3" @@ -1513,6 +1773,21 @@ __metadata: languageName: node linkType: hard +"@babel/preset-typescript@npm:^7.20.2": + version: 7.26.0 + resolution: "@babel/preset-typescript@npm:7.26.0" + dependencies: + "@babel/helper-plugin-utils": "npm:^7.25.9" + "@babel/helper-validator-option": "npm:^7.25.9" + "@babel/plugin-syntax-jsx": "npm:^7.25.9" + "@babel/plugin-transform-modules-commonjs": "npm:^7.25.9" + "@babel/plugin-transform-typescript": "npm:^7.25.9" + peerDependencies: + "@babel/core": ^7.0.0-0 + checksum: 10/81a60826160163a3daae017709f42147744757b725b50c9024ef3ee5a402ee45fd2e93eaecdaaa22c81be91f7940916249cfb7711366431cfcacc69c95878c03 + languageName: node + linkType: hard + "@babel/regjsgen@npm:^0.8.0": version: 0.8.0 resolution: "@babel/regjsgen@npm:0.8.0" @@ -1560,6 +1835,17 @@ __metadata: languageName: node linkType: hard +"@babel/template@npm:^7.25.9": + version: 7.25.9 + resolution: "@babel/template@npm:7.25.9" + dependencies: + "@babel/code-frame": "npm:^7.25.9" + "@babel/parser": "npm:^7.25.9" + "@babel/types": "npm:^7.25.9" + checksum: 10/e861180881507210150c1335ad94aff80fd9e9be6202e1efa752059c93224e2d5310186ddcdd4c0f0b0fc658ce48cb47823f15142b5c00c8456dde54f5de80b2 + languageName: node + linkType: hard + "@babel/traverse@npm:^7.24.5, @babel/traverse@npm:^7.7.2": version: 7.24.5 resolution: "@babel/traverse@npm:7.24.5" @@ -1593,6 +1879,21 @@ __metadata: languageName: node linkType: hard +"@babel/traverse@npm:^7.25.9": + version: 7.25.9 + resolution: "@babel/traverse@npm:7.25.9" + dependencies: + "@babel/code-frame": "npm:^7.25.9" + "@babel/generator": "npm:^7.25.9" + "@babel/parser": "npm:^7.25.9" + "@babel/template": "npm:^7.25.9" + "@babel/types": "npm:^7.25.9" + debug: "npm:^4.3.1" + globals: "npm:^11.1.0" + checksum: 10/7431614d76d4a053e429208db82f2846a415833f3d9eb2e11ef72eeb3c64dfd71f4a4d983de1a4a047b36165a1f5a64de8ca2a417534cc472005c740ffcb9c6a + languageName: node + linkType: hard + "@babel/types@npm:^7.0.0, @babel/types@npm:^7.20.7, @babel/types@npm:^7.21.3, @babel/types@npm:^7.22.15, @babel/types@npm:^7.22.19, @babel/types@npm:^7.22.5, @babel/types@npm:^7.23.0, @babel/types@npm:^7.24.5, @babel/types@npm:^7.24.7, @babel/types@npm:^7.25.0, @babel/types@npm:^7.25.6, @babel/types@npm:^7.3.0, @babel/types@npm:^7.3.3, @babel/types@npm:^7.4.4": version: 7.25.6 resolution: "@babel/types@npm:7.25.6" @@ -1615,6 +1916,16 @@ __metadata: languageName: node linkType: hard +"@babel/types@npm:^7.25.9, @babel/types@npm:^7.26.0": + version: 7.26.0 + resolution: "@babel/types@npm:7.26.0" + dependencies: + "@babel/helper-string-parser": "npm:^7.25.9" + "@babel/helper-validator-identifier": "npm:^7.25.9" + checksum: 10/40780741ecec886ed9edae234b5eb4976968cc70d72b4e5a40d55f83ff2cc457de20f9b0f4fe9d858350e43dab0ea496e7ef62e2b2f08df699481a76df02cd6e + languageName: node + linkType: hard + "@bcoe/v8-coverage@npm:^0.2.3": version: 0.2.3 resolution: "@bcoe/v8-coverage@npm:0.2.3" @@ -2217,6 +2528,13 @@ __metadata: languageName: node linkType: hard +"@eslint-community/regexpp@npm:^4.4.0": + version: 4.12.1 + resolution: "@eslint-community/regexpp@npm:4.12.1" + checksum: 10/c08f1dd7dd18fbb60bdd0d85820656d1374dd898af9be7f82cb00451313402a22d5e30569c150315b4385907cdbca78c22389b2a72ab78883b3173be317620cc + languageName: node + linkType: hard + "@eslint/eslintrc@npm:^2.1.4": version: 2.1.4 resolution: "@eslint/eslintrc@npm:2.1.4" @@ -2241,6 +2559,13 @@ __metadata: languageName: node linkType: hard +"@eslint/js@npm:8.57.1": + version: 8.57.1 + resolution: "@eslint/js@npm:8.57.1" + checksum: 10/7562b21be10c2adbfa4aa5bb2eccec2cb9ac649a3569560742202c8d1cb6c931ce634937a2f0f551e078403a1c1285d6c2c0aa345dafc986149665cd69fe8b59 + languageName: node + linkType: hard + "@fontsource/redacted-script@npm:^5.0.21": version: 5.0.21 resolution: "@fontsource/redacted-script@npm:5.0.21" @@ -2340,6 +2665,17 @@ __metadata: languageName: node linkType: hard +"@humanwhocodes/config-array@npm:^0.13.0": + version: 0.13.0 + resolution: "@humanwhocodes/config-array@npm:0.13.0" + dependencies: + "@humanwhocodes/object-schema": "npm:^2.0.3" + debug: "npm:^4.3.1" + minimatch: "npm:^3.0.5" + checksum: 10/524df31e61a85392a2433bf5d03164e03da26c03d009f27852e7dcfdafbc4a23f17f021dacf88e0a7a9fe04ca032017945d19b57a16e2676d9114c22a53a9d11 + languageName: node + linkType: hard + "@humanwhocodes/module-importer@npm:^1.0.1": version: 1.0.1 resolution: "@humanwhocodes/module-importer@npm:1.0.1" @@ -2347,7 +2683,7 @@ __metadata: languageName: node linkType: hard -"@humanwhocodes/object-schema@npm:^2.0.2": +"@humanwhocodes/object-schema@npm:^2.0.2, @humanwhocodes/object-schema@npm:^2.0.3": version: 2.0.3 resolution: "@humanwhocodes/object-schema@npm:2.0.3" checksum: 10/05bb99ed06c16408a45a833f03a732f59bf6184795d4efadd33238ff8699190a8c871ad1121241bb6501589a9598dc83bf25b99dcbcf41e155cdf36e35e937a3 @@ -2418,7 +2754,7 @@ __metadata: languageName: node linkType: hard -"@istanbuljs/schema@npm:^0.1.2": +"@istanbuljs/schema@npm:^0.1.2, @istanbuljs/schema@npm:^0.1.3": version: 0.1.3 resolution: "@istanbuljs/schema@npm:0.1.3" checksum: 10/a9b1e49acdf5efc2f5b2359f2df7f90c5c725f2656f16099e8b2cd3a000619ecca9fc48cf693ba789cf0fd989f6e0df6a22bc05574be4223ecdbb7997d04384b @@ -2453,6 +2789,20 @@ __metadata: languageName: node linkType: hard +"@jest/console@npm:^29.7.0": + version: 29.7.0 + resolution: "@jest/console@npm:29.7.0" + dependencies: + "@jest/types": "npm:^29.6.3" + "@types/node": "npm:*" + chalk: "npm:^4.0.0" + jest-message-util: "npm:^29.7.0" + jest-util: "npm:^29.7.0" + slash: "npm:^3.0.0" + checksum: 10/4a80c750e8a31f344233cb9951dee9b77bf6b89377cb131f8b3cde07ff218f504370133a5963f6a786af4d2ce7f85642db206ff7a15f99fe58df4c38ac04899e + languageName: node + linkType: hard + "@jest/core@npm:^27.5.1": version: 27.5.1 resolution: "@jest/core@npm:27.5.1" @@ -2494,6 +2844,47 @@ __metadata: languageName: node linkType: hard +"@jest/core@npm:^29.7.0": + version: 29.7.0 + resolution: "@jest/core@npm:29.7.0" + dependencies: + "@jest/console": "npm:^29.7.0" + "@jest/reporters": "npm:^29.7.0" + "@jest/test-result": "npm:^29.7.0" + "@jest/transform": "npm:^29.7.0" + "@jest/types": "npm:^29.6.3" + "@types/node": "npm:*" + ansi-escapes: "npm:^4.2.1" + chalk: "npm:^4.0.0" + ci-info: "npm:^3.2.0" + exit: "npm:^0.1.2" + graceful-fs: "npm:^4.2.9" + jest-changed-files: "npm:^29.7.0" + jest-config: "npm:^29.7.0" + jest-haste-map: "npm:^29.7.0" + jest-message-util: "npm:^29.7.0" + jest-regex-util: "npm:^29.6.3" + jest-resolve: "npm:^29.7.0" + jest-resolve-dependencies: "npm:^29.7.0" + jest-runner: "npm:^29.7.0" + jest-runtime: "npm:^29.7.0" + jest-snapshot: "npm:^29.7.0" + jest-util: "npm:^29.7.0" + jest-validate: "npm:^29.7.0" + jest-watcher: "npm:^29.7.0" + micromatch: "npm:^4.0.4" + pretty-format: "npm:^29.7.0" + slash: "npm:^3.0.0" + strip-ansi: "npm:^6.0.0" + peerDependencies: + node-notifier: ^8.0.1 || ^9.0.0 || ^10.0.0 + peerDependenciesMeta: + node-notifier: + optional: true + checksum: 10/ab6ac2e562d083faac7d8152ec1cc4eccc80f62e9579b69ed40aedf7211a6b2d57024a6cd53c4e35fd051c39a236e86257d1d99ebdb122291969a0a04563b51e + languageName: node + linkType: hard + "@jest/create-cache-key-function@npm:^29.7.0": version: 29.7.0 resolution: "@jest/create-cache-key-function@npm:29.7.0" @@ -2515,6 +2906,18 @@ __metadata: languageName: node linkType: hard +"@jest/environment@npm:^29.7.0": + version: 29.7.0 + resolution: "@jest/environment@npm:29.7.0" + dependencies: + "@jest/fake-timers": "npm:^29.7.0" + "@jest/types": "npm:^29.6.3" + "@types/node": "npm:*" + jest-mock: "npm:^29.7.0" + checksum: 10/90b5844a9a9d8097f2cf107b1b5e57007c552f64315da8c1f51217eeb0a9664889d3f145cdf8acf23a84f4d8309a6675e27d5b059659a004db0ea9546d1c81a8 + languageName: node + linkType: hard + "@jest/expect-utils@npm:^29.5.0": version: 29.5.0 resolution: "@jest/expect-utils@npm:29.5.0" @@ -2524,6 +2927,25 @@ __metadata: languageName: node linkType: hard +"@jest/expect-utils@npm:^29.7.0": + version: 29.7.0 + resolution: "@jest/expect-utils@npm:29.7.0" + dependencies: + jest-get-type: "npm:^29.6.3" + checksum: 10/ef8d379778ef574a17bde2801a6f4469f8022a46a5f9e385191dc73bb1fc318996beaed4513fbd7055c2847227a1bed2469977821866534593a6e52a281499ee + languageName: node + linkType: hard + +"@jest/expect@npm:^29.7.0": + version: 29.7.0 + resolution: "@jest/expect@npm:29.7.0" + dependencies: + expect: "npm:^29.7.0" + jest-snapshot: "npm:^29.7.0" + checksum: 10/fea6c3317a8da5c840429d90bfe49d928e89c9e89fceee2149b93a11b7e9c73d2f6e4d7cdf647163da938fc4e2169e4490be6bae64952902bc7a701033fd4880 + languageName: node + linkType: hard + "@jest/fake-timers@npm:^27.5.1": version: 27.5.1 resolution: "@jest/fake-timers@npm:27.5.1" @@ -2538,6 +2960,20 @@ __metadata: languageName: node linkType: hard +"@jest/fake-timers@npm:^29.7.0": + version: 29.7.0 + resolution: "@jest/fake-timers@npm:29.7.0" + dependencies: + "@jest/types": "npm:^29.6.3" + "@sinonjs/fake-timers": "npm:^10.0.2" + "@types/node": "npm:*" + jest-message-util: "npm:^29.7.0" + jest-mock: "npm:^29.7.0" + jest-util: "npm:^29.7.0" + checksum: 10/9b394e04ffc46f91725ecfdff34c4e043eb7a16e1d78964094c9db3fde0b1c8803e45943a980e8c740d0a3d45661906de1416ca5891a538b0660481a3a828c27 + languageName: node + linkType: hard + "@jest/globals@npm:^27.5.1": version: 27.5.1 resolution: "@jest/globals@npm:27.5.1" @@ -2549,6 +2985,18 @@ __metadata: languageName: node linkType: hard +"@jest/globals@npm:^29.7.0": + version: 29.7.0 + resolution: "@jest/globals@npm:29.7.0" + dependencies: + "@jest/environment": "npm:^29.7.0" + "@jest/expect": "npm:^29.7.0" + "@jest/types": "npm:^29.6.3" + jest-mock: "npm:^29.7.0" + checksum: 10/97dbb9459135693ad3a422e65ca1c250f03d82b2a77f6207e7fa0edd2c9d2015fbe4346f3dc9ebff1678b9d8da74754d4d440b7837497f8927059c0642a22123 + languageName: node + linkType: hard + "@jest/reporters@npm:^27.5.1": version: 27.5.1 resolution: "@jest/reporters@npm:27.5.1" @@ -2587,6 +3035,43 @@ __metadata: languageName: node linkType: hard +"@jest/reporters@npm:^29.7.0": + version: 29.7.0 + resolution: "@jest/reporters@npm:29.7.0" + dependencies: + "@bcoe/v8-coverage": "npm:^0.2.3" + "@jest/console": "npm:^29.7.0" + "@jest/test-result": "npm:^29.7.0" + "@jest/transform": "npm:^29.7.0" + "@jest/types": "npm:^29.6.3" + "@jridgewell/trace-mapping": "npm:^0.3.18" + "@types/node": "npm:*" + chalk: "npm:^4.0.0" + collect-v8-coverage: "npm:^1.0.0" + exit: "npm:^0.1.2" + glob: "npm:^7.1.3" + graceful-fs: "npm:^4.2.9" + istanbul-lib-coverage: "npm:^3.0.0" + istanbul-lib-instrument: "npm:^6.0.0" + istanbul-lib-report: "npm:^3.0.0" + istanbul-lib-source-maps: "npm:^4.0.0" + istanbul-reports: "npm:^3.1.3" + jest-message-util: "npm:^29.7.0" + jest-util: "npm:^29.7.0" + jest-worker: "npm:^29.7.0" + slash: "npm:^3.0.0" + string-length: "npm:^4.0.1" + strip-ansi: "npm:^6.0.0" + v8-to-istanbul: "npm:^9.0.1" + peerDependencies: + node-notifier: ^8.0.1 || ^9.0.0 || ^10.0.0 + peerDependenciesMeta: + node-notifier: + optional: true + checksum: 10/a17d1644b26dea14445cedd45567f4ba7834f980be2ef74447204e14238f121b50d8b858fde648083d2cd8f305f81ba434ba49e37a5f4237a6f2a61180cc73dc + languageName: node + linkType: hard + "@jest/schemas@npm:^29.6.3": version: 29.6.3 resolution: "@jest/schemas@npm:29.6.3" @@ -2607,6 +3092,17 @@ __metadata: languageName: node linkType: hard +"@jest/source-map@npm:^29.6.3": + version: 29.6.3 + resolution: "@jest/source-map@npm:29.6.3" + dependencies: + "@jridgewell/trace-mapping": "npm:^0.3.18" + callsites: "npm:^3.0.0" + graceful-fs: "npm:^4.2.9" + checksum: 10/bcc5a8697d471396c0003b0bfa09722c3cd879ad697eb9c431e6164e2ea7008238a01a07193dfe3cbb48b1d258eb7251f6efcea36f64e1ebc464ea3c03ae2deb + languageName: node + linkType: hard + "@jest/test-result@npm:^27.5.1": version: 27.5.1 resolution: "@jest/test-result@npm:27.5.1" @@ -2631,6 +3127,18 @@ __metadata: languageName: node linkType: hard +"@jest/test-result@npm:^29.7.0": + version: 29.7.0 + resolution: "@jest/test-result@npm:29.7.0" + dependencies: + "@jest/console": "npm:^29.7.0" + "@jest/types": "npm:^29.6.3" + "@types/istanbul-lib-coverage": "npm:^2.0.0" + collect-v8-coverage: "npm:^1.0.0" + checksum: 10/c073ab7dfe3c562bff2b8fee6cc724ccc20aa96bcd8ab48ccb2aa309b4c0c1923a9e703cea386bd6ae9b71133e92810475bb9c7c22328fc63f797ad3324ed189 + languageName: node + linkType: hard + "@jest/test-sequencer@npm:^27.5.1": version: 27.5.1 resolution: "@jest/test-sequencer@npm:27.5.1" @@ -2643,6 +3151,18 @@ __metadata: languageName: node linkType: hard +"@jest/test-sequencer@npm:^29.7.0": + version: 29.7.0 + resolution: "@jest/test-sequencer@npm:29.7.0" + dependencies: + "@jest/test-result": "npm:^29.7.0" + graceful-fs: "npm:^4.2.9" + jest-haste-map: "npm:^29.7.0" + slash: "npm:^3.0.0" + checksum: 10/4420c26a0baa7035c5419b0892ff8ffe9a41b1583ec54a10db3037cd46a7e29dd3d7202f8aa9d376e9e53be5f8b1bc0d16e1de6880a6d319b033b01dc4c8f639 + languageName: node + linkType: hard + "@jest/transform@npm:^27.5.1": version: 27.5.1 resolution: "@jest/transform@npm:27.5.1" @@ -2689,6 +3209,29 @@ __metadata: languageName: node linkType: hard +"@jest/transform@npm:^29.7.0": + version: 29.7.0 + resolution: "@jest/transform@npm:29.7.0" + dependencies: + "@babel/core": "npm:^7.11.6" + "@jest/types": "npm:^29.6.3" + "@jridgewell/trace-mapping": "npm:^0.3.18" + babel-plugin-istanbul: "npm:^6.1.1" + chalk: "npm:^4.0.0" + convert-source-map: "npm:^2.0.0" + fast-json-stable-stringify: "npm:^2.1.0" + graceful-fs: "npm:^4.2.9" + jest-haste-map: "npm:^29.7.0" + jest-regex-util: "npm:^29.6.3" + jest-util: "npm:^29.7.0" + micromatch: "npm:^4.0.4" + pirates: "npm:^4.0.4" + slash: "npm:^3.0.0" + write-file-atomic: "npm:^4.0.2" + checksum: 10/30f42293545ab037d5799c81d3e12515790bb58513d37f788ce32d53326d0d72ebf5b40f989e6896739aa50a5f77be44686e510966370d58511d5ad2637c68c1 + languageName: node + linkType: hard + "@jest/types@npm:^27.5.1": version: 27.5.1 resolution: "@jest/types@npm:27.5.1" @@ -2775,7 +3318,7 @@ __metadata: languageName: node linkType: hard -"@jridgewell/trace-mapping@npm:^0.3.15, @jridgewell/trace-mapping@npm:^0.3.20, @jridgewell/trace-mapping@npm:^0.3.24, @jridgewell/trace-mapping@npm:^0.3.25, @jridgewell/trace-mapping@npm:^0.3.9": +"@jridgewell/trace-mapping@npm:^0.3.12, @jridgewell/trace-mapping@npm:^0.3.15, @jridgewell/trace-mapping@npm:^0.3.18, @jridgewell/trace-mapping@npm:^0.3.20, @jridgewell/trace-mapping@npm:^0.3.24, @jridgewell/trace-mapping@npm:^0.3.25, @jridgewell/trace-mapping@npm:^0.3.9": version: 0.3.25 resolution: "@jridgewell/trace-mapping@npm:0.3.25" dependencies: @@ -5026,6 +5569,24 @@ __metadata: languageName: node linkType: hard +"@sinonjs/commons@npm:^3.0.0": + version: 3.0.1 + resolution: "@sinonjs/commons@npm:3.0.1" + dependencies: + type-detect: "npm:4.0.8" + checksum: 10/a0af217ba7044426c78df52c23cedede6daf377586f3ac58857c565769358ab1f44ebf95ba04bbe38814fba6e316ca6f02870a009328294fc2c555d0f85a7117 + languageName: node + linkType: hard + +"@sinonjs/fake-timers@npm:^10.0.2": + version: 10.3.0 + resolution: "@sinonjs/fake-timers@npm:10.3.0" + dependencies: + "@sinonjs/commons": "npm:^3.0.0" + checksum: 10/78155c7bd866a85df85e22028e046b8d46cf3e840f72260954f5e3ed5bd97d66c595524305a6841ffb3f681a08f6e5cef572a2cce5442a8a232dc29fb409b83e + languageName: node + linkType: hard + "@sinonjs/fake-timers@npm:^8.0.1": version: 8.1.0 resolution: "@sinonjs/fake-timers@npm:8.1.0" @@ -5534,6 +6095,24 @@ __metadata: languageName: node linkType: hard +"@types/bcrypt@npm:^5.0.2": + version: 5.0.2 + resolution: "@types/bcrypt@npm:5.0.2" + dependencies: + "@types/node": "npm:*" + checksum: 10/b1f97532ffe6079cb57a464f28b5b37a30bc9620f43469e1f27ab9c979c8a114be5b667e7b115a5556fd5be463b65968da9bb32573c6faf74fecf6e565d8974b + languageName: node + linkType: hard + +"@types/better-sqlite3@npm:^7.6.7": + version: 7.6.11 + resolution: "@types/better-sqlite3@npm:7.6.11" + dependencies: + "@types/node": "npm:*" + checksum: 10/660f29485803e8f1a443b6009c48d172130e5d91f5b2728d8868ed9b39650cf5835b6747fb61141ea4f706dd321fcbf442dac245437ed7f3511901d3578380d3 + languageName: node + linkType: hard + "@types/better-sqlite3@npm:^7.6.8": version: 7.6.8 resolution: "@types/better-sqlite3@npm:7.6.8" @@ -5543,6 +6122,16 @@ __metadata: languageName: node linkType: hard +"@types/body-parser@npm:*": + version: 1.19.5 + resolution: "@types/body-parser@npm:1.19.5" + dependencies: + "@types/connect": "npm:*" + "@types/node": "npm:*" + checksum: 10/1e251118c4b2f61029cc43b0dc028495f2d1957fe8ee49a707fb940f86a9bd2f9754230805598278fe99958b49e9b7e66eec8ef6a50ab5c1f6b93e1ba2aaba82 + languageName: node + linkType: hard + "@types/cacheable-request@npm:^6.0.1": version: 6.0.3 resolution: "@types/cacheable-request@npm:6.0.3" @@ -5555,6 +6144,22 @@ __metadata: languageName: node linkType: hard +"@types/connect@npm:*": + version: 3.4.38 + resolution: "@types/connect@npm:3.4.38" + dependencies: + "@types/node": "npm:*" + checksum: 10/7eb1bc5342a9604facd57598a6c62621e244822442976c443efb84ff745246b10d06e8b309b6e80130026a396f19bf6793b7cecd7380169f369dac3bfc46fb99 + languageName: node + linkType: hard + +"@types/cookiejar@npm:^2.1.5": + version: 2.1.5 + resolution: "@types/cookiejar@npm:2.1.5" + checksum: 10/04d5990e87b6387532d15a87d9ec9b2eb783039291193863751dcfd7fc723a3b3aa30ce4c06b03975cba58632e933772f1ff031af23eaa3ac7f94e71afa6e073 + languageName: node + linkType: hard + "@types/copyfiles@npm:^2": version: 2.4.4 resolution: "@types/copyfiles@npm:2.4.4" @@ -5562,6 +6167,15 @@ __metadata: languageName: node linkType: hard +"@types/cors@npm:^2.8.13": + version: 2.8.17 + resolution: "@types/cors@npm:2.8.17" + dependencies: + "@types/node": "npm:*" + checksum: 10/469bd85e29a35977099a3745c78e489916011169a664e97c4c3d6538143b0a16e4cc72b05b407dc008df3892ed7bf595f9b7c0f1f4680e169565ee9d64966bde + languageName: node + linkType: hard + "@types/d3-array@npm:^3.0.3": version: 3.0.4 resolution: "@types/d3-array@npm:3.0.4" @@ -5668,6 +6282,63 @@ __metadata: languageName: node linkType: hard +"@types/express-actuator@npm:^1.8.0": + version: 1.8.3 + resolution: "@types/express-actuator@npm:1.8.3" + dependencies: + "@types/express": "npm:*" + checksum: 10/0da489ce311c36dcf6ee1a03ec082742b181aa8ac95eb884fe09f5635ee5621b575100133b3589e7e8f8056da608010bd7232c0c21aeee484735f5b8fce73948 + languageName: node + linkType: hard + +"@types/express-serve-static-core@npm:^4.17.33": + version: 4.19.6 + resolution: "@types/express-serve-static-core@npm:4.19.6" + dependencies: + "@types/node": "npm:*" + "@types/qs": "npm:*" + "@types/range-parser": "npm:*" + "@types/send": "npm:*" + checksum: 10/a2e00b6c5993f0dd63ada2239be81076fe0220314b9e9fde586e8946c9c09ce60f9a2dd0d74410ee2b5fd10af8c3e755a32bb3abf134533e2158142488995455 + languageName: node + linkType: hard + +"@types/express-serve-static-core@npm:^5.0.0": + version: 5.0.1 + resolution: "@types/express-serve-static-core@npm:5.0.1" + dependencies: + "@types/node": "npm:*" + "@types/qs": "npm:*" + "@types/range-parser": "npm:*" + "@types/send": "npm:*" + checksum: 10/9bccbf4c927a877e4fe60f9664737ec6ac39d4d906dbb2c8d00f67849bb0968833573c48602b5e77d3e0129fd1bdbe0eae08e68485f028ebf8c557806caa3377 + languageName: node + linkType: hard + +"@types/express@npm:*": + version: 5.0.0 + resolution: "@types/express@npm:5.0.0" + dependencies: + "@types/body-parser": "npm:*" + "@types/express-serve-static-core": "npm:^5.0.0" + "@types/qs": "npm:*" + "@types/serve-static": "npm:*" + checksum: 10/45b199ab669caa33e6badafeebf078e277ea95042309d325a04b1ec498f33d33fd5a4ae9c8e358342367b178fe454d7323c5dfc8002bf27070b210a2c6cc11f0 + languageName: node + linkType: hard + +"@types/express@npm:^4.17.17": + version: 4.17.21 + resolution: "@types/express@npm:4.17.21" + dependencies: + "@types/body-parser": "npm:*" + "@types/express-serve-static-core": "npm:^4.17.33" + "@types/qs": "npm:*" + "@types/serve-static": "npm:*" + checksum: 10/7a6d26cf6f43d3151caf4fec66ea11c9d23166e4f3102edfe45a94170654a54ea08cf3103d26b3928d7ebcc24162c90488e33986b7e3a5f8941225edd5eb18c7 + languageName: node + linkType: hard + "@types/fs-extra@npm:9.0.13, @types/fs-extra@npm:^9.0.11": version: 9.0.13 resolution: "@types/fs-extra@npm:9.0.13" @@ -5722,6 +6393,13 @@ __metadata: languageName: node linkType: hard +"@types/http-errors@npm:*": + version: 2.0.4 + resolution: "@types/http-errors@npm:2.0.4" + checksum: 10/1f3d7c3b32c7524811a45690881736b3ef741bf9849ae03d32ad1ab7062608454b150a4e7f1351f83d26a418b2d65af9bdc06198f1c079d75578282884c4e8e3 + languageName: node + linkType: hard + "@types/istanbul-lib-coverage@npm:*, @types/istanbul-lib-coverage@npm:^2.0.0, @types/istanbul-lib-coverage@npm:^2.0.1": version: 2.0.4 resolution: "@types/istanbul-lib-coverage@npm:2.0.4" @@ -5757,6 +6435,16 @@ __metadata: languageName: node linkType: hard +"@types/jest@npm:^29.2.3": + version: 29.5.14 + resolution: "@types/jest@npm:29.5.14" + dependencies: + expect: "npm:^29.0.0" + pretty-format: "npm:^29.0.0" + checksum: 10/59ec7a9c4688aae8ee529316c43853468b6034f453d08a2e1064b281af9c81234cec986be796288f1bbb29efe943bc950e70c8fa8faae1e460d50e3cf9760f9b + languageName: node + linkType: hard + "@types/jlongster__sql.js@npm:@types/sql.js@latest": version: 1.4.4 resolution: "@types/sql.js@npm:1.4.4" @@ -5774,6 +6462,13 @@ __metadata: languageName: node linkType: hard +"@types/json-schema@npm:^7.0.9": + version: 7.0.15 + resolution: "@types/json-schema@npm:7.0.15" + checksum: 10/1a3c3e06236e4c4aab89499c428d585527ce50c24fe8259e8b3926d3df4cfbbbcf306cfc73ddfb66cbafc973116efd15967020b0f738f63e09e64c7d260519e7 + languageName: node + linkType: hard + "@types/json5@npm:^0.0.29": version: 0.0.29 resolution: "@types/json5@npm:0.0.29" @@ -5824,6 +6519,20 @@ __metadata: languageName: node linkType: hard +"@types/methods@npm:^1.1.4": + version: 1.1.4 + resolution: "@types/methods@npm:1.1.4" + checksum: 10/ad2a7178486f2fd167750f3eb920ab032a947ff2e26f55c86670a6038632d790b46f52e5b6ead5823f1e53fc68028f1e9ddd15cfead7903e04517c88debd72b1 + languageName: node + linkType: hard + +"@types/mime@npm:^1": + version: 1.3.5 + resolution: "@types/mime@npm:1.3.5" + checksum: 10/e29a5f9c4776f5229d84e525b7cd7dd960b51c30a0fb9a028c0821790b82fca9f672dab56561e2acd9e8eed51d431bde52eafdfef30f643586c4162f1aecfc78 + languageName: node + linkType: hard + "@types/minimatch@npm:^3.0.3": version: 3.0.5 resolution: "@types/minimatch@npm:3.0.5" @@ -5847,6 +6556,13 @@ __metadata: languageName: node linkType: hard +"@types/node@npm:^17.0.45": + version: 17.0.45 + resolution: "@types/node@npm:17.0.45" + checksum: 10/b45fff7270b5e81be19ef91a66b764a8b21473a97a8d211218a52e3426b79ad48f371819ab9153370756b33ba284e5c875463de4d2cf48a472e9098d7f09e8a2 + languageName: node + linkType: hard + "@types/parse-json@npm:^4.0.0": version: 4.0.2 resolution: "@types/parse-json@npm:4.0.2" @@ -5894,6 +6610,20 @@ __metadata: languageName: node linkType: hard +"@types/qs@npm:*": + version: 6.9.16 + resolution: "@types/qs@npm:6.9.16" + checksum: 10/2e8918150c12735630f7ee16b770c72949274938c30306025f68aaf977227f41fe0c698ed93db1099e04916d582ac5a1faf7e3c7061c8d885d9169f59a184b6c + languageName: node + linkType: hard + +"@types/range-parser@npm:*": + version: 1.2.7 + resolution: "@types/range-parser@npm:1.2.7" + checksum: 10/95640233b689dfbd85b8c6ee268812a732cf36d5affead89e806fe30da9a430767af8ef2cd661024fd97e19d61f3dec75af2df5e80ec3bea000019ab7028629a + languageName: node + linkType: hard + "@types/react-dom@npm:^18.0.0, @types/react-dom@npm:^18.2.1": version: 18.2.1 resolution: "@types/react-dom@npm:18.2.1" @@ -5974,6 +6704,34 @@ __metadata: languageName: node linkType: hard +"@types/semver@npm:^7.3.12": + version: 7.5.8 + resolution: "@types/semver@npm:7.5.8" + checksum: 10/3496808818ddb36deabfe4974fd343a78101fa242c4690044ccdc3b95dcf8785b494f5d628f2f47f38a702f8db9c53c67f47d7818f2be1b79f2efb09692e1178 + languageName: node + linkType: hard + +"@types/send@npm:*": + version: 0.17.4 + resolution: "@types/send@npm:0.17.4" + dependencies: + "@types/mime": "npm:^1" + "@types/node": "npm:*" + checksum: 10/28320a2aa1eb704f7d96a65272a07c0bf3ae7ed5509c2c96ea5e33238980f71deeed51d3631927a77d5250e4091b3e66bce53b42d770873282c6a20bb8b0280d + languageName: node + linkType: hard + +"@types/serve-static@npm:*": + version: 1.15.7 + resolution: "@types/serve-static@npm:1.15.7" + dependencies: + "@types/http-errors": "npm:*" + "@types/node": "npm:*" + "@types/send": "npm:*" + checksum: 10/c5a7171d5647f9fbd096ed1a26105759f3153ccf683824d99fee4c7eb9cde2953509621c56a070dd9fb1159e799e86d300cbe4e42245ebc5b0c1767e8ca94a67 + languageName: node + linkType: hard + "@types/stack-utils@npm:^2.0.0": version: 2.0.1 resolution: "@types/stack-utils@npm:2.0.1" @@ -5981,6 +6739,27 @@ __metadata: languageName: node linkType: hard +"@types/superagent@npm:*": + version: 8.1.9 + resolution: "@types/superagent@npm:8.1.9" + dependencies: + "@types/cookiejar": "npm:^2.1.5" + "@types/methods": "npm:^1.1.4" + "@types/node": "npm:*" + form-data: "npm:^4.0.0" + checksum: 10/6d9687b0bc3d693b900ef76000b02437a70879c3219b28606879c086d786bb1e48429813e72e32dd0aafc94c053a78a2aa8be67c45bc8e6b968ca62d6d5cc554 + languageName: node + linkType: hard + +"@types/supertest@npm:^2.0.12": + version: 2.0.16 + resolution: "@types/supertest@npm:2.0.16" + dependencies: + "@types/superagent": "npm:*" + checksum: 10/2fc998ea698e0467cdbe3bea0ebce2027ea3a45a13e51a6cecb0435f44b486faecf99c34d8702d2d7fe033e6e09fdd2b374af52ecc8d0c69a1deec66b8c0dd52 + languageName: node + linkType: hard + "@types/symlink-or-copy@npm:^1.2.0": version: 1.2.2 resolution: "@types/symlink-or-copy@npm:1.2.2" @@ -6016,6 +6795,13 @@ __metadata: languageName: node linkType: hard +"@types/uuid@npm:^9.0.0": + version: 9.0.8 + resolution: "@types/uuid@npm:9.0.8" + checksum: 10/b8c60b7ba8250356b5088302583d1704a4e1a13558d143c549c408bf8920535602ffc12394ede77f8a8083511b023704bc66d1345792714002bfa261b17c5275 + languageName: node + linkType: hard + "@types/uuid@npm:^9.0.2": version: 9.0.2 resolution: "@types/uuid@npm:9.0.2" @@ -6086,6 +6872,30 @@ __metadata: languageName: node linkType: hard +"@typescript-eslint/eslint-plugin@npm:^5.51.0": + version: 5.62.0 + resolution: "@typescript-eslint/eslint-plugin@npm:5.62.0" + dependencies: + "@eslint-community/regexpp": "npm:^4.4.0" + "@typescript-eslint/scope-manager": "npm:5.62.0" + "@typescript-eslint/type-utils": "npm:5.62.0" + "@typescript-eslint/utils": "npm:5.62.0" + debug: "npm:^4.3.4" + graphemer: "npm:^1.4.0" + ignore: "npm:^5.2.0" + natural-compare-lite: "npm:^1.4.0" + semver: "npm:^7.3.7" + tsutils: "npm:^3.21.0" + peerDependencies: + "@typescript-eslint/parser": ^5.0.0 + eslint: ^6.0.0 || ^7.0.0 || ^8.0.0 + peerDependenciesMeta: + typescript: + optional: true + checksum: 10/9cc8319c6fd8a21938f5b69476974a7e778c283a55ef9fad183c850995b9adcb0087d57cea7b2ac6b9449570eee983aad39491d14cdd2e52d6b4b0485e7b2482 + languageName: node + linkType: hard + "@typescript-eslint/eslint-plugin@npm:^8.1.0": version: 8.1.0 resolution: "@typescript-eslint/eslint-plugin@npm:8.1.0" @@ -6109,6 +6919,23 @@ __metadata: languageName: node linkType: hard +"@typescript-eslint/parser@npm:^5.51.0": + version: 5.62.0 + resolution: "@typescript-eslint/parser@npm:5.62.0" + dependencies: + "@typescript-eslint/scope-manager": "npm:5.62.0" + "@typescript-eslint/types": "npm:5.62.0" + "@typescript-eslint/typescript-estree": "npm:5.62.0" + debug: "npm:^4.3.4" + peerDependencies: + eslint: ^6.0.0 || ^7.0.0 || ^8.0.0 + peerDependenciesMeta: + typescript: + optional: true + checksum: 10/b6ca629d8f4e6283ff124501731cc886703eb4ce2c7d38b3e4110322ea21452b9d9392faf25be6bd72f54b89de7ffc72a40d9b159083ac54345a3d04b4fa5394 + languageName: node + linkType: hard + "@typescript-eslint/parser@npm:^8.1.0": version: 8.1.0 resolution: "@typescript-eslint/parser@npm:8.1.0" @@ -6127,6 +6954,16 @@ __metadata: languageName: node linkType: hard +"@typescript-eslint/scope-manager@npm:5.62.0": + version: 5.62.0 + resolution: "@typescript-eslint/scope-manager@npm:5.62.0" + dependencies: + "@typescript-eslint/types": "npm:5.62.0" + "@typescript-eslint/visitor-keys": "npm:5.62.0" + checksum: 10/e827770baa202223bc0387e2fd24f630690809e460435b7dc9af336c77322290a770d62bd5284260fa881c86074d6a9fd6c97b07382520b115f6786b8ed499da + languageName: node + linkType: hard + "@typescript-eslint/scope-manager@npm:8.1.0": version: 8.1.0 resolution: "@typescript-eslint/scope-manager@npm:8.1.0" @@ -6137,6 +6974,23 @@ __metadata: languageName: node linkType: hard +"@typescript-eslint/type-utils@npm:5.62.0": + version: 5.62.0 + resolution: "@typescript-eslint/type-utils@npm:5.62.0" + dependencies: + "@typescript-eslint/typescript-estree": "npm:5.62.0" + "@typescript-eslint/utils": "npm:5.62.0" + debug: "npm:^4.3.4" + tsutils: "npm:^3.21.0" + peerDependencies: + eslint: "*" + peerDependenciesMeta: + typescript: + optional: true + checksum: 10/f9a4398d6d2aae09e3e765eff04cf4ab364376a87868031ac5c6a64c9bbb555cb1a7f99b07b3d1017e7422725b5f0bbee537f13b82ab2d930f161c987b3dece0 + languageName: node + linkType: hard + "@typescript-eslint/type-utils@npm:8.1.0": version: 8.1.0 resolution: "@typescript-eslint/type-utils@npm:8.1.0" @@ -6152,6 +7006,13 @@ __metadata: languageName: node linkType: hard +"@typescript-eslint/types@npm:5.62.0": + version: 5.62.0 + resolution: "@typescript-eslint/types@npm:5.62.0" + checksum: 10/24e8443177be84823242d6729d56af2c4b47bfc664dd411a1d730506abf2150d6c31bdefbbc6d97c8f91043e3a50e0c698239dcb145b79bb6b0c34469aaf6c45 + languageName: node + linkType: hard + "@typescript-eslint/types@npm:8.1.0": version: 8.1.0 resolution: "@typescript-eslint/types@npm:8.1.0" @@ -6159,6 +7020,24 @@ __metadata: languageName: node linkType: hard +"@typescript-eslint/typescript-estree@npm:5.62.0": + version: 5.62.0 + resolution: "@typescript-eslint/typescript-estree@npm:5.62.0" + dependencies: + "@typescript-eslint/types": "npm:5.62.0" + "@typescript-eslint/visitor-keys": "npm:5.62.0" + debug: "npm:^4.3.4" + globby: "npm:^11.1.0" + is-glob: "npm:^4.0.3" + semver: "npm:^7.3.7" + tsutils: "npm:^3.21.0" + peerDependenciesMeta: + typescript: + optional: true + checksum: 10/06c975eb5f44b43bd19fadc2e1023c50cf87038fe4c0dd989d4331c67b3ff509b17fa60a3251896668ab4d7322bdc56162a9926971218d2e1a1874d2bef9a52e + languageName: node + linkType: hard + "@typescript-eslint/typescript-estree@npm:8.1.0": version: 8.1.0 resolution: "@typescript-eslint/typescript-estree@npm:8.1.0" @@ -6178,6 +7057,24 @@ __metadata: languageName: node linkType: hard +"@typescript-eslint/utils@npm:5.62.0": + version: 5.62.0 + resolution: "@typescript-eslint/utils@npm:5.62.0" + dependencies: + "@eslint-community/eslint-utils": "npm:^4.2.0" + "@types/json-schema": "npm:^7.0.9" + "@types/semver": "npm:^7.3.12" + "@typescript-eslint/scope-manager": "npm:5.62.0" + "@typescript-eslint/types": "npm:5.62.0" + "@typescript-eslint/typescript-estree": "npm:5.62.0" + eslint-scope: "npm:^5.1.1" + semver: "npm:^7.3.7" + peerDependencies: + eslint: ^6.0.0 || ^7.0.0 || ^8.0.0 + checksum: 10/15ef13e43998a082b15f85db979f8d3ceb1f9ce4467b8016c267b1738d5e7cdb12aa90faf4b4e6dd6486c236cf9d33c463200465cf25ff997dbc0f12358550a1 + languageName: node + linkType: hard + "@typescript-eslint/utils@npm:8.1.0": version: 8.1.0 resolution: "@typescript-eslint/utils@npm:8.1.0" @@ -6192,6 +7089,16 @@ __metadata: languageName: node linkType: hard +"@typescript-eslint/visitor-keys@npm:5.62.0": + version: 5.62.0 + resolution: "@typescript-eslint/visitor-keys@npm:5.62.0" + dependencies: + "@typescript-eslint/types": "npm:5.62.0" + eslint-visitor-keys: "npm:^3.3.0" + checksum: 10/dc613ab7569df9bbe0b2ca677635eb91839dfb2ca2c6fa47870a5da4f160db0b436f7ec0764362e756d4164e9445d49d5eb1ff0b87f4c058946ae9d8c92eb388 + languageName: node + linkType: hard + "@typescript-eslint/visitor-keys@npm:8.1.0": version: 8.1.0 resolution: "@typescript-eslint/visitor-keys@npm:8.1.0" @@ -6608,31 +7515,48 @@ __metadata: languageName: node linkType: hard -"actual-sync@file:../../../actual-server::locator=desktop-electron%40workspace%3Apackages%2Fdesktop-electron": - version: 24.10.1 - resolution: "actual-sync@file:../../../actual-server#../../../actual-server::hash=9c9a2b&locator=desktop-electron%40workspace%3Apackages%2Fdesktop-electron" +"actual-sync@npm:*, actual-sync@workspace:packages/sync-server": + version: 0.0.0-use.local + resolution: "actual-sync@workspace:packages/sync-server" dependencies: - "@actual-app/crdt": "npm:2.1.0" - "@actual-app/web": "npm:24.10.1" + "@actual-app/crdt": "npm:*" + "@actual-app/web": "npm:*" + "@babel/preset-typescript": "npm:^7.20.2" + "@types/bcrypt": "npm:^5.0.2" + "@types/better-sqlite3": "npm:^7.6.7" + "@types/cors": "npm:^2.8.13" + "@types/express": "npm:^4.17.17" + "@types/express-actuator": "npm:^1.8.0" + "@types/jest": "npm:^29.2.3" + "@types/node": "npm:^17.0.45" + "@types/supertest": "npm:^2.0.12" + "@types/uuid": "npm:^9.0.0" + "@typescript-eslint/eslint-plugin": "npm:^5.51.0" + "@typescript-eslint/parser": "npm:^5.51.0" bcrypt: "npm:^5.1.1" better-sqlite3: "npm:^9.6.0" body-parser: "npm:^1.20.3" cors: "npm:^2.8.5" date-fns: "npm:^2.30.0" debug: "npm:^4.3.4" + eslint: "npm:^8.33.0" + eslint-plugin-prettier: "npm:^4.2.1" express: "npm:4.20.0" express-actuator: "npm:1.8.4" express-rate-limit: "npm:^6.7.0" express-response-size: "npm:^0.0.3" express-winston: "npm:^4.2.0" + jest: "npm:^29.3.1" jws: "npm:^4.0.0" migrate: "npm:^2.0.1" nordigen-node: "npm:^1.4.0" + prettier: "npm:^2.8.3" + supertest: "npm:^6.3.1" + typescript: "npm:^4.9.5" uuid: "npm:^9.0.0" winston: "npm:^3.14.2" - checksum: 10/b837f26312430cfbfec0e5827119cf9fd01934a2916e8f5b054780536243ce5d157810ba0f2e5a6e7f67aec9d7e7cfbda0942fdecaec2660ed643d0d0f2f453f - languageName: node - linkType: hard + languageName: unknown + linkType: soft "actual@workspace:.": version: 0.0.0-use.local @@ -7038,6 +7962,13 @@ __metadata: languageName: node linkType: hard +"asap@npm:^2.0.0": + version: 2.0.6 + resolution: "asap@npm:2.0.6" + checksum: 10/b244c0458c571945e4b3be0b14eb001bea5596f9868cc50cc711dc03d58a7e953517d3f0dad81ccde3ff37d1f074701fa76a6f07d41aaa992d7204a37b915dda + languageName: node + linkType: hard + "assert-plus@npm:^1.0.0": version: 1.0.0 resolution: "assert-plus@npm:1.0.0" @@ -7177,6 +8108,23 @@ __metadata: languageName: node linkType: hard +"babel-jest@npm:^29.7.0": + version: 29.7.0 + resolution: "babel-jest@npm:29.7.0" + dependencies: + "@jest/transform": "npm:^29.7.0" + "@types/babel__core": "npm:^7.1.14" + babel-plugin-istanbul: "npm:^6.1.1" + babel-preset-jest: "npm:^29.6.3" + chalk: "npm:^4.0.0" + graceful-fs: "npm:^4.2.9" + slash: "npm:^3.0.0" + peerDependencies: + "@babel/core": ^7.8.0 + checksum: 10/8a0953bd813b3a8926008f7351611055548869e9a53dd36d6e7e96679001f71e65fd7dbfe253265c3ba6a4e630dc7c845cf3e78b17d758ef1880313ce8fba258 + languageName: node + linkType: hard + "babel-plugin-istanbul@npm:^6.1.1": version: 6.1.1 resolution: "babel-plugin-istanbul@npm:6.1.1" @@ -7202,6 +8150,18 @@ __metadata: languageName: node linkType: hard +"babel-plugin-jest-hoist@npm:^29.6.3": + version: 29.6.3 + resolution: "babel-plugin-jest-hoist@npm:29.6.3" + dependencies: + "@babel/template": "npm:^7.3.3" + "@babel/types": "npm:^7.3.3" + "@types/babel__core": "npm:^7.1.14" + "@types/babel__traverse": "npm:^7.0.6" + checksum: 10/9bfa86ec4170bd805ab8ca5001ae50d8afcb30554d236ba4a7ffc156c1a92452e220e4acbd98daefc12bf0216fccd092d0a2efed49e7e384ec59e0597a926d65 + languageName: node + linkType: hard + "babel-plugin-macros@npm:^3.1.0": version: 3.1.0 resolution: "babel-plugin-macros@npm:3.1.0" @@ -7283,6 +8243,18 @@ __metadata: languageName: node linkType: hard +"babel-preset-jest@npm:^29.6.3": + version: 29.6.3 + resolution: "babel-preset-jest@npm:29.6.3" + dependencies: + babel-plugin-jest-hoist: "npm:^29.6.3" + babel-preset-current-node-syntax: "npm:^1.0.0" + peerDependencies: + "@babel/core": ^7.0.0 + checksum: 10/aa4ff2a8a728d9d698ed521e3461a109a1e66202b13d3494e41eea30729a5e7cc03b3a2d56c594423a135429c37bf63a9fa8b0b9ce275298be3095a88c69f6fb + languageName: node + linkType: hard + "bail@npm:^2.0.0": version: 2.0.2 resolution: "bail@npm:2.0.2" @@ -7548,6 +8520,20 @@ __metadata: languageName: node linkType: hard +"browserslist@npm:^4.24.0": + version: 4.24.2 + resolution: "browserslist@npm:4.24.2" + dependencies: + caniuse-lite: "npm:^1.0.30001669" + electron-to-chromium: "npm:^1.5.41" + node-releases: "npm:^2.0.18" + update-browserslist-db: "npm:^1.1.1" + bin: + browserslist: cli.js + checksum: 10/f8a9d78bbabe466c57ffd5c50a9e5582a5df9aa68f43078ca62a9f6d0d6c70ba72eca72d0a574dbf177cf55cdca85a46f7eb474917a47ae5398c66f8b76f7d1c + languageName: node + linkType: hard + "bser@npm:2.1.1": version: 2.1.1 resolution: "bser@npm:2.1.1" @@ -7793,6 +8779,13 @@ __metadata: languageName: node linkType: hard +"caniuse-lite@npm:^1.0.30001669": + version: 1.0.30001677 + resolution: "caniuse-lite@npm:1.0.30001677" + checksum: 10/e07439bdeade5ffdd974691f44f8549ae0730fcf510acaa32d0b657c10370cd5aad09eeca37248966205fb37fce5f464dbce73ce177b4a1fdc3a34adbcfd7192 + languageName: node + linkType: hard + "caw@npm:^2.0.0": version: 2.0.1 resolution: "caw@npm:2.0.1" @@ -8306,6 +9299,13 @@ __metadata: languageName: node linkType: hard +"component-emitter@npm:^1.3.0": + version: 1.3.1 + resolution: "component-emitter@npm:1.3.1" + checksum: 10/94550aa462c7bd5a61c1bc480e28554aa306066930152d1b1844a0dd3845d4e5db7e261ddec62ae184913b3e59b55a2ad84093b9d3596a8f17c341514d6c483d + languageName: node + linkType: hard + "compute-scroll-into-view@npm:^2.0.4": version: 2.0.4 resolution: "compute-scroll-into-view@npm:2.0.4" @@ -8398,6 +9398,13 @@ __metadata: languageName: node linkType: hard +"cookiejar@npm:^2.1.4": + version: 2.1.4 + resolution: "cookiejar@npm:2.1.4" + checksum: 10/4a184f5a0591df8b07d22a43ea5d020eacb4572c383e853a33361a99710437eaa0971716c688684075bbf695b484f5872e9e3f562382e46858716cb7fc8ce3f4 + languageName: node + linkType: hard + "copyfiles@npm:^2.4.1": version: 2.4.1 resolution: "copyfiles@npm:2.4.1" @@ -8490,6 +9497,23 @@ __metadata: languageName: node linkType: hard +"create-jest@npm:^29.7.0": + version: 29.7.0 + resolution: "create-jest@npm:29.7.0" + dependencies: + "@jest/types": "npm:^29.6.3" + chalk: "npm:^4.0.0" + exit: "npm:^0.1.2" + graceful-fs: "npm:^4.2.9" + jest-config: "npm:^29.7.0" + jest-util: "npm:^29.7.0" + prompts: "npm:^2.0.1" + bin: + create-jest: bin/create-jest.js + checksum: 10/847b4764451672b4174be4d5c6d7d63442ec3aa5f3de52af924e4d996d87d7801c18e125504f25232fc75840f6625b3ac85860fac6ce799b5efae7bdcaf4a2b7 + languageName: node + linkType: hard + "create-require@npm:^1.1.0": version: 1.1.1 resolution: "create-require@npm:1.1.1" @@ -8980,6 +10004,18 @@ __metadata: languageName: node linkType: hard +"dedent@npm:^1.0.0": + version: 1.5.3 + resolution: "dedent@npm:1.5.3" + peerDependencies: + babel-plugin-macros: ^3.1.0 + peerDependenciesMeta: + babel-plugin-macros: + optional: true + checksum: 10/e5277f6268f288649503125b781a7b7a2c9b22d011139688c0b3619fe40121e600eb1f077c891938d4b2428bdb6326cc3c77a763e4b1cc681bd9666ab1bad2a1 + languageName: node + linkType: hard + "deep-eql@npm:^4.1.3": version: 4.1.3 resolution: "deep-eql@npm:4.1.3" @@ -9118,7 +10154,7 @@ __metadata: "@ngrok/ngrok": "npm:^1.4.1" "@types/copyfiles": "npm:^2" "@types/fs-extra": "npm:^11" - actual-sync: "file:../../../actual-server" + actual-sync: "npm:*" better-sqlite3: "npm:^9.6.0" copyfiles: "npm:^2.4.1" cross-env: "npm:^7.0.3" @@ -9158,6 +10194,16 @@ __metadata: languageName: node linkType: hard +"dezalgo@npm:^1.0.4": + version: 1.0.4 + resolution: "dezalgo@npm:1.0.4" + dependencies: + asap: "npm:^2.0.0" + wrappy: "npm:1" + checksum: 10/895389c6aead740d2ab5da4d3466d20fa30f738010a4d3f4dcccc9fc645ca31c9d10b7e1804ae489b1eb02c7986f9f1f34ba132d409b043082a86d9a4e745624 + languageName: node + linkType: hard + "diff-sequences@npm:^27.5.1": version: 27.5.1 resolution: "diff-sequences@npm:27.5.1" @@ -9506,6 +10552,13 @@ __metadata: languageName: node linkType: hard +"electron-to-chromium@npm:^1.5.41": + version: 1.5.50 + resolution: "electron-to-chromium@npm:1.5.50" + checksum: 10/635ca4b593e64697fbebc9fe7f557abcb030e5f6edcefb596ae3f8c9313221a754b513b70f2ba12595a9ee5733442b2b58db9eed7a2fa63e9f7539d581dd4ac0 + languageName: node + linkType: hard + "electron@npm:30.0.6": version: 30.0.6 resolution: "electron@npm:30.0.6" @@ -9993,6 +11046,13 @@ __metadata: languageName: node linkType: hard +"escalade@npm:^3.2.0": + version: 3.2.0 + resolution: "escalade@npm:3.2.0" + checksum: 10/9d7169e3965b2f9ae46971afa392f6e5a25545ea30f2e2dd99c9b0a95a3f52b5653681a84f5b2911a413ddad2d7a93d3514165072f349b5ffc59c75a899970d6 + languageName: node + linkType: hard + "escape-html@npm:~1.0.3": version: 1.0.3 resolution: "escape-html@npm:1.0.3" @@ -10226,6 +11286,21 @@ __metadata: languageName: node linkType: hard +"eslint-plugin-prettier@npm:^4.2.1": + version: 4.2.1 + resolution: "eslint-plugin-prettier@npm:4.2.1" + dependencies: + prettier-linter-helpers: "npm:^1.0.0" + peerDependencies: + eslint: ">=7.28.0" + prettier: ">=2.0.0" + peerDependenciesMeta: + eslint-config-prettier: + optional: true + checksum: 10/d387f85dd1bfcb6bc6b794845fee6afb9ebb2375653de6bcde6e615892fb97f85121a7c012a4651b181fc09953bdf54c9bc70cab7ad297019d89ae87dd007e28 + languageName: node + linkType: hard + "eslint-plugin-react-hooks@npm:^4.6.2": version: 4.6.2 resolution: "eslint-plugin-react-hooks@npm:4.6.2" @@ -10270,7 +11345,7 @@ __metadata: languageName: node linkType: hard -"eslint-scope@npm:5.1.1": +"eslint-scope@npm:5.1.1, eslint-scope@npm:^5.1.1": version: 5.1.1 resolution: "eslint-scope@npm:5.1.1" dependencies: @@ -10331,6 +11406,54 @@ __metadata: languageName: node linkType: hard +"eslint@npm:^8.33.0": + version: 8.57.1 + resolution: "eslint@npm:8.57.1" + dependencies: + "@eslint-community/eslint-utils": "npm:^4.2.0" + "@eslint-community/regexpp": "npm:^4.6.1" + "@eslint/eslintrc": "npm:^2.1.4" + "@eslint/js": "npm:8.57.1" + "@humanwhocodes/config-array": "npm:^0.13.0" + "@humanwhocodes/module-importer": "npm:^1.0.1" + "@nodelib/fs.walk": "npm:^1.2.8" + "@ungap/structured-clone": "npm:^1.2.0" + ajv: "npm:^6.12.4" + chalk: "npm:^4.0.0" + cross-spawn: "npm:^7.0.2" + debug: "npm:^4.3.2" + doctrine: "npm:^3.0.0" + escape-string-regexp: "npm:^4.0.0" + eslint-scope: "npm:^7.2.2" + eslint-visitor-keys: "npm:^3.4.3" + espree: "npm:^9.6.1" + esquery: "npm:^1.4.2" + esutils: "npm:^2.0.2" + fast-deep-equal: "npm:^3.1.3" + file-entry-cache: "npm:^6.0.1" + find-up: "npm:^5.0.0" + glob-parent: "npm:^6.0.2" + globals: "npm:^13.19.0" + graphemer: "npm:^1.4.0" + ignore: "npm:^5.2.0" + imurmurhash: "npm:^0.1.4" + is-glob: "npm:^4.0.0" + is-path-inside: "npm:^3.0.3" + js-yaml: "npm:^4.1.0" + json-stable-stringify-without-jsonify: "npm:^1.0.1" + levn: "npm:^0.4.1" + lodash.merge: "npm:^4.6.2" + minimatch: "npm:^3.1.2" + natural-compare: "npm:^1.4.0" + optionator: "npm:^0.9.3" + strip-ansi: "npm:^6.0.1" + text-table: "npm:^0.2.0" + bin: + eslint: bin/eslint.js + checksum: 10/5504fa24879afdd9f9929b2fbfc2ee9b9441a3d464efd9790fbda5f05738858530182029f13323add68d19fec749d3ab4a70320ded091ca4432b1e9cc4ed104c + languageName: node + linkType: hard + "eslint@npm:^8.57.0": version: 8.57.0 resolution: "eslint@npm:8.57.0" @@ -10596,6 +11719,19 @@ __metadata: languageName: node linkType: hard +"expect@npm:^29.0.0, expect@npm:^29.7.0": + version: 29.7.0 + resolution: "expect@npm:29.7.0" + dependencies: + "@jest/expect-utils": "npm:^29.7.0" + jest-get-type: "npm:^29.6.3" + jest-matcher-utils: "npm:^29.7.0" + jest-message-util: "npm:^29.7.0" + jest-util: "npm:^29.7.0" + checksum: 10/63f97bc51f56a491950fb525f9ad94f1916e8a014947f8d8445d3847a665b5471b768522d659f5e865db20b6c2033d2ac10f35fcbd881a4d26407a4f6f18451a + languageName: node + linkType: hard + "expect@npm:^29.5.0": version: 29.5.0 resolution: "expect@npm:29.5.0" @@ -10825,6 +11961,13 @@ __metadata: languageName: node linkType: hard +"fast-safe-stringify@npm:^2.1.1": + version: 2.1.1 + resolution: "fast-safe-stringify@npm:2.1.1" + checksum: 10/dc1f063c2c6ac9533aee14d406441f86783a8984b2ca09b19c2fe281f9ff59d315298bc7bc22fd1f83d26fe19ef2f20e2ddb68e96b15040292e555c5ced0c1e4 + languageName: node + linkType: hard + "fastest-levenshtein@npm:^1.0.12": version: 1.0.16 resolution: "fastest-levenshtein@npm:1.0.16" @@ -11082,6 +12225,18 @@ __metadata: languageName: node linkType: hard +"formidable@npm:^2.1.2": + version: 2.1.2 + resolution: "formidable@npm:2.1.2" + dependencies: + dezalgo: "npm:^1.0.4" + hexoid: "npm:^1.0.0" + once: "npm:^1.4.0" + qs: "npm:^6.11.0" + checksum: 10/d385180e0461f65e6f7b70452859fe1c32aa97a290c2ca33f00cdc33145ef44fa68bbc9b93af2c3af73ae726e42c3477c6619c49f3c34b49934e9481275b7b4c + languageName: node + linkType: hard + "forwarded@npm:0.2.0": version: 0.2.0 resolution: "forwarded@npm:0.2.0" @@ -11805,6 +12960,13 @@ __metadata: languageName: node linkType: hard +"hexoid@npm:^1.0.0": + version: 1.0.0 + resolution: "hexoid@npm:1.0.0" + checksum: 10/f2271b8b6b0e13fb5a1eccf740f53ce8bae689c80b9498b854c447f9dc94f75f44e0de064c0e4660ecdbfa8942bb2b69973fdcb080187b45bbb409a3c71f19d4 + languageName: node + linkType: hard + "hoist-non-react-statics@npm:^3.3.0, hoist-non-react-statics@npm:^3.3.2": version: 3.3.2 resolution: "hoist-non-react-statics@npm:3.3.2" @@ -12793,6 +13955,19 @@ __metadata: languageName: node linkType: hard +"istanbul-lib-instrument@npm:^6.0.0": + version: 6.0.3 + resolution: "istanbul-lib-instrument@npm:6.0.3" + dependencies: + "@babel/core": "npm:^7.23.9" + "@babel/parser": "npm:^7.23.9" + "@istanbuljs/schema": "npm:^0.1.3" + istanbul-lib-coverage: "npm:^3.2.0" + semver: "npm:^7.5.4" + checksum: 10/aa5271c0008dfa71b6ecc9ba1e801bf77b49dc05524e8c30d58aaf5b9505e0cd12f25f93165464d4266a518c5c75284ecb598fbd89fec081ae77d2c9d3327695 + languageName: node + linkType: hard + "istanbul-lib-report@npm:^3.0.0": version: 3.0.0 resolution: "istanbul-lib-report@npm:3.0.0" @@ -12886,6 +14061,17 @@ __metadata: languageName: node linkType: hard +"jest-changed-files@npm:^29.7.0": + version: 29.7.0 + resolution: "jest-changed-files@npm:29.7.0" + dependencies: + execa: "npm:^5.0.0" + jest-util: "npm:^29.7.0" + p-limit: "npm:^3.1.0" + checksum: 10/3d93742e56b1a73a145d55b66e96711fbf87ef89b96c2fab7cfdfba8ec06612591a982111ca2b712bb853dbc16831ec8b43585a2a96b83862d6767de59cbf83d + languageName: node + linkType: hard + "jest-circus@npm:^27.5.1": version: 27.5.1 resolution: "jest-circus@npm:27.5.1" @@ -12913,6 +14099,34 @@ __metadata: languageName: node linkType: hard +"jest-circus@npm:^29.7.0": + version: 29.7.0 + resolution: "jest-circus@npm:29.7.0" + dependencies: + "@jest/environment": "npm:^29.7.0" + "@jest/expect": "npm:^29.7.0" + "@jest/test-result": "npm:^29.7.0" + "@jest/types": "npm:^29.6.3" + "@types/node": "npm:*" + chalk: "npm:^4.0.0" + co: "npm:^4.6.0" + dedent: "npm:^1.0.0" + is-generator-fn: "npm:^2.0.0" + jest-each: "npm:^29.7.0" + jest-matcher-utils: "npm:^29.7.0" + jest-message-util: "npm:^29.7.0" + jest-runtime: "npm:^29.7.0" + jest-snapshot: "npm:^29.7.0" + jest-util: "npm:^29.7.0" + p-limit: "npm:^3.1.0" + pretty-format: "npm:^29.7.0" + pure-rand: "npm:^6.0.0" + slash: "npm:^3.0.0" + stack-utils: "npm:^2.0.3" + checksum: 10/716a8e3f40572fd0213bcfc1da90274bf30d856e5133af58089a6ce45089b63f4d679bd44e6be9d320e8390483ebc3ae9921981993986d21639d9019b523123d + languageName: node + linkType: hard + "jest-cli@npm:^27.5.1": version: 27.5.1 resolution: "jest-cli@npm:27.5.1" @@ -12940,6 +14154,32 @@ __metadata: languageName: node linkType: hard +"jest-cli@npm:^29.7.0": + version: 29.7.0 + resolution: "jest-cli@npm:29.7.0" + dependencies: + "@jest/core": "npm:^29.7.0" + "@jest/test-result": "npm:^29.7.0" + "@jest/types": "npm:^29.6.3" + chalk: "npm:^4.0.0" + create-jest: "npm:^29.7.0" + exit: "npm:^0.1.2" + import-local: "npm:^3.0.2" + jest-config: "npm:^29.7.0" + jest-util: "npm:^29.7.0" + jest-validate: "npm:^29.7.0" + yargs: "npm:^17.3.1" + peerDependencies: + node-notifier: ^8.0.1 || ^9.0.0 || ^10.0.0 + peerDependenciesMeta: + node-notifier: + optional: true + bin: + jest: bin/jest.js + checksum: 10/6cc62b34d002c034203065a31e5e9a19e7c76d9e8ef447a6f70f759c0714cb212c6245f75e270ba458620f9c7b26063cd8cf6cd1f7e3afd659a7cc08add17307 + languageName: node + linkType: hard + "jest-config@npm:^27.5.1": version: 27.5.1 resolution: "jest-config@npm:27.5.1" @@ -12977,6 +14217,44 @@ __metadata: languageName: node linkType: hard +"jest-config@npm:^29.7.0": + version: 29.7.0 + resolution: "jest-config@npm:29.7.0" + dependencies: + "@babel/core": "npm:^7.11.6" + "@jest/test-sequencer": "npm:^29.7.0" + "@jest/types": "npm:^29.6.3" + babel-jest: "npm:^29.7.0" + chalk: "npm:^4.0.0" + ci-info: "npm:^3.2.0" + deepmerge: "npm:^4.2.2" + glob: "npm:^7.1.3" + graceful-fs: "npm:^4.2.9" + jest-circus: "npm:^29.7.0" + jest-environment-node: "npm:^29.7.0" + jest-get-type: "npm:^29.6.3" + jest-regex-util: "npm:^29.6.3" + jest-resolve: "npm:^29.7.0" + jest-runner: "npm:^29.7.0" + jest-util: "npm:^29.7.0" + jest-validate: "npm:^29.7.0" + micromatch: "npm:^4.0.4" + parse-json: "npm:^5.2.0" + pretty-format: "npm:^29.7.0" + slash: "npm:^3.0.0" + strip-json-comments: "npm:^3.1.1" + peerDependencies: + "@types/node": "*" + ts-node: ">=9.0.0" + peerDependenciesMeta: + "@types/node": + optional: true + ts-node: + optional: true + checksum: 10/6bdf570e9592e7d7dd5124fc0e21f5fe92bd15033513632431b211797e3ab57eaa312f83cc6481b3094b72324e369e876f163579d60016677c117ec4853cf02b + languageName: node + linkType: hard + "jest-diff@npm:^27.5.1": version: 27.5.1 resolution: "jest-diff@npm:27.5.1" @@ -13001,6 +14279,18 @@ __metadata: languageName: node linkType: hard +"jest-diff@npm:^29.7.0": + version: 29.7.0 + resolution: "jest-diff@npm:29.7.0" + dependencies: + chalk: "npm:^4.0.0" + diff-sequences: "npm:^29.6.3" + jest-get-type: "npm:^29.6.3" + pretty-format: "npm:^29.7.0" + checksum: 10/6f3a7eb9cd9de5ea9e5aa94aed535631fa6f80221832952839b3cb59dd419b91c20b73887deb0b62230d06d02d6b6cf34ebb810b88d904bb4fe1e2e4f0905c98 + languageName: node + linkType: hard + "jest-docblock@npm:^27.5.1": version: 27.5.1 resolution: "jest-docblock@npm:27.5.1" @@ -13010,6 +14300,15 @@ __metadata: languageName: node linkType: hard +"jest-docblock@npm:^29.7.0": + version: 29.7.0 + resolution: "jest-docblock@npm:29.7.0" + dependencies: + detect-newline: "npm:^3.0.0" + checksum: 10/8d48818055bc96c9e4ec2e217a5a375623c0d0bfae8d22c26e011074940c202aa2534a3362294c81d981046885c05d304376afba9f2874143025981148f3e96d + languageName: node + linkType: hard + "jest-each@npm:^27.5.1": version: 27.5.1 resolution: "jest-each@npm:27.5.1" @@ -13023,6 +14322,19 @@ __metadata: languageName: node linkType: hard +"jest-each@npm:^29.7.0": + version: 29.7.0 + resolution: "jest-each@npm:29.7.0" + dependencies: + "@jest/types": "npm:^29.6.3" + chalk: "npm:^4.0.0" + jest-get-type: "npm:^29.6.3" + jest-util: "npm:^29.7.0" + pretty-format: "npm:^29.7.0" + checksum: 10/bd1a077654bdaa013b590deb5f7e7ade68f2e3289180a8c8f53bc8a49f3b40740c0ec2d3a3c1aee906f682775be2bebbac37491d80b634d15276b0aa0f2e3fda + languageName: node + linkType: hard + "jest-environment-jsdom@npm:^27.5.1": version: 27.5.1 resolution: "jest-environment-jsdom@npm:27.5.1" @@ -13052,6 +14364,20 @@ __metadata: languageName: node linkType: hard +"jest-environment-node@npm:^29.7.0": + version: 29.7.0 + resolution: "jest-environment-node@npm:29.7.0" + dependencies: + "@jest/environment": "npm:^29.7.0" + "@jest/fake-timers": "npm:^29.7.0" + "@jest/types": "npm:^29.6.3" + "@types/node": "npm:*" + jest-mock: "npm:^29.7.0" + jest-util: "npm:^29.7.0" + checksum: 10/9cf7045adf2307cc93aed2f8488942e39388bff47ec1df149a997c6f714bfc66b2056768973770d3f8b1bf47396c19aa564877eb10ec978b952c6018ed1bd637 + languageName: node + linkType: hard + "jest-get-type@npm:^27.5.1": version: 27.5.1 resolution: "jest-get-type@npm:27.5.1" @@ -13066,6 +14392,13 @@ __metadata: languageName: node linkType: hard +"jest-get-type@npm:^29.6.3": + version: 29.6.3 + resolution: "jest-get-type@npm:29.6.3" + checksum: 10/88ac9102d4679d768accae29f1e75f592b760b44277df288ad76ce5bf038c3f5ce3719dea8aa0f035dac30e9eb034b848ce716b9183ad7cc222d029f03e92205 + languageName: node + linkType: hard + "jest-haste-map@npm:^27.5.1": version: 27.5.1 resolution: "jest-haste-map@npm:27.5.1" @@ -13113,6 +14446,29 @@ __metadata: languageName: node linkType: hard +"jest-haste-map@npm:^29.7.0": + version: 29.7.0 + resolution: "jest-haste-map@npm:29.7.0" + dependencies: + "@jest/types": "npm:^29.6.3" + "@types/graceful-fs": "npm:^4.1.3" + "@types/node": "npm:*" + anymatch: "npm:^3.0.3" + fb-watchman: "npm:^2.0.0" + fsevents: "npm:^2.3.2" + graceful-fs: "npm:^4.2.9" + jest-regex-util: "npm:^29.6.3" + jest-util: "npm:^29.7.0" + jest-worker: "npm:^29.7.0" + micromatch: "npm:^4.0.4" + walker: "npm:^1.0.8" + dependenciesMeta: + fsevents: + optional: true + checksum: 10/8531b42003581cb18a69a2774e68c456fb5a5c3280b1b9b77475af9e346b6a457250f9d756bfeeae2fe6cbc9ef28434c205edab9390ee970a919baddfa08bb85 + languageName: node + linkType: hard + "jest-jasmine2@npm:^27.5.1": version: 27.5.1 resolution: "jest-jasmine2@npm:27.5.1" @@ -13148,6 +14504,16 @@ __metadata: languageName: node linkType: hard +"jest-leak-detector@npm:^29.7.0": + version: 29.7.0 + resolution: "jest-leak-detector@npm:29.7.0" + dependencies: + jest-get-type: "npm:^29.6.3" + pretty-format: "npm:^29.7.0" + checksum: 10/e3950e3ddd71e1d0c22924c51a300a1c2db6cf69ec1e51f95ccf424bcc070f78664813bef7aed4b16b96dfbdeea53fe358f8aeaaea84346ae15c3735758f1605 + languageName: node + linkType: hard + "jest-matcher-utils@npm:^27.0.0, jest-matcher-utils@npm:^27.5.1": version: 27.5.1 resolution: "jest-matcher-utils@npm:27.5.1" @@ -13172,6 +14538,18 @@ __metadata: languageName: node linkType: hard +"jest-matcher-utils@npm:^29.7.0": + version: 29.7.0 + resolution: "jest-matcher-utils@npm:29.7.0" + dependencies: + chalk: "npm:^4.0.0" + jest-diff: "npm:^29.7.0" + jest-get-type: "npm:^29.6.3" + pretty-format: "npm:^29.7.0" + checksum: 10/981904a494299cf1e3baed352f8a3bd8b50a8c13a662c509b6a53c31461f94ea3bfeffa9d5efcfeb248e384e318c87de7e3baa6af0f79674e987482aa189af40 + languageName: node + linkType: hard + "jest-message-util@npm:^27.5.1": version: 27.5.1 resolution: "jest-message-util@npm:27.5.1" @@ -13206,6 +14584,23 @@ __metadata: languageName: node linkType: hard +"jest-message-util@npm:^29.7.0": + version: 29.7.0 + resolution: "jest-message-util@npm:29.7.0" + dependencies: + "@babel/code-frame": "npm:^7.12.13" + "@jest/types": "npm:^29.6.3" + "@types/stack-utils": "npm:^2.0.0" + chalk: "npm:^4.0.0" + graceful-fs: "npm:^4.2.9" + micromatch: "npm:^4.0.4" + pretty-format: "npm:^29.7.0" + slash: "npm:^3.0.0" + stack-utils: "npm:^2.0.3" + checksum: 10/31d53c6ed22095d86bab9d14c0fa70c4a92c749ea6ceece82cf30c22c9c0e26407acdfbdb0231435dc85a98d6d65ca0d9cbcd25cd1abb377fe945e843fb770b9 + languageName: node + linkType: hard + "jest-mock@npm:^27.5.1": version: 27.5.1 resolution: "jest-mock@npm:27.5.1" @@ -13216,6 +14611,17 @@ __metadata: languageName: node linkType: hard +"jest-mock@npm:^29.7.0": + version: 29.7.0 + resolution: "jest-mock@npm:29.7.0" + dependencies: + "@jest/types": "npm:^29.6.3" + "@types/node": "npm:*" + jest-util: "npm:^29.7.0" + checksum: 10/ae51d1b4f898724be5e0e52b2268a68fcd876d9b20633c864a6dd6b1994cbc48d62402b0f40f3a1b669b30ebd648821f086c26c08ffde192ced951ff4670d51c + languageName: node + linkType: hard + "jest-pnp-resolver@npm:^1.2.2": version: 1.2.3 resolution: "jest-pnp-resolver@npm:1.2.3" @@ -13242,6 +14648,13 @@ __metadata: languageName: node linkType: hard +"jest-regex-util@npm:^29.6.3": + version: 29.6.3 + resolution: "jest-regex-util@npm:29.6.3" + checksum: 10/0518beeb9bf1228261695e54f0feaad3606df26a19764bc19541e0fc6e2a3737191904607fb72f3f2ce85d9c16b28df79b7b1ec9443aa08c3ef0e9efda6f8f2a + languageName: node + linkType: hard + "jest-resolve-dependencies@npm:^27.5.1": version: 27.5.1 resolution: "jest-resolve-dependencies@npm:27.5.1" @@ -13253,6 +14666,16 @@ __metadata: languageName: node linkType: hard +"jest-resolve-dependencies@npm:^29.7.0": + version: 29.7.0 + resolution: "jest-resolve-dependencies@npm:29.7.0" + dependencies: + jest-regex-util: "npm:^29.6.3" + jest-snapshot: "npm:^29.7.0" + checksum: 10/1e206f94a660d81e977bcfb1baae6450cb4a81c92e06fad376cc5ea16b8e8c6ea78c383f39e95591a9eb7f925b6a1021086c38941aa7c1b8a6a813c2f6e93675 + languageName: node + linkType: hard + "jest-resolve@npm:^27.5.1": version: 27.5.1 resolution: "jest-resolve@npm:27.5.1" @@ -13271,6 +14694,23 @@ __metadata: languageName: node linkType: hard +"jest-resolve@npm:^29.7.0": + version: 29.7.0 + resolution: "jest-resolve@npm:29.7.0" + dependencies: + chalk: "npm:^4.0.0" + graceful-fs: "npm:^4.2.9" + jest-haste-map: "npm:^29.7.0" + jest-pnp-resolver: "npm:^1.2.2" + jest-util: "npm:^29.7.0" + jest-validate: "npm:^29.7.0" + resolve: "npm:^1.20.0" + resolve.exports: "npm:^2.0.0" + slash: "npm:^3.0.0" + checksum: 10/faa466fd9bc69ea6c37a545a7c6e808e073c66f46ab7d3d8a6ef084f8708f201b85d5fe1799789578b8b47fa1de47b9ee47b414d1863bc117a49e032ba77b7c7 + languageName: node + linkType: hard + "jest-runner@npm:^27.5.1": version: 27.5.1 resolution: "jest-runner@npm:27.5.1" @@ -13300,6 +14740,35 @@ __metadata: languageName: node linkType: hard +"jest-runner@npm:^29.7.0": + version: 29.7.0 + resolution: "jest-runner@npm:29.7.0" + dependencies: + "@jest/console": "npm:^29.7.0" + "@jest/environment": "npm:^29.7.0" + "@jest/test-result": "npm:^29.7.0" + "@jest/transform": "npm:^29.7.0" + "@jest/types": "npm:^29.6.3" + "@types/node": "npm:*" + chalk: "npm:^4.0.0" + emittery: "npm:^0.13.1" + graceful-fs: "npm:^4.2.9" + jest-docblock: "npm:^29.7.0" + jest-environment-node: "npm:^29.7.0" + jest-haste-map: "npm:^29.7.0" + jest-leak-detector: "npm:^29.7.0" + jest-message-util: "npm:^29.7.0" + jest-resolve: "npm:^29.7.0" + jest-runtime: "npm:^29.7.0" + jest-util: "npm:^29.7.0" + jest-watcher: "npm:^29.7.0" + jest-worker: "npm:^29.7.0" + p-limit: "npm:^3.1.0" + source-map-support: "npm:0.5.13" + checksum: 10/9d8748a494bd90f5c82acea99be9e99f21358263ce6feae44d3f1b0cd90991b5df5d18d607e73c07be95861ee86d1cbab2a3fc6ca4b21805f07ac29d47c1da1e + languageName: node + linkType: hard + "jest-runtime@npm:^27.5.1": version: 27.5.1 resolution: "jest-runtime@npm:27.5.1" @@ -13330,6 +14799,36 @@ __metadata: languageName: node linkType: hard +"jest-runtime@npm:^29.7.0": + version: 29.7.0 + resolution: "jest-runtime@npm:29.7.0" + dependencies: + "@jest/environment": "npm:^29.7.0" + "@jest/fake-timers": "npm:^29.7.0" + "@jest/globals": "npm:^29.7.0" + "@jest/source-map": "npm:^29.6.3" + "@jest/test-result": "npm:^29.7.0" + "@jest/transform": "npm:^29.7.0" + "@jest/types": "npm:^29.6.3" + "@types/node": "npm:*" + chalk: "npm:^4.0.0" + cjs-module-lexer: "npm:^1.0.0" + collect-v8-coverage: "npm:^1.0.0" + glob: "npm:^7.1.3" + graceful-fs: "npm:^4.2.9" + jest-haste-map: "npm:^29.7.0" + jest-message-util: "npm:^29.7.0" + jest-mock: "npm:^29.7.0" + jest-regex-util: "npm:^29.6.3" + jest-resolve: "npm:^29.7.0" + jest-snapshot: "npm:^29.7.0" + jest-util: "npm:^29.7.0" + slash: "npm:^3.0.0" + strip-bom: "npm:^4.0.0" + checksum: 10/59eb58eb7e150e0834a2d0c0d94f2a0b963ae7182cfa6c63f2b49b9c6ef794e5193ef1634e01db41420c36a94cefc512cdd67a055cd3e6fa2f41eaf0f82f5a20 + languageName: node + linkType: hard + "jest-serializer@npm:^27.5.1": version: 27.5.1 resolution: "jest-serializer@npm:27.5.1" @@ -13401,6 +14900,34 @@ __metadata: languageName: node linkType: hard +"jest-snapshot@npm:^29.7.0": + version: 29.7.0 + resolution: "jest-snapshot@npm:29.7.0" + dependencies: + "@babel/core": "npm:^7.11.6" + "@babel/generator": "npm:^7.7.2" + "@babel/plugin-syntax-jsx": "npm:^7.7.2" + "@babel/plugin-syntax-typescript": "npm:^7.7.2" + "@babel/types": "npm:^7.3.3" + "@jest/expect-utils": "npm:^29.7.0" + "@jest/transform": "npm:^29.7.0" + "@jest/types": "npm:^29.6.3" + babel-preset-current-node-syntax: "npm:^1.0.0" + chalk: "npm:^4.0.0" + expect: "npm:^29.7.0" + graceful-fs: "npm:^4.2.9" + jest-diff: "npm:^29.7.0" + jest-get-type: "npm:^29.6.3" + jest-matcher-utils: "npm:^29.7.0" + jest-message-util: "npm:^29.7.0" + jest-util: "npm:^29.7.0" + natural-compare: "npm:^1.4.0" + pretty-format: "npm:^29.7.0" + semver: "npm:^7.5.3" + checksum: 10/cb19a3948256de5f922d52f251821f99657339969bf86843bd26cf3332eae94883e8260e3d2fba46129a27c3971c1aa522490e460e16c7fad516e82d10bbf9f8 + languageName: node + linkType: hard + "jest-util@npm:^27.5.1": version: 27.5.1 resolution: "jest-util@npm:27.5.1" @@ -13429,6 +14956,20 @@ __metadata: languageName: node linkType: hard +"jest-util@npm:^29.7.0": + version: 29.7.0 + resolution: "jest-util@npm:29.7.0" + dependencies: + "@jest/types": "npm:^29.6.3" + "@types/node": "npm:*" + chalk: "npm:^4.0.0" + ci-info: "npm:^3.2.0" + graceful-fs: "npm:^4.2.9" + picomatch: "npm:^2.2.3" + checksum: 10/30d58af6967e7d42bd903ccc098f3b4d3859ed46238fbc88d4add6a3f10bea00c226b93660285f058bc7a65f6f9529cf4eb80f8d4707f79f9e3a23686b4ab8f3 + languageName: node + linkType: hard + "jest-validate@npm:^27.5.1": version: 27.5.1 resolution: "jest-validate@npm:27.5.1" @@ -13443,6 +14984,20 @@ __metadata: languageName: node linkType: hard +"jest-validate@npm:^29.7.0": + version: 29.7.0 + resolution: "jest-validate@npm:29.7.0" + dependencies: + "@jest/types": "npm:^29.6.3" + camelcase: "npm:^6.2.0" + chalk: "npm:^4.0.0" + jest-get-type: "npm:^29.6.3" + leven: "npm:^3.1.0" + pretty-format: "npm:^29.7.0" + checksum: 10/8ee1163666d8eaa16d90a989edba2b4a3c8ab0ffaa95ad91b08ca42b015bfb70e164b247a5b17f9de32d096987cada63ed8491ab82761bfb9a28bc34b27ae161 + languageName: node + linkType: hard + "jest-watch-typeahead@npm:^2.2.2": version: 2.2.2 resolution: "jest-watch-typeahead@npm:2.2.2" @@ -13491,6 +15046,22 @@ __metadata: languageName: node linkType: hard +"jest-watcher@npm:^29.7.0": + version: 29.7.0 + resolution: "jest-watcher@npm:29.7.0" + dependencies: + "@jest/test-result": "npm:^29.7.0" + "@jest/types": "npm:^29.6.3" + "@types/node": "npm:*" + ansi-escapes: "npm:^4.2.1" + chalk: "npm:^4.0.0" + emittery: "npm:^0.13.1" + jest-util: "npm:^29.7.0" + string-length: "npm:^4.0.1" + checksum: 10/4f616e0345676631a7034b1d94971aaa719f0cd4a6041be2aa299be437ea047afd4fe05c48873b7963f5687a2f6c7cbf51244be8b14e313b97bfe32b1e127e55 + languageName: node + linkType: hard + "jest-worker@npm:^27.4.5, jest-worker@npm:^27.5.1": version: 27.5.1 resolution: "jest-worker@npm:27.5.1" @@ -13514,6 +15085,18 @@ __metadata: languageName: node linkType: hard +"jest-worker@npm:^29.7.0": + version: 29.7.0 + resolution: "jest-worker@npm:29.7.0" + dependencies: + "@types/node": "npm:*" + jest-util: "npm:^29.7.0" + merge-stream: "npm:^2.0.0" + supports-color: "npm:^8.0.0" + checksum: 10/364cbaef00d8a2729fc760227ad34b5e60829e0869bd84976bdfbd8c0d0f9c2f22677b3e6dd8afa76ed174765351cd12bae3d4530c62eefb3791055127ca9745 + languageName: node + linkType: hard + "jest@npm:^27.5.1": version: 27.5.1 resolution: "jest@npm:27.5.1" @@ -13532,6 +15115,25 @@ __metadata: languageName: node linkType: hard +"jest@npm:^29.3.1": + version: 29.7.0 + resolution: "jest@npm:29.7.0" + dependencies: + "@jest/core": "npm:^29.7.0" + "@jest/types": "npm:^29.6.3" + import-local: "npm:^3.0.2" + jest-cli: "npm:^29.7.0" + peerDependencies: + node-notifier: ^8.0.1 || ^9.0.0 || ^10.0.0 + peerDependenciesMeta: + node-notifier: + optional: true + bin: + jest: bin/jest.js + checksum: 10/97023d78446098c586faaa467fbf2c6b07ff06e2c85a19e3926adb5b0effe9ac60c4913ae03e2719f9c01ae8ffd8d92f6b262cedb9555ceeb5d19263d8c6362a + languageName: node + linkType: hard + "joi@npm:^17.4.0": version: 17.9.2 resolution: "joi@npm:17.9.2" @@ -13631,6 +15233,15 @@ __metadata: languageName: node linkType: hard +"jsesc@npm:^3.0.2": + version: 3.0.2 + resolution: "jsesc@npm:3.0.2" + bin: + jsesc: bin/jsesc + checksum: 10/8e5a7de6b70a8bd71f9cb0b5a7ade6a73ae6ab55e697c74cc997cede97417a3a65ed86c36f7dd6125fe49766e8386c845023d9e213916ca92c9dfdd56e2babf3 + languageName: node + linkType: hard + "jsesc@npm:~0.5.0": version: 0.5.0 resolution: "jsesc@npm:0.5.0" @@ -14616,7 +16227,7 @@ __metadata: languageName: node linkType: hard -"methods@npm:~1.1.2": +"methods@npm:^1.1.2, methods@npm:~1.1.2": version: 1.1.2 resolution: "methods@npm:1.1.2" checksum: 10/a385dd974faa34b5dd021b2bbf78c722881bf6f003bfe6d391d7da3ea1ed625d1ff10ddd13c57531f628b3e785be38d3eed10ad03cebd90b76932413df9a1820 @@ -15010,7 +16621,7 @@ __metadata: languageName: node linkType: hard -"mime@npm:^2.5.2": +"mime@npm:2.6.0, mime@npm:^2.5.2": version: 2.6.0 resolution: "mime@npm:2.6.0" bin: @@ -15310,6 +16921,13 @@ __metadata: languageName: node linkType: hard +"natural-compare-lite@npm:^1.4.0": + version: 1.4.0 + resolution: "natural-compare-lite@npm:1.4.0" + checksum: 10/5222ac3986a2b78dd6069ac62cbb52a7bf8ffc90d972ab76dfe7b01892485d229530ed20d0c62e79a6b363a663b273db3bde195a1358ce9e5f779d4453887225 + languageName: node + linkType: hard + "natural-compare@npm:^1.4.0": version: 1.4.0 resolution: "natural-compare@npm:1.4.0" @@ -15494,6 +17112,13 @@ __metadata: languageName: node linkType: hard +"node-releases@npm:^2.0.18": + version: 2.0.18 + resolution: "node-releases@npm:2.0.18" + checksum: 10/241e5fa9556f1c12bafb83c6c3e94f8cf3d8f2f8f904906ecef6e10bcaa1d59aa61212d4651bec70052015fc54bd3fdcdbe7fc0f638a17e6685aa586c076ec4e + languageName: node + linkType: hard + "noms@npm:0.0.0": version: 0.0.0 resolution: "noms@npm:0.0.0" @@ -15922,7 +17547,7 @@ __metadata: languageName: node linkType: hard -"p-limit@npm:^3.0.2": +"p-limit@npm:^3.0.2, p-limit@npm:^3.1.0": version: 3.1.0 resolution: "p-limit@npm:3.1.0" dependencies: @@ -16411,7 +18036,7 @@ __metadata: languageName: node linkType: hard -"prettier@npm:^2.8.7": +"prettier@npm:^2.8.3, prettier@npm:^2.8.7": version: 2.8.8 resolution: "prettier@npm:2.8.8" bin: @@ -16609,7 +18234,7 @@ __metadata: languageName: node linkType: hard -"qs@npm:6.13.0": +"qs@npm:6.13.0, qs@npm:^6.11.0": version: 6.13.0 resolution: "qs@npm:6.13.0" dependencies: @@ -17558,6 +19183,13 @@ __metadata: languageName: node linkType: hard +"resolve.exports@npm:^2.0.0": + version: 2.0.2 + resolution: "resolve.exports@npm:2.0.2" + checksum: 10/f1cc0b6680f9a7e0345d783e0547f2a5110d8336b3c2a4227231dd007271ffd331fd722df934f017af90bae0373920ca0d4005da6f76cb3176c8ae426370f893 + languageName: node + linkType: hard + "resolve@npm:^1.10.0, resolve@npm:^1.10.1, resolve@npm:^1.14.2, resolve@npm:^1.19.0, resolve@npm:^1.20.0, resolve@npm:^1.22.1, resolve@npm:^1.22.4": version: 1.22.8 resolution: "resolve@npm:1.22.8" @@ -17954,7 +19586,7 @@ __metadata: languageName: node linkType: hard -"semver@npm:^7.3.2, semver@npm:^7.3.5, semver@npm:^7.3.8, semver@npm:^7.5.3, semver@npm:^7.6.0": +"semver@npm:^7.3.2, semver@npm:^7.3.5, semver@npm:^7.3.7, semver@npm:^7.3.8, semver@npm:^7.5.3, semver@npm:^7.5.4, semver@npm:^7.6.0": version: 7.6.3 resolution: "semver@npm:7.6.3" bin: @@ -18363,6 +19995,16 @@ __metadata: languageName: node linkType: hard +"source-map-support@npm:0.5.13": + version: 0.5.13 + resolution: "source-map-support@npm:0.5.13" + dependencies: + buffer-from: "npm:^1.0.0" + source-map: "npm:^0.6.0" + checksum: 10/d1514a922ac9c7e4786037eeff6c3322f461cd25da34bb9fefb15387b3490531774e6e31d95ab6d5b84a3e139af9c3a570ccaee6b47bd7ea262691ed3a8bc34e + languageName: node + linkType: hard + "source-map-support@npm:^0.5.19, source-map-support@npm:^0.5.21, source-map-support@npm:^0.5.6, source-map-support@npm:~0.5.20": version: 0.5.21 resolution: "source-map-support@npm:0.5.21" @@ -18877,6 +20519,34 @@ __metadata: languageName: node linkType: hard +"superagent@npm:^8.1.2": + version: 8.1.2 + resolution: "superagent@npm:8.1.2" + dependencies: + component-emitter: "npm:^1.3.0" + cookiejar: "npm:^2.1.4" + debug: "npm:^4.3.4" + fast-safe-stringify: "npm:^2.1.1" + form-data: "npm:^4.0.0" + formidable: "npm:^2.1.2" + methods: "npm:^1.1.2" + mime: "npm:2.6.0" + qs: "npm:^6.11.0" + semver: "npm:^7.3.8" + checksum: 10/33d0072e051baf91c7d68131c70682a0650dd1bd0b8dfb6f88e5bdfcb02e18cc2b42a66e44b32fd405ac6bcf5fd57c6e267bf80e2a8ce57a18166a9d3a78f57d + languageName: node + linkType: hard + +"supertest@npm:^6.3.1": + version: 6.3.4 + resolution: "supertest@npm:6.3.4" + dependencies: + methods: "npm:^1.1.2" + superagent: "npm:^8.1.2" + checksum: 10/93015318f5a90398915a032747973d9eacf9aebec3f07b413eba9d8b3db83ff48fbf6f5a92f9526578cae50153b0f76a37de197141030d856db4371a711b86ee + languageName: node + linkType: hard + "supports-color@npm:^5.3.0": version: 5.5.0 resolution: "supports-color@npm:5.5.0" @@ -19506,6 +21176,13 @@ __metadata: languageName: node linkType: hard +"tslib@npm:^1.8.1": + version: 1.14.1 + resolution: "tslib@npm:1.14.1" + checksum: 10/7dbf34e6f55c6492637adb81b555af5e3b4f9cc6b998fb440dac82d3b42bdc91560a35a5fb75e20e24a076c651438234da6743d139e4feabf0783f3cdfe1dddb + languageName: node + linkType: hard + "tslib@npm:^2.0.3, tslib@npm:^2.3.0, tslib@npm:^2.4.0, tslib@npm:^2.6.2": version: 2.6.2 resolution: "tslib@npm:2.6.2" @@ -19513,6 +21190,17 @@ __metadata: languageName: node linkType: hard +"tsutils@npm:^3.21.0": + version: 3.21.0 + resolution: "tsutils@npm:3.21.0" + dependencies: + tslib: "npm:^1.8.1" + peerDependencies: + typescript: ">=2.8.0 || >= 3.2.0-dev || >= 3.3.0-dev || >= 3.4.0-dev || >= 3.5.0-dev || >= 3.6.0-dev || >= 3.6.0-beta || >= 3.7.0-dev || >= 3.7.0-beta" + checksum: 10/ea036bec1dd024e309939ffd49fda7a351c0e87a1b8eb049570dd119d447250e2c56e0e6c00554e8205760e7417793fdebff752a46e573fbe07d4f375502a5b2 + languageName: node + linkType: hard + "tunnel-agent@npm:^0.6.0": version: 0.6.0 resolution: "tunnel-agent@npm:0.6.0" @@ -19671,7 +21359,7 @@ __metadata: languageName: node linkType: hard -"typescript@npm:^4.0.2": +"typescript@npm:^4.0.2, typescript@npm:^4.9.5": version: 4.9.5 resolution: "typescript@npm:4.9.5" bin: @@ -19691,7 +21379,7 @@ __metadata: languageName: node linkType: hard -"typescript@patch:typescript@npm%3A^4.0.2#optional!builtin": +"typescript@patch:typescript@npm%3A^4.0.2#optional!builtin, typescript@patch:typescript@npm%3A^4.9.5#optional!builtin": version: 4.9.5 resolution: "typescript@patch:typescript@npm%3A4.9.5#optional!builtin::version=4.9.5&hash=289587" bin: @@ -20012,6 +21700,20 @@ __metadata: languageName: node linkType: hard +"update-browserslist-db@npm:^1.1.1": + version: 1.1.1 + resolution: "update-browserslist-db@npm:1.1.1" + dependencies: + escalade: "npm:^3.2.0" + picocolors: "npm:^1.1.0" + peerDependencies: + browserslist: ">= 4.21.0" + bin: + update-browserslist-db: cli.js + checksum: 10/7678dd8609750588d01aa7460e8eddf2ff9d16c2a52fb1811190e0d056390f1fdffd94db3cf8fb209cf634ab4fa9407886338711c71cc6ccade5eeb22b093734 + languageName: node + linkType: hard + "uri-js@npm:^4.2.2": version: 4.4.1 resolution: "uri-js@npm:4.4.1" @@ -20151,6 +21853,17 @@ __metadata: languageName: node linkType: hard +"v8-to-istanbul@npm:^9.0.1": + version: 9.3.0 + resolution: "v8-to-istanbul@npm:9.3.0" + dependencies: + "@jridgewell/trace-mapping": "npm:^0.3.12" + "@types/istanbul-lib-coverage": "npm:^2.0.1" + convert-source-map: "npm:^2.0.0" + checksum: 10/fb1d70f1176cb9dc46cabbb3fd5c52c8f3e8738b61877b6e7266029aed0870b04140e3f9f4550ac32aebcfe1d0f38b0bac57e1e8fb97d68fec82f2b416148166 + languageName: node + linkType: hard + "validate-npm-package-license@npm:^3.0.1": version: 3.0.4 resolution: "validate-npm-package-license@npm:3.0.4" @@ -21236,7 +22949,7 @@ __metadata: languageName: node linkType: hard -"yargs@npm:^17.0.1, yargs@npm:^17.5.1, yargs@npm:^17.6.2": +"yargs@npm:^17.0.1, yargs@npm:^17.3.1, yargs@npm:^17.5.1, yargs@npm:^17.6.2": version: 17.7.2 resolution: "yargs@npm:17.7.2" dependencies: From 651320707f4edfa68434f1dd070794e0838a4994 Mon Sep 17 00:00:00 2001 From: Mike Clark Date: Mon, 4 Nov 2024 20:56:38 +0000 Subject: [PATCH 41/55] update server --- packages/sync-server/.yarnrc.yml | 3 - packages/sync-server/package.json | 6 +- .../src/app-gocardless/bank-factory.js | 3 + .../app-gocardless/banks/easybank-bawaatww.js | 2 +- .../app-gocardless/banks/integration-bank.js | 6 +- .../banks/tests/integration-bank.spec.js | 2 +- .../src/app-gocardless/tests/utils.spec.js | 125 +++++++ .../sync-server/src/app-gocardless/utils.js | 39 +- .../src/app-simplefin/app-simplefin.js | 50 ++- packages/sync-server/src/app-sync.js | 339 +++++++++--------- packages/sync-server/src/app-sync.test.js | 50 ++- packages/sync-server/src/app-sync/errors.js | 13 + .../src/app-sync/services/files-service.js | 197 ++++++++++ .../tests/services/files-service.test.js | 214 +++++++++++ .../sync-server/src/app-sync/validation.js | 77 ++++ packages/sync-server/src/load-config.js | 6 +- packages/sync-server/src/util/middlewares.js | 18 +- .../sync-server/upcoming-release-notes/474.md | 6 - .../sync-server/upcoming-release-notes/487.md | 6 - yarn.lock | 8 +- 20 files changed, 937 insertions(+), 233 deletions(-) delete mode 100644 packages/sync-server/.yarnrc.yml create mode 100644 packages/sync-server/src/app-sync/errors.js create mode 100644 packages/sync-server/src/app-sync/services/files-service.js create mode 100644 packages/sync-server/src/app-sync/tests/services/files-service.test.js create mode 100644 packages/sync-server/src/app-sync/validation.js delete mode 100644 packages/sync-server/upcoming-release-notes/474.md delete mode 100644 packages/sync-server/upcoming-release-notes/487.md diff --git a/packages/sync-server/.yarnrc.yml b/packages/sync-server/.yarnrc.yml deleted file mode 100644 index fd5296c36ec..00000000000 --- a/packages/sync-server/.yarnrc.yml +++ /dev/null @@ -1,3 +0,0 @@ -nodeLinker: node-modules - -yarnPath: .yarn/releases/yarn-4.3.1.cjs diff --git a/packages/sync-server/package.json b/packages/sync-server/package.json index 5113b7e10f6..c402dfe4c3a 100644 --- a/packages/sync-server/package.json +++ b/packages/sync-server/package.json @@ -1,6 +1,6 @@ { "name": "actual-sync", - "version": "24.10.1", + "version": "24.11.0", "license": "MIT", "description": "actual syncing server", "type": "module", @@ -20,8 +20,8 @@ "health-check": "node src/scripts/health-check.js" }, "dependencies": { - "@actual-app/crdt": "*", - "@actual-app/web": "*", + "@actual-app/crdt": "2.1.0", + "@actual-app/web": "24.11.0", "bcrypt": "^5.1.1", "better-sqlite3": "^9.6.0", "body-parser": "^1.20.3", diff --git a/packages/sync-server/src/app-gocardless/bank-factory.js b/packages/sync-server/src/app-gocardless/bank-factory.js index 2bf09e1ec71..c7e2966e1d6 100644 --- a/packages/sync-server/src/app-gocardless/bank-factory.js +++ b/packages/sync-server/src/app-gocardless/bank-factory.js @@ -75,6 +75,8 @@ export const BANKS_WITH_LIMITED_HISTORY = [ 'CESKA_SPORITELNA_LONG_GIBACZPX', 'COOP_EKRDEE22', 'DOTS_HYEEIT22', + 'FINECO_FEBIITM2XXX', + 'FINECO_UK_FEBIITM2XXX', 'HYPE_BUSINESS_HYEEIT22', 'HYPE_HYEEIT2', 'ILLIMITY_ITTPIT2M', @@ -90,6 +92,7 @@ export const BANKS_WITH_LIMITED_HISTORY = [ 'LUMINOR_RIKOLV2X', 'MEDICINOSBANK_MDBALT22XXX', 'NORDEA_NDEADKKK', + 'N26_NTSBDEB1', 'OPYN_BITAITRRB2B', 'PAYTIPPER_PAYTITM1', 'REVOLUT_REVOLT21', diff --git a/packages/sync-server/src/app-gocardless/banks/easybank-bawaatww.js b/packages/sync-server/src/app-gocardless/banks/easybank-bawaatww.js index e738d8302a6..f93ab951f4a 100644 --- a/packages/sync-server/src/app-gocardless/banks/easybank-bawaatww.js +++ b/packages/sync-server/src/app-gocardless/banks/easybank-bawaatww.js @@ -10,7 +10,7 @@ export default { institutionIds: ['EASYBANK_BAWAATWW'], - accessValidForDays: 180, + accessValidForDays: 179, // If date is same, sort by transactionId sortTransactions: (transactions = []) => diff --git a/packages/sync-server/src/app-gocardless/banks/integration-bank.js b/packages/sync-server/src/app-gocardless/banks/integration-bank.js index cbe6cd57f96..2b4f358e3dd 100644 --- a/packages/sync-server/src/app-gocardless/banks/integration-bank.js +++ b/packages/sync-server/src/app-gocardless/banks/integration-bank.js @@ -28,7 +28,7 @@ export default { accessValidForDays: 90, normalizeAccount(account) { - console.log( + console.debug( 'Available account properties for new institution integration', { account: JSON.stringify(account) }, ); @@ -66,7 +66,7 @@ export default { }, sortTransactions(transactions = []) { - console.log( + console.debug( 'Available (first 10) transactions properties for new integration of institution in sortTransactions function', { top10Transactions: JSON.stringify(transactions.slice(0, 10)) }, ); @@ -74,7 +74,7 @@ export default { }, calculateStartingBalance(sortedTransactions = [], balances = []) { - console.log( + console.debug( 'Available (first 10) transactions properties for new integration of institution in calculateStartingBalance function', { balances: JSON.stringify(balances), diff --git a/packages/sync-server/src/app-gocardless/banks/tests/integration-bank.spec.js b/packages/sync-server/src/app-gocardless/banks/tests/integration-bank.spec.js index 7f1f235d389..f244d00a9a3 100644 --- a/packages/sync-server/src/app-gocardless/banks/tests/integration-bank.spec.js +++ b/packages/sync-server/src/app-gocardless/banks/tests/integration-bank.spec.js @@ -9,7 +9,7 @@ describe('IntegrationBank', () => { let consoleSpy; beforeEach(() => { - consoleSpy = jest.spyOn(console, 'log'); + consoleSpy = jest.spyOn(console, 'debug'); }); describe('normalizeAccount', () => { diff --git a/packages/sync-server/src/app-gocardless/tests/utils.spec.js b/packages/sync-server/src/app-gocardless/tests/utils.spec.js index 352525b9da8..9d43448d9d4 100644 --- a/packages/sync-server/src/app-gocardless/tests/utils.spec.js +++ b/packages/sync-server/src/app-gocardless/tests/utils.spec.js @@ -33,5 +33,130 @@ describe('utils', () => { }, ]); }); + + it('should sort by valueDate if bookingDate is missing', () => { + const transactions = [ + { + valueDate: '2023-01-01', + transactionAmount: mockTransactionAmount, + }, + { + valueDate: '2023-01-20', + transactionAmount: mockTransactionAmount, + }, + { + valueDate: '2023-01-10', + transactionAmount: mockTransactionAmount, + }, + ]; + expect(sortByBookingDateOrValueDate(transactions)).toEqual([ + { + valueDate: '2023-01-20', + transactionAmount: mockTransactionAmount, + }, + { + valueDate: '2023-01-10', + transactionAmount: mockTransactionAmount, + }, + { + valueDate: '2023-01-01', + transactionAmount: mockTransactionAmount, + }, + ]); + }); + + it('should use bookingDate primarily even if bookingDateTime is on an other date', () => { + const transactions = [ + { + bookingDate: '2023-01-01', + bookingDateTime: '2023-01-01T00:00:00Z', + transactionAmount: mockTransactionAmount, + }, + { + bookingDate: '2023-01-10', + bookingDateTime: '2023-01-01T12:00:00Z', + transactionAmount: mockTransactionAmount, + }, + { + bookingDate: '2023-01-01', + bookingDateTime: '2023-01-01T12:00:00Z', + transactionAmount: mockTransactionAmount, + }, + { + bookingDate: '2023-01-10', + bookingDateTime: '2023-01-01T00:00:00Z', + transactionAmount: mockTransactionAmount, + }, + ]; + expect(sortByBookingDateOrValueDate(transactions)).toEqual([ + { + bookingDate: '2023-01-10', + bookingDateTime: '2023-01-01T12:00:00Z', + transactionAmount: mockTransactionAmount, + }, + { + bookingDate: '2023-01-10', + bookingDateTime: '2023-01-01T00:00:00Z', + transactionAmount: mockTransactionAmount, + }, + { + bookingDate: '2023-01-01', + bookingDateTime: '2023-01-01T12:00:00Z', + transactionAmount: mockTransactionAmount, + }, + { + bookingDate: '2023-01-01', + bookingDateTime: '2023-01-01T00:00:00Z', + transactionAmount: mockTransactionAmount, + }, + ]); + }); + + it('should sort on booking date if value date is widely off', () => { + const transactions = [ + { + bookingDate: '2023-01-01', + valueDateTime: '2023-01-31T00:00:00Z', + transactionAmount: mockTransactionAmount, + }, + { + bookingDate: '2023-01-02', + valueDateTime: '2023-01-02T12:00:00Z', + transactionAmount: mockTransactionAmount, + }, + { + bookingDate: '2023-01-30', + valueDateTime: '2023-01-01T12:00:00Z', + transactionAmount: mockTransactionAmount, + }, + { + bookingDate: '2023-01-30', + valueDateTime: '2023-01-01T00:00:00Z', + transactionAmount: mockTransactionAmount, + }, + ]; + expect(sortByBookingDateOrValueDate(transactions)).toEqual([ + { + bookingDate: '2023-01-30', + valueDateTime: '2023-01-01T12:00:00Z', + transactionAmount: mockTransactionAmount, + }, + { + bookingDate: '2023-01-30', + valueDateTime: '2023-01-01T00:00:00Z', + transactionAmount: mockTransactionAmount, + }, + { + bookingDate: '2023-01-02', + valueDateTime: '2023-01-02T12:00:00Z', + transactionAmount: mockTransactionAmount, + }, + { + bookingDate: '2023-01-01', + valueDateTime: '2023-01-31T00:00:00Z', + transactionAmount: mockTransactionAmount, + }, + ]); + }); }); }); diff --git a/packages/sync-server/src/app-gocardless/utils.js b/packages/sync-server/src/app-gocardless/utils.js index 7a3987b7756..147276427cd 100644 --- a/packages/sync-server/src/app-gocardless/utils.js +++ b/packages/sync-server/src/app-gocardless/utils.js @@ -6,11 +6,40 @@ export const printIban = (account) => { } }; +const compareDates = ( + /** @type {string | number | Date | undefined} */ a, + /** @type {string | number | Date | undefined} */ b, +) => { + if (a == null && b == null) { + return 0; + } else if (a == null) { + return 1; + } else if (b == null) { + return -1; + } + + return +new Date(a) - +new Date(b); +}; + +/** + * @type {(function(*, *): number)[]} + */ +const compareFunctions = [ + (a, b) => compareDates(a.bookingDate, b.bookingDate), + (a, b) => compareDates(a.bookingDateTime, b.bookingDateTime), + (a, b) => compareDates(a.valueDate, b.valueDate), + (a, b) => compareDates(a.valueDateTime, b.valueDateTime), +]; + export const sortByBookingDateOrValueDate = (transactions = []) => - transactions.sort( - (a, b) => - +new Date(b.bookingDate || b.valueDate) - - +new Date(a.bookingDate || a.valueDate), - ); + transactions.sort((a, b) => { + for (const sortFunction of compareFunctions) { + const result = sortFunction(b, a); + if (result !== 0) { + return result; + } + } + return 0; + }); export const amountToInteger = (n) => Math.round(n * 100); diff --git a/packages/sync-server/src/app-simplefin/app-simplefin.js b/packages/sync-server/src/app-simplefin/app-simplefin.js index 7d27c1bd7d2..7c123166f16 100644 --- a/packages/sync-server/src/app-simplefin/app-simplefin.js +++ b/packages/sync-server/src/app-simplefin/app-simplefin.js @@ -42,17 +42,13 @@ app.post( } } } - } catch (error) { + } catch { invalidToken(res); return; } - const now = new Date(); - const startDate = new Date(now.getFullYear(), now.getMonth(), 1); - const endDate = new Date(now.getFullYear(), now.getMonth() + 1, 1); - try { - const accounts = await getAccounts(accessKey, startDate, endDate); + const accounts = await getAccounts(accessKey, null, null, null, true); res.send({ status: 'ok', @@ -95,7 +91,11 @@ app.post( : startDate; let results; try { - results = await getTransactions(accessKey, new Date(earliestStartDate)); + results = await getTransactions( + accessKey, + Array.isArray(accountId) ? accountId : [accountId], + new Date(earliestStartDate), + ); } catch (e) { if (e.message === 'Forbidden') { invalidToken(res); @@ -297,12 +297,12 @@ async function getAccessKey(base64Token) { }); } -async function getTransactions(accessKey, startDate, endDate) { +async function getTransactions(accessKey, accounts, startDate, endDate) { const now = new Date(); startDate = startDate || new Date(now.getFullYear(), now.getMonth(), 1); endDate = endDate || new Date(now.getFullYear(), now.getMonth() + 1, 1); console.log(`${getDate(startDate)} - ${getDate(endDate)}`); - return await getAccounts(accessKey, startDate, endDate); + return await getAccounts(accessKey, accounts, startDate, endDate); } function getDate(date) { @@ -313,7 +313,13 @@ function normalizeDate(date) { return (date.valueOf() - date.getTimezoneOffset() * 60 * 1000) / 1000; } -async function getAccounts(accessKey, startDate, endDate) { +async function getAccounts( + accessKey, + accounts, + startDate, + endDate, + noTransactions = false, +) { const sfin = parseAccessKey(accessKey); const options = { headers: { @@ -323,16 +329,26 @@ async function getAccounts(accessKey, startDate, endDate) { }, }; const params = []; - let queryString = ''; - if (startDate) { - params.push(`start-date=${normalizeDate(startDate)}`); - } - if (endDate) { - params.push(`end-date=${normalizeDate(endDate)}`); + if (!noTransactions) { + if (startDate) { + params.push(`start-date=${normalizeDate(startDate)}`); + } + if (endDate) { + params.push(`end-date=${normalizeDate(endDate)}`); + } + + params.push(`pending=1`); + } else { + params.push(`balances-only=1`); } - params.push(`pending=1`); + if (accounts) { + accounts.forEach((id) => { + params.push(`account=${encodeURIComponent(id)}`); + }); + } + let queryString = ''; if (params.length > 0) { queryString += '?' + params.join('&'); } diff --git a/packages/sync-server/src/app-sync.js b/packages/sync-server/src/app-sync.js index 447f9d96611..79303216603 100644 --- a/packages/sync-server/src/app-sync.js +++ b/packages/sync-server/src/app-sync.js @@ -13,6 +13,16 @@ import { getPathForUserFile, getPathForGroupFile } from './util/paths.js'; import * as simpleSync from './sync-simple.js'; import { SyncProtoBuf } from '@actual-app/crdt'; +import { + File, + FilesService, + FileUpdate, +} from './app-sync/services/files-service.js'; +import { FileNotFound } from './app-sync/errors.js'; +import { + validateSyncedFile, + validateUploadedFile, +} from './app-sync/validation.js'; const app = express(); app.use(errorMiddleware); @@ -26,11 +36,24 @@ export { app as handlers }; const OK_RESPONSE = { status: 'ok' }; -// This is a version representing the internal format of sync -// messages. When this changes, all sync files need to be reset. We -// will check this version when syncing and notify the user if they -// need to reset. -const SYNC_FORMAT_VERSION = 2; +function boolToInt(deleted) { + return deleted ? 1 : 0; +} + +const verifyFileExists = (fileId, filesService, res, errorObject) => { + try { + return filesService.get(fileId); + } catch (e) { + if (e instanceof FileNotFound) { + //FIXME: error code should be 404. Need to make sure frontend is ok with it. + //TODO: put this into a middleware that checks if FileNotFound is thrown and returns 404 and same error message + // for every FileNotFound error + res.status(400).send(errorObject); + return; + } + throw e; + } +}; app.post('/sync', async (req, res) => { let requestPb; @@ -43,10 +66,9 @@ app.post('/sync', async (req, res) => { return; } - let accountDb = getAccountDb(); - let file_id = requestPb.getFileid() || null; - let group_id = requestPb.getGroupid() || null; - let key_id = requestPb.getKeyid() || null; + let fileId = requestPb.getFileid() || null; + let groupId = requestPb.getGroupid() || null; + let keyId = requestPb.getKeyid() || null; let since = requestPb.getSince() || null; let messages = requestPb.getMessagesList(); @@ -58,68 +80,27 @@ app.post('/sync', async (req, res) => { }); } - let currentFiles = accountDb.all( - 'SELECT group_id, encrypt_keyid, encrypt_meta, sync_version FROM files WHERE id = ?', - [file_id], - ); + const filesService = new FilesService(getAccountDb()); - if (currentFiles.length === 0) { - res.status(400); - res.send('file-not-found'); - return; - } - - let currentFile = currentFiles[0]; - - if ( - currentFile.sync_version == null || - currentFile.sync_version < SYNC_FORMAT_VERSION - ) { - res.status(400); - res.send('file-old-version'); - return; - } - - // When resetting sync state, something went wrong. There is no - // group id and it's awaiting a file to be uploaded. - if (currentFile.group_id == null) { - res.status(400); - res.send('file-needs-upload'); - return; - } + const currentFile = verifyFileExists( + fileId, + filesService, + res, + 'file-not-found', + ); - // Check to make sure the uploaded file is valid and has been - // encrypted with the same key it is registered with (this might - // be wrong if there was an error during the key creation - // process) - let uploadedKeyId = currentFile.encrypt_meta - ? JSON.parse(currentFile.encrypt_meta).keyId - : null; - if (uploadedKeyId !== currentFile.encrypt_keyid) { - res.status(400); - res.send('file-key-mismatch'); + if (!currentFile) { return; } - // The changes being synced are part of an old group, which - // means the file has been reset. User needs to re-download. - if (group_id !== currentFile.group_id) { + const errorMessage = validateSyncedFile(groupId, keyId, currentFile); + if (errorMessage) { res.status(400); - res.send('file-has-reset'); + res.send(errorMessage); return; } - // The data is encrypted with a different key which is - // unacceptable. We can't accept these changes. Reject them and - // tell the user that they need to generate the correct key - // (which necessitates a sync reset so they need to re-download). - if (key_id !== currentFile.encrypt_keyid) { - res.status(400); - res.send('file-has-new-key'); - return false; - } - - let { trie, newMessages } = simpleSync.sync(messages, since, group_id); + let { trie, newMessages } = simpleSync.sync(messages, since, groupId); // encode it back... let responsePb = new SyncProtoBuf.SyncResponse(); @@ -132,57 +113,70 @@ app.post('/sync', async (req, res) => { }); app.post('/user-get-key', (req, res) => { - let accountDb = getAccountDb(); let { fileId } = req.body; - let rows = accountDb.all( - 'SELECT encrypt_salt, encrypt_keyid, encrypt_test FROM files WHERE id = ?', - [fileId], - ); - if (rows.length === 0) { - res.status(400).send('file-not-found'); + const filesService = new FilesService(getAccountDb()); + const file = verifyFileExists(fileId, filesService, res, 'file-not-found'); + + if (!file) { return; } - let { encrypt_salt, encrypt_keyid, encrypt_test } = rows[0]; res.send({ status: 'ok', - data: { id: encrypt_keyid, salt: encrypt_salt, test: encrypt_test }, + data: { + id: file.encryptKeyId, + salt: file.encryptSalt, + test: file.encryptTest, + }, }); }); app.post('/user-create-key', (req, res) => { - let accountDb = getAccountDb(); let { fileId, keyId, keySalt, testContent } = req.body; - accountDb.mutate( - 'UPDATE files SET encrypt_salt = ?, encrypt_keyid = ?, encrypt_test = ? WHERE id = ?', - [keySalt, keyId, testContent, fileId], + const filesService = new FilesService(getAccountDb()); + + if (!verifyFileExists(fileId, filesService, res, 'file not found')) { + return; + } + + filesService.update( + fileId, + new FileUpdate({ + encryptSalt: keySalt, + encryptKeyId: keyId, + encryptTest: testContent, + }), ); res.send(OK_RESPONSE); }); app.post('/reset-user-file', async (req, res) => { - let accountDb = getAccountDb(); let { fileId } = req.body; - let files = accountDb.all('SELECT group_id FROM files WHERE id = ?', [ + const filesService = new FilesService(getAccountDb()); + const file = verifyFileExists( fileId, - ]); - if (files.length === 0) { - res.status(400).send('User or file not found'); + filesService, + res, + 'User or file not found', + ); + + if (!file) { return; } - let { group_id } = files[0]; - accountDb.mutate('UPDATE files SET group_id = NULL WHERE id = ?', [fileId]); + const groupId = file.groupId; + + filesService.update(fileId, new FileUpdate({ groupId: null })); - if (group_id) { + if (groupId) { try { - await fs.unlink(getPathForGroupFile(group_id)); - } catch (e) { - console.log(`Unable to delete sync data for group "${group_id}"`); + await fs.unlink(getPathForGroupFile(groupId)); + } catch { + console.log(`Unable to delete sync data for group "${groupId}"`); } } @@ -190,7 +184,6 @@ app.post('/reset-user-file', async (req, res) => { }); app.post('/upload-user-file', async (req, res) => { - let accountDb = getAccountDb(); if (typeof req.headers['x-actual-name'] !== 'string') { // FIXME: Not sure how this cannot be a string when the header is // set. @@ -215,38 +208,25 @@ app.post('/upload-user-file', async (req, res) => { ? JSON.parse(encryptMeta).keyId : null; - let currentFiles = accountDb.all( - 'SELECT group_id, encrypt_keyid, encrypt_meta FROM files WHERE id = ?', - [fileId], - ); - if (currentFiles.length > 0) { - let currentFile = currentFiles[0]; - - // The uploading file is part of an old group, so reject - // it. All of its internal sync state is invalid because its - // old. The sync state has been reset, so user needs to - // either reset again or download from the current group. - if (groupId !== currentFile.group_id) { - res.status(400); - res.send('file-has-reset'); - return; - } + const filesService = new FilesService(getAccountDb()); + let currentFile; - // The key that the file is encrypted with is different than - // the current registered key. All data must always be - // encrypted with the registered key for consistency. Key - // changes always necessitate a sync reset, which means this - // upload is trying to overwrite another reset. That might - // be be fine, but since we definitely cannot accept a file - // encrypted with the wrong key, we bail and suggest the - // user download the latest file. - if (keyId !== currentFile.encrypt_keyid) { - res.status(400); - res.send('file-has-new-key'); - return; + try { + currentFile = filesService.get(fileId); + } catch (e) { + if (e instanceof FileNotFound) { + currentFile = null; + } else { + throw e; } } + const errorMessage = validateUploadedFile(groupId, keyId, currentFile); + if (errorMessage) { + res.status(400).send(errorMessage); + return; + } + try { await fs.writeFile(getPathForUserFile(fileId), req.body); } catch (err) { @@ -255,37 +235,44 @@ app.post('/upload-user-file', async (req, res) => { return; } - let rows = accountDb.all('SELECT id FROM files WHERE id = ?', [fileId]); - if (rows.length === 0) { + if (!currentFile) { // it's new groupId = uuid.v4(); - accountDb.mutate( - 'INSERT INTO files (id, group_id, sync_version, name, encrypt_meta) VALUES (?, ?, ?, ?, ?)', - [fileId, groupId, syncFormatVersion, name, encryptMeta], - ); - res.send({ status: 'ok', groupId }); - } else { - if (!groupId) { - // sync state was reset, create new group - groupId = uuid.v4(); - accountDb.mutate('UPDATE files SET group_id = ? WHERE id = ?', [ - groupId, - fileId, - ]); - } - // Regardless, update some properties - accountDb.mutate( - 'UPDATE files SET sync_version = ?, encrypt_meta = ?, name = ? WHERE id = ?', - [syncFormatVersion, encryptMeta, name, fileId], + filesService.set( + new File({ + id: fileId, + groupId: groupId, + syncVersion: syncFormatVersion, + name: name, + encryptMeta: encryptMeta, + }), ); res.send({ status: 'ok', groupId }); + return; } + + if (!groupId) { + // sync state was reset, create new group + groupId = uuid.v4(); + filesService.update(fileId, new FileUpdate({ groupId: groupId })); + } + + // Regardless, update some properties + filesService.update( + fileId, + new FileUpdate({ + syncVersion: syncFormatVersion, + encryptMeta: encryptMeta, + name: name, + }), + ); + + res.send({ status: 'ok', groupId }); }); app.get('/download-user-file', async (req, res) => { - let accountDb = getAccountDb(); let fileId = req.headers['x-actual-file-id']; if (typeof fileId !== 'string') { // FIXME: Not sure how this cannot be a string when the header is @@ -294,13 +281,8 @@ app.get('/download-user-file', async (req, res) => { return; } - // Do some authentication - let rows = accountDb.all( - 'SELECT id FROM files WHERE id = ? AND deleted = FALSE', - [fileId], - ); - if (rows.length === 0) { - res.status(400).send('User or file not found'); + const filesService = new FilesService(getAccountDb()); + if (!verifyFileExists(fileId, filesService, res, 'User or file not found')) { return; } @@ -309,70 +291,69 @@ app.get('/download-user-file', async (req, res) => { }); app.post('/update-user-filename', (req, res) => { - let accountDb = getAccountDb(); let { fileId, name } = req.body; - // Do some authentication - let rows = accountDb.all( - 'SELECT id FROM files WHERE id = ? AND deleted = FALSE', - [fileId], - ); - if (rows.length === 0) { - res.status(400).send('file not found'); + const filesService = new FilesService(getAccountDb()); + + if (!verifyFileExists(fileId, filesService, res, 'file not found')) { return; } - accountDb.mutate('UPDATE files SET name = ? WHERE id = ?', [name, fileId]); - + filesService.update(fileId, new FileUpdate({ name: name })); res.send(OK_RESPONSE); }); app.get('/list-user-files', (req, res) => { - let accountDb = getAccountDb(); - let rows = accountDb.all('SELECT * FROM files'); - + const fileService = new FilesService(getAccountDb()); + const rows = fileService.find(); res.send({ status: 'ok', data: rows.map((row) => ({ - deleted: row.deleted, + deleted: boolToInt(row.deleted), fileId: row.id, - groupId: row.group_id, + groupId: row.groupId, name: row.name, - encryptKeyId: row.encrypt_keyid, + encryptKeyId: row.encryptKeyId, })), }); }); app.get('/get-user-file-info', (req, res) => { - let accountDb = getAccountDb(); let fileId = req.headers['x-actual-file-id']; - let rows = accountDb.all( - 'SELECT * FROM files WHERE id = ? AND deleted = FALSE', - [fileId], - ); + // TODO: Return 422 if fileId is not provided. Need to make sure frontend can handle it + // if (!fileId) { + // return res.status(422).send({ + // details: 'fileId-required', + // reason: 'unprocessable-entity', + // status: 'error', + // }); + // } - if (rows.length === 0) { - res.status(400).send({ status: 'error', reason: 'file-not-found' }); + const fileService = new FilesService(getAccountDb()); + + const file = verifyFileExists(fileId, fileService, res, { + status: 'error', + reason: 'file-not-found', + }); + + if (!file) { return; } - let row = rows[0]; - res.send({ status: 'ok', data: { - deleted: row.deleted, - fileId: row.id, - groupId: row.group_id, - name: row.name, - encryptMeta: row.encrypt_meta ? JSON.parse(row.encrypt_meta) : null, + deleted: boolToInt(file.deleted), // FIXME: convert to boolean, make sure it works in the frontend + fileId: file.id, + groupId: file.groupId, + name: file.name, + encryptMeta: file.encryptMeta ? JSON.parse(file.encryptMeta) : null, }, }); }); app.post('/delete-user-file', (req, res) => { - let accountDb = getAccountDb(); let { fileId } = req.body; if (!fileId) { @@ -383,12 +364,12 @@ app.post('/delete-user-file', (req, res) => { }); } - let rows = accountDb.all('SELECT * FROM files WHERE id = ?', [fileId]); - - if (rows.length === 0) { - return res.status(400).send('file-not-found'); + const filesService = new FilesService(getAccountDb()); + if (!verifyFileExists(fileId, filesService, res, 'file-not-found')) { + return; } - accountDb.mutate('UPDATE files SET deleted = TRUE WHERE id = ?', [fileId]); + filesService.update(fileId, new FileUpdate({ deleted: true })); + res.send(OK_RESPONSE); }); diff --git a/packages/sync-server/src/app-sync.test.js b/packages/sync-server/src/app-sync.test.js index 4c3328abd54..b81d5f14460 100644 --- a/packages/sync-server/src/app-sync.test.js +++ b/packages/sync-server/src/app-sync.test.js @@ -67,6 +67,54 @@ describe('/user-create-key', () => { status: 'error', }); }); + + it('returns 400 if the file is not found', async () => { + const res = await request(app) + .post('/user-create-key') + .set('x-actual-token', 'valid-token') + .send({ fileId: 'non-existent-file-id' }); + + expect(res.statusCode).toEqual(400); + expect(res.text).toBe('file not found'); + }); + + it('creates a new encryption key for the file', async () => { + const fileId = crypto.randomBytes(16).toString('hex'); + + const old_encrypt_salt = 'old-salt'; + const old_encrypt_keyid = 'old-key'; + const old_encrypt_test = 'old-encrypt-test'; + const encrypt_salt = 'test-salt'; + const encrypt_keyid = 'test-key-id'; + const encrypt_test = 'test-encrypt-test'; + + getAccountDb().mutate( + 'INSERT INTO files (id, encrypt_salt, encrypt_keyid, encrypt_test) VALUES (?, ?, ?, ?)', + [fileId, old_encrypt_salt, old_encrypt_keyid, old_encrypt_test], + ); + + const res = await request(app) + .post('/user-create-key') + .set('x-actual-token', 'valid-token') + .send({ + fileId, + keyId: encrypt_keyid, + keySalt: encrypt_salt, + testContent: encrypt_test, + }); + + expect(res.statusCode).toEqual(200); + expect(res.body).toEqual({ status: 'ok' }); + + const rows = getAccountDb().all( + 'SELECT encrypt_salt, encrypt_keyid, encrypt_test FROM files WHERE id = ?', + [fileId], + ); + + expect(rows[0].encrypt_salt).toEqual(encrypt_salt); + expect(rows[0].encrypt_keyid).toEqual(encrypt_keyid); + expect(rows[0].encrypt_test).toEqual(encrypt_test); + }); }); describe('/reset-user-file', () => { @@ -104,7 +152,7 @@ describe('/reset-user-file', () => { fileId, ]); - expect(rows[0].group_id).toBeNull; + expect(rows[0].group_id).toBeNull(); }); it('returns 400 if the file is not found', async () => { diff --git a/packages/sync-server/src/app-sync/errors.js b/packages/sync-server/src/app-sync/errors.js new file mode 100644 index 00000000000..b57867cdda3 --- /dev/null +++ b/packages/sync-server/src/app-sync/errors.js @@ -0,0 +1,13 @@ +export class FileNotFound extends Error { + constructor(params = {}) { + super("File does not exist or you don't have access to it"); + this.details = params; + } +} + +export class GenericFileError extends Error { + constructor(message, params = {}) { + super(message); + this.details = params; + } +} diff --git a/packages/sync-server/src/app-sync/services/files-service.js b/packages/sync-server/src/app-sync/services/files-service.js new file mode 100644 index 00000000000..63fe7f373b6 --- /dev/null +++ b/packages/sync-server/src/app-sync/services/files-service.js @@ -0,0 +1,197 @@ +import getAccountDb from '../../account-db.js'; +import { FileNotFound, GenericFileError } from '../errors.js'; + +class FileBase { + constructor( + name, + groupId, + encryptSalt, + encryptTest, + encryptKeyId, + encryptMeta, + syncVersion, + deleted, + ) { + this.name = name; + this.groupId = groupId; + this.encryptSalt = encryptSalt; + this.encryptTest = encryptTest; + this.encryptKeyId = encryptKeyId; + this.encryptMeta = encryptMeta; + this.syncVersion = syncVersion; + this.deleted = typeof deleted === 'boolean' ? deleted : Boolean(deleted); + } +} + +class File extends FileBase { + constructor({ + id, + name = null, + groupId = null, + encryptSalt = null, + encryptTest = null, + encryptKeyId = null, + encryptMeta = null, + syncVersion = null, + deleted = false, + }) { + super( + name, + groupId, + encryptSalt, + encryptTest, + encryptKeyId, + encryptMeta, + syncVersion, + deleted, + ); + this.id = id; + } +} + +/** + * Represents a file update. Will only update the fields that are defined. + * @class + * @extends FileBase + */ +class FileUpdate extends FileBase { + constructor({ + name = undefined, + groupId = undefined, + encryptSalt = undefined, + encryptTest = undefined, + encryptKeyId = undefined, + encryptMeta = undefined, + syncVersion = undefined, + deleted = undefined, + }) { + super( + name, + groupId, + encryptSalt, + encryptTest, + encryptKeyId, + encryptMeta, + syncVersion, + deleted, + ); + } +} + +const boolToInt = (bool) => { + return bool ? 1 : 0; +}; + +class FilesService { + constructor(accountDb) { + this.accountDb = accountDb; + } + + get(fileId) { + const rawFile = this.getRaw(fileId); + if (!rawFile || (rawFile && rawFile.deleted)) { + throw new FileNotFound(); + } + + return this.validate(rawFile); + } + + set(file) { + const deletedInt = boolToInt(file.deleted); + this.accountDb.mutate( + 'INSERT INTO files (id, group_id, sync_version, name, encrypt_meta, encrypt_salt, encrypt_test, encrypt_keyid, deleted) VALUES (?, ?, ?, ?, ?, ?, ?,? ,?)', + [ + file.id, + file.groupId, + file.syncVersion.toString(), + file.name, + file.encryptMeta, + file.encryptSalt, + file.encrypt_test, + file.encrypt_keyid, + deletedInt, + ], + ); + } + + find(limit = 1000) { + return this.accountDb + .all('SELECT * FROM files WHERE deleted = 0 LIMIT ?', [limit]) + .map(this.validate); + } + + update(id, fileUpdate) { + let query = 'UPDATE files SET'; + const params = []; + const updates = []; + + if (fileUpdate.name !== undefined) { + updates.push('name = ?'); + params.push(fileUpdate.name); + } + if (fileUpdate.groupId !== undefined) { + updates.push('group_id = ?'); + params.push(fileUpdate.groupId); + } + if (fileUpdate.encryptSalt !== undefined) { + updates.push('encrypt_salt = ?'); + params.push(fileUpdate.encryptSalt); + } + if (fileUpdate.encryptTest !== undefined) { + updates.push('encrypt_test = ?'); + params.push(fileUpdate.encryptTest); + } + if (fileUpdate.encryptKeyId !== undefined) { + updates.push('encrypt_keyid = ?'); + params.push(fileUpdate.encryptKeyId); + } + if (fileUpdate.encryptMeta !== undefined) { + updates.push('encrypt_meta = ?'); + params.push(fileUpdate.encryptMeta); + } + if (fileUpdate.syncVersion !== undefined) { + updates.push('sync_version = ?'); + params.push(fileUpdate.syncVersion); + } + if (fileUpdate.deleted !== undefined) { + updates.push('deleted = ?'); + params.push(boolToInt(fileUpdate.deleted)); + } + + if (updates.length > 0) { + query += ' ' + updates.join(', ') + ' WHERE id = ?'; + params.push(id); + + const res = this.accountDb.mutate(query, params); + + if (res.changes != 1) { + throw new GenericFileError('Could not update File', { id }); + } + } + + // Return the modified object + return this.validate(this.getRaw(id)); + } + + getRaw(fileId) { + return this.accountDb.first(`SELECT * FROM files WHERE id = ?`, [fileId]); + } + + validate(rawFile) { + return new File({ + id: rawFile.id, + name: rawFile.name, + groupId: rawFile.group_id, + encryptSalt: rawFile.encrypt_salt, + encryptTest: rawFile.encrypt_test, + encryptKeyId: rawFile.encrypt_keyid, + encryptMeta: rawFile.encrypt_meta, + syncVersion: rawFile.sync_version, + deleted: Boolean(rawFile.deleted), + }); + } +} + +const filesService = new FilesService(getAccountDb()); + +export { filesService, FilesService, File, FileUpdate }; diff --git a/packages/sync-server/src/app-sync/tests/services/files-service.test.js b/packages/sync-server/src/app-sync/tests/services/files-service.test.js new file mode 100644 index 00000000000..7d3dadd6d11 --- /dev/null +++ b/packages/sync-server/src/app-sync/tests/services/files-service.test.js @@ -0,0 +1,214 @@ +import getAccountDb from '../../../account-db.js'; +import { FileNotFound } from '../../errors.js'; +import { + FilesService, + File, + FileUpdate, +} from '../../services/files-service.js'; // Adjust the path as necessary +import crypto from 'node:crypto'; +describe('FilesService', () => { + let filesService; + let accountDb; + + const insertToyExampleData = () => { + accountDb.mutate( + 'INSERT INTO files (id, group_id, sync_version, name, encrypt_meta, encrypt_salt, encrypt_test, encrypt_keyid, deleted) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?)', + [ + '1', + 'group1', + 1, + 'file1', + '{"key":"value"}', + 'salt', + 'test', + 'keyid', + 0, + ], + ); + }; + + const clearDatabase = () => { + accountDb.mutate('DELETE FROM files'); + }; + + beforeAll((done) => { + accountDb = getAccountDb(); + filesService = new FilesService(accountDb); + done(); + }); + + beforeEach((done) => { + insertToyExampleData(); + done(); + }); + + afterEach((done) => { + clearDatabase(); + done(); + }); + + test('get should return a file', () => { + const file = filesService.get('1'); + const expectedFile = new File({ + id: '1', + groupId: 'group1', + syncVersion: 1, + name: 'file1', + encryptMeta: '{"key":"value"}', + encryptSalt: 'salt', + encryptTest: 'test', + encryptKeyId: 'keyid', + deleted: false, + }); + + expect(file).toEqual(expectedFile); + }); + + test('get should throw FileNotFound if file is deleted or does not exist', () => { + const fileId = crypto.randomBytes(16).toString('hex'); + accountDb.mutate( + 'INSERT INTO files (id, group_id, sync_version, name, encrypt_meta, encrypt_salt, encrypt_test, encrypt_keyid, deleted) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?)', + [ + fileId, + 'group1', + 1, + 'file1', + '{"key":"value"}', + 'salt', + 'test', + 'keyid', + 1, + ], + ); + + expect(() => { + filesService.get(fileId); + }).toThrow(FileNotFound); + + expect(() => { + filesService.get(crypto.randomBytes(16).toString('hex')); + }).toThrow(FileNotFound); + }); + + test.each([true, false])( + 'set should insert a new file with deleted: %p', + (deleted) => { + const fileId = crypto.randomBytes(16).toString('hex'); + const newFile = new File({ + id: fileId, + groupId: 'group2', + syncVersion: 1, + name: 'file2', + encryptMeta: '{"key":"value2"}', + deleted: deleted, + }); + + filesService.set(newFile); + + const file = filesService.validate(filesService.getRaw(fileId)); + const expectedFile = new File({ + id: fileId, + groupId: 'group2', + syncVersion: 1, + name: 'file2', + encryptMeta: '{"key":"value2"}', + encryptSalt: null, // default value + encryptTest: null, // default value + encryptKeyId: null, // default value + deleted: deleted, + }); + + expect(file).toEqual(expectedFile); + }, + ); + + test('find should return a list of files', () => { + const files = filesService.find(); + expect(files.length).toBe(1); + expect(files[0]).toEqual( + new File({ + id: '1', + groupId: 'group1', + syncVersion: 1, + name: 'file1', + encryptMeta: '{"key":"value"}', + encryptSalt: 'salt', + encryptTest: 'test', + encryptKeyId: 'keyid', + deleted: false, + }), + ); + }); + + test('find should respect the limit parameter', () => { + filesService.set( + new File({ + id: crypto.randomBytes(16).toString('hex'), + groupId: 'group2', + syncVersion: 1, + name: 'file2', + encryptMeta: '{"key":"value2"}', + deleted: false, + }), + ); + // Make sure that the file was inserted + const allFiles = filesService.find(); + expect(allFiles.length).toBe(2); + + // Limit the number of files returned + const limitedFiles = filesService.find(1); + expect(limitedFiles.length).toBe(1); + }); + + test('update should modify all attributes of an existing file', () => { + const fileUpdate = new FileUpdate({ + name: 'updatedFile1', + groupId: 'updatedGroup1', + encryptSalt: 'updatedSalt', + encryptTest: 'updatedTest', + encryptKeyId: 'updatedKeyId', + encryptMeta: '{"key":"updatedValue"}', + syncVersion: 2, + deleted: true, + }); + const updatedFile = filesService.update('1', fileUpdate); + + expect(updatedFile).toEqual( + new File({ + id: '1', + name: 'updatedFile1', + groupId: 'updatedGroup1', + encryptSalt: 'updatedSalt', + encryptTest: 'updatedTest', + encryptMeta: '{"key":"updatedValue"}', + encryptKeyId: 'updatedKeyId', + syncVersion: 2, + deleted: true, + }), + ); + }); + + test.each([['update-group', null]])( + 'update should modify a single attribute with groupId = $groupId', + (newGroupId) => { + const fileUpdate = new FileUpdate({ + groupId: newGroupId, + }); + const updatedFile = filesService.update('1', fileUpdate); + + expect(updatedFile).toEqual( + new File({ + id: '1', + name: 'file1', + groupId: newGroupId, + syncVersion: 1, + encryptMeta: '{"key":"value"}', + encryptSalt: 'salt', + encryptTest: 'test', + encryptKeyId: 'keyid', + deleted: false, + }), + ); + }, + ); +}); diff --git a/packages/sync-server/src/app-sync/validation.js b/packages/sync-server/src/app-sync/validation.js new file mode 100644 index 00000000000..4fe3fff2c02 --- /dev/null +++ b/packages/sync-server/src/app-sync/validation.js @@ -0,0 +1,77 @@ +// This is a version representing the internal format of sync +// messages. When this changes, all sync files need to be reset. We +// will check this version when syncing and notify the user if they +// need to reset. +const SYNC_FORMAT_VERSION = 2; + +const validateSyncedFile = (groupId, keyId, currentFile) => { + if ( + currentFile.syncVersion == null || + currentFile.syncVersion < SYNC_FORMAT_VERSION + ) { + return 'file-old-version'; + } + + // When resetting sync state, something went wrong. There is no + // group id and it's awaiting a file to be uploaded. + if (currentFile.groupId == null) { + return 'file-needs-upload'; + } + + // Check to make sure the uploaded file is valid and has been + // encrypted with the same key it is registered with (this might + // be wrong if there was an error during the key creation + // process) + let uploadedKeyId = currentFile.encryptMeta + ? JSON.parse(currentFile.encryptMeta).keyId + : null; + if (uploadedKeyId !== currentFile.encryptKeyId) { + return 'file-key-mismatch'; + } + + // The changes being synced are part of an old group, which + // means the file has been reset. User needs to re-download. + if (groupId !== currentFile.groupId) { + return 'file-has-reset'; + } + + // The data is encrypted with a different key which is + // unacceptable. We can't accept these changes. Reject them and + // tell the user that they need to generate the correct key + // (which necessitates a sync reset so they need to re-download). + if (keyId !== currentFile.encryptKeyId) { + return 'file-has-new-key'; + } + + return null; +}; + +const validateUploadedFile = (groupId, keyId, currentFile) => { + if (!currentFile) { + // File is new, so no need to validate + return null; + } + // The uploading file is part of an old group, so reject + // it. All of its internal sync state is invalid because its + // old. The sync state has been reset, so user needs to + // either reset again or download from the current group. + if (groupId !== currentFile.groupId) { + return 'file-has-reset'; + } + + // The key that the file is encrypted with is different than + // the current registered key. All data must always be + // encrypted with the registered key for consistency. Key + // changes always necessitate a sync reset, which means this + // upload is trying to overwrite another reset. That might + // be be fine, but since we definitely cannot accept a file + // encrypted with the wrong key, we bail and suggest the + // user download the latest file. + if (keyId !== currentFile.encryptKeyId) { + return 'file-has-new-key'; + } + + return null; +}; + +export { validateSyncedFile, validateUploadedFile }; diff --git a/packages/sync-server/src/load-config.js b/packages/sync-server/src/load-config.js index d99ce421180..19696d59df5 100644 --- a/packages/sync-server/src/load-config.js +++ b/packages/sync-server/src/load-config.js @@ -11,6 +11,11 @@ debug(`project root: '${projectRoot}'`); export const sqlDir = path.join(projectRoot, 'src', 'sql'); let defaultDataDir = fs.existsSync('/data') ? '/data' : projectRoot; + +if (process.env.ACTUAL_DATA_DIR) { + defaultDataDir = process.env.ACTUAL_DATA_DIR; +} + debug(`default data directory: '${defaultDataDir}'`); function parseJSON(path, allowMissing = false) { @@ -135,7 +140,6 @@ const finalConfig = { } : config.upload, }; - debug(`using port ${finalConfig.port}`); debug(`using hostname ${finalConfig.hostname}`); debug(`using data directory ${finalConfig.dataDir}`); diff --git a/packages/sync-server/src/util/middlewares.js b/packages/sync-server/src/util/middlewares.js index 14e6e4dc14f..5bbec6946ad 100644 --- a/packages/sync-server/src/util/middlewares.js +++ b/packages/sync-server/src/util/middlewares.js @@ -7,10 +7,22 @@ import * as expressWinston from 'express-winston'; * @param {Error} err * @param {import('express').Request} req * @param {import('express').Response} res - * @param {import('express').NextFunction} _next + * @param {import('express').NextFunction} next */ -async function errorMiddleware(err, req, res, _next) { - console.log('ERROR', err); +async function errorMiddleware(err, req, res, next) { + if (res.headersSent) { + // If you call next() with an error after you have started writing the response + // (for example, if you encounter an error while streaming the response + // to the client), the Express default error handler closes + // the connection and fails the request. + + // So when you add a custom error handler, you must delegate + // to the default Express error handler, when the headers + // have already been sent to the client + // Source: https://expressjs.com/en/guide/error-handling.html + return next(err); + } + console.log(`Error on endpoint ${req.url}`, err.message, err.stack); res.status(500).send({ status: 'error', reason: 'internal-error' }); } diff --git a/packages/sync-server/upcoming-release-notes/474.md b/packages/sync-server/upcoming-release-notes/474.md deleted file mode 100644 index 2e2e3623f92..00000000000 --- a/packages/sync-server/upcoming-release-notes/474.md +++ /dev/null @@ -1,6 +0,0 @@ ---- -category: Bugfix -authors: [davidmartos96] ---- - -Fixes Sabadell Bank regression, by including the date field during normalization diff --git a/packages/sync-server/upcoming-release-notes/487.md b/packages/sync-server/upcoming-release-notes/487.md deleted file mode 100644 index c0c53da4646..00000000000 --- a/packages/sync-server/upcoming-release-notes/487.md +++ /dev/null @@ -1,6 +0,0 @@ ---- -category: Bugfix -authors: [MikesGlitch] ---- - -Fix migrations not running properly on inital setup diff --git a/yarn.lock b/yarn.lock index 85c0cdb87ca..3072997535c 100644 --- a/yarn.lock +++ b/yarn.lock @@ -38,7 +38,7 @@ __metadata: languageName: unknown linkType: soft -"@actual-app/crdt@npm:*, @actual-app/crdt@workspace:^, @actual-app/crdt@workspace:packages/crdt": +"@actual-app/crdt@npm:*, @actual-app/crdt@npm:2.1.0, @actual-app/crdt@workspace:^, @actual-app/crdt@workspace:packages/crdt": version: 0.0.0-use.local resolution: "@actual-app/crdt@workspace:packages/crdt" dependencies: @@ -55,7 +55,7 @@ __metadata: languageName: unknown linkType: soft -"@actual-app/web@npm:*, @actual-app/web@workspace:packages/desktop-client": +"@actual-app/web@npm:24.11.0, @actual-app/web@workspace:packages/desktop-client": version: 0.0.0-use.local resolution: "@actual-app/web@workspace:packages/desktop-client" dependencies: @@ -7519,8 +7519,8 @@ __metadata: version: 0.0.0-use.local resolution: "actual-sync@workspace:packages/sync-server" dependencies: - "@actual-app/crdt": "npm:*" - "@actual-app/web": "npm:*" + "@actual-app/crdt": "npm:2.1.0" + "@actual-app/web": "npm:24.11.0" "@babel/preset-typescript": "npm:^7.20.2" "@types/bcrypt": "npm:^5.0.2" "@types/better-sqlite3": "npm:^7.6.7" From 4036b570ecb88ce55e47041bd399567a9104f61e Mon Sep 17 00:00:00 2001 From: Mike Clark Date: Thu, 7 Nov 2024 21:08:17 +0000 Subject: [PATCH 42/55] responsive --- .../src/components/manager/ConfigInternalSyncServer.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/desktop-client/src/components/manager/ConfigInternalSyncServer.tsx b/packages/desktop-client/src/components/manager/ConfigInternalSyncServer.tsx index 7e0be3ddf98..7a7038b16d2 100644 --- a/packages/desktop-client/src/components/manager/ConfigInternalSyncServer.tsx +++ b/packages/desktop-client/src/components/manager/ConfigInternalSyncServer.tsx @@ -5,7 +5,6 @@ import { Trans, useTranslation } from 'react-i18next'; import { useGlobalPref } from '../../hooks/useGlobalPref'; import { useNavigate } from '../../hooks/useNavigate'; -import { useResponsive } from '../../ResponsiveProvider'; import { styles, theme } from '../../style'; import { Button, ButtonWithLoading } from '../common/Button2'; import { Input } from '../common/Input'; @@ -14,6 +13,7 @@ import { Text } from '../common/Text'; import { View } from '../common/View'; import { Title } from './subscribe/common'; +import { useResponsive } from '../responsive/ResponsiveProvider'; export function ConfigInternalSyncServer() { const { t } = useTranslation(); From c718e675b80548b7bd08ea00e2afa2215c58e947 Mon Sep 17 00:00:00 2001 From: Mike Clark Date: Thu, 7 Nov 2024 21:33:01 +0000 Subject: [PATCH 43/55] test --- packages/loot-core/src/platform/server/fs/index.electron.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/loot-core/src/platform/server/fs/index.electron.ts b/packages/loot-core/src/platform/server/fs/index.electron.ts index fc165ab6383..9d78de4bcb5 100644 --- a/packages/loot-core/src/platform/server/fs/index.electron.ts +++ b/packages/loot-core/src/platform/server/fs/index.electron.ts @@ -13,7 +13,7 @@ let rootPath = path.join(__dirname, '..', '..', '..', '..'); if (__filename.match('bundle')) { // The file name is not our filename and indicates that we're in the // bundled form. Because of this, the root path is different. - rootPath = path.join(__dirname, '..', '..'); + rootPath = path.join(__dirname, '..'); } export const init = () => { From 2a1bb2cf03f86adf496976bb21a6c125334f39b6 Mon Sep 17 00:00:00 2001 From: Mike Clark Date: Thu, 7 Nov 2024 21:35:27 +0000 Subject: [PATCH 44/55] put it back --- packages/loot-core/src/platform/server/fs/index.electron.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/loot-core/src/platform/server/fs/index.electron.ts b/packages/loot-core/src/platform/server/fs/index.electron.ts index 9d78de4bcb5..fc165ab6383 100644 --- a/packages/loot-core/src/platform/server/fs/index.electron.ts +++ b/packages/loot-core/src/platform/server/fs/index.electron.ts @@ -13,7 +13,7 @@ let rootPath = path.join(__dirname, '..', '..', '..', '..'); if (__filename.match('bundle')) { // The file name is not our filename and indicates that we're in the // bundled form. Because of this, the root path is different. - rootPath = path.join(__dirname, '..'); + rootPath = path.join(__dirname, '..', '..'); } export const init = () => { From 8847f64dbe77f7aa91f1daa787ca4b46f2e8dcd4 Mon Sep 17 00:00:00 2001 From: Mike Clark Date: Sat, 9 Nov 2024 20:28:49 +0000 Subject: [PATCH 45/55] fix rootpath --- .../src/platform/server/fs/index.electron.ts | 13 +++++++++---- 1 file changed, 9 insertions(+), 4 deletions(-) diff --git a/packages/loot-core/src/platform/server/fs/index.electron.ts b/packages/loot-core/src/platform/server/fs/index.electron.ts index fc165ab6383..f03554f6df4 100644 --- a/packages/loot-core/src/platform/server/fs/index.electron.ts +++ b/packages/loot-core/src/platform/server/fs/index.electron.ts @@ -10,10 +10,15 @@ export { getDocumentDir, getBudgetDir, _setDocumentDir } from './shared'; let rootPath = path.join(__dirname, '..', '..', '..', '..'); -if (__filename.match('bundle')) { - // The file name is not our filename and indicates that we're in the - // bundled form. Because of this, the root path is different. - rootPath = path.join(__dirname, '..', '..'); +switch (path.basename(__filename)) { + case 'bundle.api.js': // api bundle uses the electron bundle - account for its file structure + rootPath = path.join(__dirname, '..'); + break; + case 'bundle.desktop.js': // electron app + rootPath = path.join(__dirname, '..', '..'); + break; + default: + break; } export const init = () => { From 2656d6508c2fed65eff3f2fddfc6f9e096552fbd Mon Sep 17 00:00:00 2001 From: Mike Clark Date: Wed, 20 Nov 2024 14:57:08 +0000 Subject: [PATCH 46/55] bits --- .../manager/ConfigExternalSyncServer.tsx | 70 ++--- .../manager/ConfigInternalSyncServer.tsx | 42 ++- .../src/components/manager/ConfigServer.tsx | 253 ++++++------------ .../src/components/manager/ManagementApp.jsx | 2 +- packages/loot-core/src/mocks/budget.ts | 12 +- 5 files changed, 137 insertions(+), 242 deletions(-) diff --git a/packages/desktop-client/src/components/manager/ConfigExternalSyncServer.tsx b/packages/desktop-client/src/components/manager/ConfigExternalSyncServer.tsx index dc3eed32a71..bcacaca582d 100644 --- a/packages/desktop-client/src/components/manager/ConfigExternalSyncServer.tsx +++ b/packages/desktop-client/src/components/manager/ConfigExternalSyncServer.tsx @@ -1,6 +1,7 @@ // @ts-strict-ignore import React, { useState, useEffect, useCallback } from 'react'; import { Trans, useTranslation } from 'react-i18next'; +import { type To } from 'react-router-dom'; import { isNonProductionEnvironment, @@ -109,19 +110,19 @@ export function ConfigExternalSyncServer() { } } - async function onSkip() { - await setServerUrl(null); - await loggedIn(); + function onStopUsingExternalServer() { + setServerUrl(null); + loggedIn(); navigate('/'); } - async function onCreateTestFile() { - await setServerUrl(null); - await createBudget({ testMode: true }); - window.__navigate('/'); - } - - if (isElectron()) { + function onBack() { + // If server url is setup, go back to files manager, otherwise go to server setup + if (currentUrl) { + navigate('/'); + } else { + navigate(-1); + } } return ( @@ -142,8 +143,7 @@ export function ConfigExternalSyncServer() { ) : ( - There is no server configured. After running the server, specify the - URL here to use the app. You can always change this later. We will + After running the server, specify the URL here to use it. We will validate that Actual is running at this URL. )} @@ -216,56 +216,20 @@ export function ConfigExternalSyncServer() { )}
- - {currentUrl ? ( - + {currentUrl && ( + - ) : ( - <> - {!isElectron() && ( - - )} - - - {isNonProductionEnvironment() && ( - - )} - )} diff --git a/packages/desktop-client/src/components/manager/ConfigInternalSyncServer.tsx b/packages/desktop-client/src/components/manager/ConfigInternalSyncServer.tsx index 7a7038b16d2..805899d79dc 100644 --- a/packages/desktop-client/src/components/manager/ConfigInternalSyncServer.tsx +++ b/packages/desktop-client/src/components/manager/ConfigInternalSyncServer.tsx @@ -3,6 +3,8 @@ import React, { useState, useEffect, useCallback } from 'react'; import { Group, NumberField } from 'react-aria-components'; import { Trans, useTranslation } from 'react-i18next'; +import { loggedIn } from 'loot-core/client/actions'; + import { useGlobalPref } from '../../hooks/useGlobalPref'; import { useNavigate } from '../../hooks/useNavigate'; import { styles, theme } from '../../style'; @@ -11,9 +13,9 @@ import { Input } from '../common/Input'; import { Label } from '../common/Label'; import { Text } from '../common/Text'; import { View } from '../common/View'; +import { useResponsive } from '../responsive/ResponsiveProvider'; import { Title } from './subscribe/common'; -import { useResponsive } from '../responsive/ResponsiveProvider'; export function ConfigInternalSyncServer() { const { t } = useTranslation(); @@ -36,7 +38,8 @@ export function ConfigInternalSyncServer() { } : {}; - const [ngrokConfig] = useGlobalPref('ngrokConfig'); + const [ngrokConfig, setNgrokConfig] = useGlobalPref('ngrokConfig'); + const exposeActualServer = async () => { const hasRequiredNgrokSettings = ngrokConfig?.authToken && ngrokConfig?.port && ngrokConfig?.domain; @@ -62,6 +65,21 @@ export function ConfigInternalSyncServer() { }); }; + function onStopUsingInternalServer() { + setNgrokConfig(undefined); + loggedIn(); + navigate('/'); + } + + function onBack() { + // If ngrok is setup, go back to files manager, otherwise go to server setup + if (ngrokConfig) { + navigate('/'); + } else { + navigate(-1); + } + } + return ( @@ -122,14 +140,27 @@ export function ConfigInternalSyncServer() { /> </View> </View> - <View> - <Button onPress={() => navigate(-1)}>Back</Button> + <View + style={{ + flexDirection: 'row', + flexFlow: 'row wrap', + justifyContent: 'center', + marginTop: 15, + gap: '10px', + }} + > + <Button onPress={onBack}>Back</Button> + {ngrokConfig && ( + <Button onPress={onStopUsingInternalServer}> + Stop using internal server + </Button> + )} + <Button variant="primary" onPress={startActualServer} style={{ ...narrowButtonStyle, - marginLeft: 10, }} > Start Server @@ -139,7 +170,6 @@ export function ConfigInternalSyncServer() { onPress={exposeActualServer} style={{ ...narrowButtonStyle, - marginLeft: 10, }} > Expose Server diff --git a/packages/desktop-client/src/components/manager/ConfigServer.tsx b/packages/desktop-client/src/components/manager/ConfigServer.tsx index 44b13d2e61a..8ed4e94eba4 100644 --- a/packages/desktop-client/src/components/manager/ConfigServer.tsx +++ b/packages/desktop-client/src/components/manager/ConfigServer.tsx @@ -1,6 +1,7 @@ // @ts-strict-ignore -import React, { useState, useEffect, useCallback } from 'react'; +import React, { useEffect } from 'react'; import { Trans, useTranslation } from 'react-i18next'; +import { redirect } from 'react-router-dom'; import { isNonProductionEnvironment, @@ -11,9 +12,7 @@ import { useActions } from '../../hooks/useActions'; import { useGlobalPref } from '../../hooks/useGlobalPref'; import { useNavigate } from '../../hooks/useNavigate'; import { theme } from '../../style'; -import { Button, ButtonWithLoading } from '../common/Button2'; -import { BigInput } from '../common/Input'; -import { Link } from '../common/Link'; +import { Button } from '../common/Button2'; import { Text } from '../common/Text'; import { View } from '../common/View'; import { useServerURL, useSetServerURL } from '../ServerContext'; @@ -22,85 +21,18 @@ import { Title } from './subscribe/common'; export function ConfigServer() { const { t } = useTranslation(); - const { createBudget, signOut, loggedIn } = useActions(); + const { createBudget, loggedIn } = useActions(); const navigate = useNavigate(); - const [url, setUrl] = useState(''); const currentUrl = useServerURL(); + const [ngrokConfig] = useGlobalPref('ngrokConfig'); const setServerUrl = useSetServerURL(); - useEffect(() => { - setUrl(currentUrl); - }, [currentUrl]); - const [loading, setLoading] = useState(false); - const [error, setError] = useState<string | null>(null); - - const restartElectronServer = useCallback(() => { - globalThis.window.Actual.restartElectronServer(); - setError(null); - }, []); - - const [_serverSelfSignedCert, setServerSelfSignedCert] = useGlobalPref( - 'serverSelfSignedCert', - restartElectronServer, - ); + const onShowExternalConfiguration = () => { + navigate('/config-server/external'); + }; - function getErrorMessage(error: string) { - switch (error) { - case 'network-failure': - return t( - 'Server is not running at this URL. Make sure you have HTTPS set up properly.', - ); - default: - return t( - 'Server does not look like an Actual server. Is it set up correctly?', - ); - } - } - - async function onSubmit() { - if (url === '' || loading) { - return; - } - - setError(null); - setLoading(true); - - let httpUrl = url; - if (!url.startsWith('http://') && !url.startsWith('https://')) { - httpUrl = 'https://' + url; - } - - const { error } = await setServerUrl(httpUrl); - setUrl(httpUrl); - - if (error) { - setLoading(false); - setError(error); - } else { - setLoading(false); - await signOut(); - navigate('/'); - } - } - - function onSameDomain() { - setUrl(window.location.origin); - } - - async function onSelectSelfSignedCertificate() { - const selfSignedCertificateLocation = await window.Actual?.openFileDialog({ - properties: ['openFile'], - filters: [ - { - name: 'Self Signed Certificate', - extensions: ['crt', 'pem'], - }, - ], - }); - - if (selfSignedCertificateLocation) { - setServerSelfSignedCert(selfSignedCertificateLocation[0]); - } - } + const onShowInternalConfiguration = () => { + navigate('/config-server/internal'); + }; async function onSkip() { await setServerUrl(null); @@ -114,98 +46,80 @@ export function ConfigServer() { window.__navigate('/'); } + // let serverConfiguration = undefined; + // if (currentUrl) { + // serverConfiguration = 'external'; + // } else if (ngrokConfig) { + // serverConfiguration = 'internal'; + // } + useEffect(() => { + // If user has already setup server navigate them to the configure screen + // TODO: make an easy setting to determin if internal or external server is setup + if (currentUrl) { + // external + navigate('/config-server/external'); + } else if (ngrokConfig) { + // internal + navigate('/config-server/internal'); + } + }, [currentUrl, navigate, ngrokConfig]); + return ( <View style={{ maxWidth: 500, marginTop: -30 }}> - <Title text={t('Where’s the server?')} /> - - <Text - style={{ - fontSize: 16, - color: theme.tableRowHeaderText, - lineHeight: 1.5, - }} - > - {currentUrl ? ( - <Trans> - Existing sessions will be logged out and you will log in to this - server. We will validate that Actual is running at this URL. - </Trans> - ) : ( - <Trans> - There is no server configured. After running the server, specify the - URL here to use the app. You can always change this later. We will - validate that Actual is running at this URL. - </Trans> - )} - </Text> - - {error && ( - <> + <Title text={t('Setup your server')} /> + + {isElectron() && ( + <View + style={{ + flexDirection: 'column', + gap: '1rem', + alignItems: 'center', + marginBottom: '20px', + }} + > <Text style={{ - marginTop: 20, - color: theme.errorText, - borderRadius: 4, - fontSize: 15, + fontSize: 16, + color: theme.pageText, + lineHeight: 1.5, }} > - {getErrorMessage(error)} + <Trans> + If you like, Actual can connect to a server for you to sync your + data across devices. + </Trans> </Text> - {isElectron() && ( - <View - style={{ display: 'flex', flexDirection: 'row', marginTop: 20 }} - > - <Text - style={{ - color: theme.errorText, - borderRadius: 4, - fontSize: 15, - }} - > - <Trans> - If the server is using a self-signed certificate{' '} - <Link - variant="text" - style={{ fontSize: 15 }} - onClick={onSelectSelfSignedCertificate} - > - select it here - </Link> - . - </Trans> - </Text> - </View> - )} - </> - )} - - <View style={{ display: 'flex', flexDirection: 'row', marginTop: 30 }}> - <BigInput - autoFocus={true} - placeholder={t('https://example.com')} - value={url || ''} - onChangeValue={setUrl} - style={{ flex: 1, marginRight: 10 }} - onEnter={onSubmit} - /> - <ButtonWithLoading - variant="primary" - isLoading={loading} - style={{ fontSize: 15 }} - onPress={onSubmit} - > - {t('OK')} - </ButtonWithLoading> - {currentUrl && ( - <Button - variant="bare" - style={{ fontSize: 15, marginLeft: 10 }} - onPress={() => navigate(-1)} + <Text + style={{ + fontSize: 16, + color: theme.pageText, + lineHeight: 1.5, + }} > - {t('Cancel')} - </Button> - )} - </View> + <Trans> + Would you like to host the server on your computer or connect to + an external server? + </Trans> + </Text> + <View + style={{ + flexDirection: 'row', + flexFlow: 'row wrap', + justifyContent: 'center', + marginTop: 15, + gap: '15px', + }} + > + <Button onPress={onShowInternalConfiguration}> + Host on this computer + </Button> + + <Button onPress={onShowExternalConfiguration}> + Connect to an external server + </Button> + </View> + </View> + )} <View style={{ @@ -225,19 +139,6 @@ export function ConfigServer() { </Button> ) : ( <> - {!isElectron() && ( - <Button - variant="bare" - style={{ - color: theme.pageTextLight, - margin: 5, - marginRight: 15, - }} - onPress={onSameDomain} - > - {t('Use current domain')} - </Button> - )} <Button variant="bare" style={{ color: theme.pageTextLight, margin: 5 }} diff --git a/packages/desktop-client/src/components/manager/ManagementApp.jsx b/packages/desktop-client/src/components/manager/ManagementApp.jsx index cc21f172a6a..303a10ed63f 100644 --- a/packages/desktop-client/src/components/manager/ManagementApp.jsx +++ b/packages/desktop-client/src/components/manager/ManagementApp.jsx @@ -22,7 +22,7 @@ import { useServerVersion } from '../ServerContext'; import { BudgetList } from './BudgetList'; import { ConfigExternalSyncServer } from './ConfigExternalSyncServer'; import { ConfigInternalSyncServer } from './ConfigInternalSyncServer'; -import { ConfigServer } from './ConfigServer'; +import { ConfigServer, loader as ConfigServerLoader } from './ConfigServer'; import { ServerURL } from './ServerURL'; import { Bootstrap } from './subscribe/Bootstrap'; import { ChangePassword } from './subscribe/ChangePassword'; diff --git a/packages/loot-core/src/mocks/budget.ts b/packages/loot-core/src/mocks/budget.ts index b7bba7666d3..303a4584346 100644 --- a/packages/loot-core/src/mocks/budget.ts +++ b/packages/loot-core/src/mocks/budget.ts @@ -96,7 +96,7 @@ async function fillPrimaryChecking( billCategories, billPayees, } = extractCommonThings(payees, groups); - const numTransactions = integer(4000, 5000); + const numTransactions = integer(100, 200); const transactions = []; for (let i = 0; i < numTransactions; i++) { @@ -257,7 +257,7 @@ async function fillPrimaryChecking( async function fillChecking(handlers, account, payees, groups) { const { incomePayee, expensePayees, incomeGroup, expenseCategories } = extractCommonThings(payees, groups); - const numTransactions = integer(3000, 6000); + const numTransactions = integer(20, 40); const transactions = []; for (let i = 0; i < numTransactions; i++) { @@ -305,7 +305,7 @@ async function fillChecking(handlers, account, payees, groups) { async function fillInvestment(handlers, account, payees, groups) { const { incomePayee, incomeGroup } = extractCommonThings(payees, groups); - const numTransactions = integer(3000, 4000); + const numTransactions = integer(10, 30); const transactions = []; for (let i = 0; i < numTransactions; i++) { @@ -342,7 +342,7 @@ async function fillSavings(handlers, account, payees, groups) { const { incomePayee, expensePayees, incomeGroup, expenseCategories } = extractCommonThings(payees, groups); - const numTransactions = integer(1500, 4000); + const numTransactions = integer(15, 40); const transactions = []; for (let i = 0; i < numTransactions; i++) { @@ -386,7 +386,7 @@ async function fillSavings(handlers, account, payees, groups) { async function fillMortgage(handlers, account, payees, groups) { const { incomePayee, incomeGroup } = extractCommonThings(payees, groups); - const numTransactions = integer(700, 1000); + const numTransactions = integer(7, 10); const amount = integer(100000, 200000); const category = incomeGroup.categories.find(c => c.name === 'Income'); @@ -423,7 +423,7 @@ async function fillMortgage(handlers, account, payees, groups) { async function fillOther(handlers, account, payees, groups) { const { incomePayee, incomeGroup } = extractCommonThings(payees, groups); - const numTransactions = integer(300, 600); + const numTransactions = integer(3, 6); const category = incomeGroup.categories.find(c => c.name === 'Income'); const transactions: TransactionEntity[] = [ From 3ada82a5d82e8d72247a9f70b19f8577971f9107 Mon Sep 17 00:00:00 2001 From: Mike Clark <mclarkgb@gmail.com> Date: Sat, 23 Nov 2024 12:47:10 +0000 Subject: [PATCH 47/55] update server --- packages/sync-server/Dockerfile | 15 +- .../sync-server/docker/edge-alpine.Dockerfile | 17 +- .../sync-server/docker/edge-ubuntu.Dockerfile | 17 +- .../docker/stable-alpine.Dockerfile | 15 +- .../docker/stable-ubuntu.Dockerfile | 15 +- packages/sync-server/jest.global-setup.js | 92 +++- .../migrations/1718889148000-openid.js | 41 ++ .../migrations/1719409568000-multiuser.js | 104 +++++ packages/sync-server/package.json | 1 + packages/sync-server/src/account-db.js | 186 ++++++-- packages/sync-server/src/accounts/openid.js | 316 ++++++++++++++ packages/sync-server/src/accounts/password.js | 124 ++++++ packages/sync-server/src/app-account.js | 83 +++- packages/sync-server/src/app-admin.js | 409 ++++++++++++++++++ packages/sync-server/src/app-admin.test.js | 380 ++++++++++++++++ .../src/app-gocardless/app-gocardless.js | 4 +- .../src/app-gocardless/bank-factory.js | 2 + .../banks/1822-direkt-heladef1822.js | 16 + .../app-gocardless/banks/ing-pl-ingbplpw.js | 2 +- .../app-gocardless/banks/swedbank-habalv22.js | 51 +++ .../banks/tests/swedbank-habalv22.spec.js | 62 +++ packages/sync-server/src/app-openid.js | 101 +++++ packages/sync-server/src/app-secrets.js | 34 +- .../src/app-simplefin/app-simplefin.js | 13 +- packages/sync-server/src/app-sync.js | 28 +- packages/sync-server/src/app-sync.test.js | 38 +- .../src/app-sync/services/files-service.js | 58 ++- .../tests/services/files-service.test.js | 41 +- packages/sync-server/src/app.js | 6 + packages/sync-server/src/config-types.ts | 17 +- packages/sync-server/src/load-config.js | 60 +++ .../sync-server/src/scripts/reset-password.js | 43 +- .../sync-server/src/services/user-service.js | 261 +++++++++++ packages/sync-server/src/util/middlewares.js | 12 +- .../sync-server/src/util/validate-user.js | 19 +- packages/sync-server/tsconfig.json | 1 + .../sync-server/upcoming-release-notes/479.md | 6 + .../sync-server/upcoming-release-notes/484.md | 6 + .../sync-server/upcoming-release-notes/485.md | 6 + .../sync-server/upcoming-release-notes/490.md | 6 + .../sync-server/upcoming-release-notes/493.md | 6 + .../sync-server/upcoming-release-notes/494.md | 6 + .../sync-server/upcoming-release-notes/497.md | 6 + .../sync-server/upcoming-release-notes/498.md | 6 + .../sync-server/upcoming-release-notes/504.md | 6 + 45 files changed, 2579 insertions(+), 159 deletions(-) create mode 100644 packages/sync-server/migrations/1718889148000-openid.js create mode 100644 packages/sync-server/migrations/1719409568000-multiuser.js create mode 100644 packages/sync-server/src/accounts/openid.js create mode 100644 packages/sync-server/src/accounts/password.js create mode 100644 packages/sync-server/src/app-admin.js create mode 100644 packages/sync-server/src/app-admin.test.js create mode 100644 packages/sync-server/src/app-gocardless/banks/1822-direkt-heladef1822.js create mode 100644 packages/sync-server/src/app-gocardless/banks/swedbank-habalv22.js create mode 100644 packages/sync-server/src/app-gocardless/banks/tests/swedbank-habalv22.spec.js create mode 100644 packages/sync-server/src/app-openid.js create mode 100644 packages/sync-server/src/services/user-service.js create mode 100644 packages/sync-server/upcoming-release-notes/479.md create mode 100644 packages/sync-server/upcoming-release-notes/484.md create mode 100644 packages/sync-server/upcoming-release-notes/485.md create mode 100644 packages/sync-server/upcoming-release-notes/490.md create mode 100644 packages/sync-server/upcoming-release-notes/493.md create mode 100644 packages/sync-server/upcoming-release-notes/494.md create mode 100644 packages/sync-server/upcoming-release-notes/497.md create mode 100644 packages/sync-server/upcoming-release-notes/498.md create mode 100644 packages/sync-server/upcoming-release-notes/504.md diff --git a/packages/sync-server/Dockerfile b/packages/sync-server/Dockerfile index ad5aa098ed9..028f514f9b8 100644 --- a/packages/sync-server/Dockerfile +++ b/packages/sync-server/Dockerfile @@ -1,11 +1,11 @@ -FROM node:18-bullseye as base +FROM node:18-bookworm as base RUN apt-get update && apt-get install -y openssl WORKDIR /app -ADD .yarn ./.yarn -ADD yarn.lock package.json .yarnrc.yml ./ +COPY .yarn ./.yarn +COPY yarn.lock package.json .yarnrc.yml ./ RUN yarn workspaces focus --all --production -FROM node:18-bullseye-slim as prod +FROM node:18-bookworm-slim as prod RUN apt-get update && apt-get install tini && apt-get clean -y && rm -rf /var/lib/apt/lists/* ARG USERNAME=actual @@ -16,10 +16,11 @@ RUN groupadd --gid $USER_GID $USERNAME \ RUN mkdir /data && chown -R ${USERNAME}:${USERNAME} /data WORKDIR /app +ENV NODE_ENV production COPY --from=base /app/node_modules /app/node_modules -ADD package.json app.js ./ -ADD src ./src -ADD migrations ./migrations +COPY package.json app.js ./ +COPY src ./src +COPY migrations ./migrations ENTRYPOINT ["/usr/bin/tini","-g", "--"] EXPOSE 5006 CMD ["node", "app.js"] diff --git a/packages/sync-server/docker/edge-alpine.Dockerfile b/packages/sync-server/docker/edge-alpine.Dockerfile index 7c3f57ec030..eece22ebcbe 100644 --- a/packages/sync-server/docker/edge-alpine.Dockerfile +++ b/packages/sync-server/docker/edge-alpine.Dockerfile @@ -1,21 +1,21 @@ -FROM alpine:3.17 as base +FROM alpine:3.18 as base RUN apk add --no-cache nodejs yarn npm python3 openssl build-base jq curl WORKDIR /app -ADD .yarn ./.yarn -ADD yarn.lock package.json .yarnrc.yml ./ +COPY .yarn ./.yarn +COPY yarn.lock package.json .yarnrc.yml ./ RUN if [ "$(uname -m)" = "armv7l" ]; then yarn config set taskPoolConcurrency 2; yarn config set networkConcurrency 5; fi RUN yarn workspaces focus --all --production RUN if [ "$(uname -m)" = "armv7l" ]; then npm install bcrypt better-sqlite3 --build-from-source; fi RUN mkdir /public -ADD artifacts.json /tmp/artifacts.json +COPY artifacts.json /tmp/artifacts.json RUN jq -r '[.artifacts[] | select(.workflow_run.head_branch == "master" and .workflow_run.head_repository_id == .workflow_run.repository_id)][0]' /tmp/artifacts.json > /tmp/latest-build.json ARG GITHUB_TOKEN RUN curl -L -o /tmp/desktop-client.zip --header "Authorization: Bearer ${GITHUB_TOKEN}" $(jq -r '.archive_download_url' /tmp/latest-build.json) RUN unzip /tmp/desktop-client.zip -d /public -FROM alpine:3.17 as prod +FROM alpine:3.18 as prod RUN apk add --no-cache nodejs tini ARG USERNAME=actual @@ -25,11 +25,12 @@ RUN addgroup -S ${USERNAME} -g ${USER_GID} && adduser -S ${USERNAME} -G ${USERNA RUN mkdir /data && chown -R ${USERNAME}:${USERNAME} /data WORKDIR /app +ENV NODE_ENV production COPY --from=base /app/node_modules /app/node_modules COPY --from=base /public /public -ADD package.json app.js ./ -ADD src ./src -ADD migrations ./migrations +COPY package.json app.js ./ +COPY src ./src +COPY migrations ./migrations ENTRYPOINT ["/sbin/tini","-g", "--"] ENV ACTUAL_WEB_ROOT=/public EXPOSE 5006 diff --git a/packages/sync-server/docker/edge-ubuntu.Dockerfile b/packages/sync-server/docker/edge-ubuntu.Dockerfile index 99e4e757ef3..500b2265412 100644 --- a/packages/sync-server/docker/edge-ubuntu.Dockerfile +++ b/packages/sync-server/docker/edge-ubuntu.Dockerfile @@ -1,20 +1,20 @@ -FROM node:18-bullseye as base +FROM node:18-bookworm as base RUN apt-get update && apt-get install -y openssl jq WORKDIR /app -ADD .yarn ./.yarn -ADD yarn.lock package.json .yarnrc.yml ./ +COPY .yarn ./.yarn +COPY yarn.lock package.json .yarnrc.yml ./ RUN if [ "$(uname -m)" = "armv7l" ]; then yarn config set taskPoolConcurrency 2; yarn config set networkConcurrency 5; fi RUN yarn workspaces focus --all --production RUN mkdir /public -ADD artifacts.json /tmp/artifacts.json +COPY artifacts.json /tmp/artifacts.json RUN jq -r '[.artifacts[] | select(.workflow_run.head_branch == "master" and .workflow_run.head_repository_id == .workflow_run.repository_id)][0]' /tmp/artifacts.json > /tmp/latest-build.json ARG GITHUB_TOKEN RUN curl -L -o /tmp/desktop-client.zip --header "Authorization: Bearer ${GITHUB_TOKEN}" $(jq -r '.archive_download_url' /tmp/latest-build.json) RUN unzip /tmp/desktop-client.zip -d /public -FROM node:18-bullseye-slim as prod +FROM node:18-bookworm-slim as prod RUN apt-get update && apt-get install tini && apt-get clean -y && rm -rf /var/lib/apt/lists/* ARG USERNAME=actual @@ -25,11 +25,12 @@ RUN groupadd --gid $USER_GID $USERNAME \ RUN mkdir /data && chown -R ${USERNAME}:${USERNAME} /data WORKDIR /app +ENV NODE_ENV production COPY --from=base /app/node_modules /app/node_modules COPY --from=base /public /public -ADD package.json app.js ./ -ADD src ./src -ADD migrations ./migrations +COPY package.json app.js ./ +COPY src ./src +COPY migrations ./migrations ENTRYPOINT ["/usr/bin/tini","-g", "--"] ENV ACTUAL_WEB_ROOT=/public EXPOSE 5006 diff --git a/packages/sync-server/docker/stable-alpine.Dockerfile b/packages/sync-server/docker/stable-alpine.Dockerfile index e6b21652046..cc00678561f 100644 --- a/packages/sync-server/docker/stable-alpine.Dockerfile +++ b/packages/sync-server/docker/stable-alpine.Dockerfile @@ -1,13 +1,13 @@ -FROM alpine:3.17 as base +FROM alpine:3.18 as base RUN apk add --no-cache nodejs yarn npm python3 openssl build-base WORKDIR /app -ADD .yarn ./.yarn -ADD yarn.lock package.json .yarnrc.yml ./ +COPY .yarn ./.yarn +COPY yarn.lock package.json .yarnrc.yml ./ RUN if [ "$(uname -m)" = "armv7l" ]; then yarn config set taskPoolConcurrency 2; yarn config set networkConcurrency 5; fi RUN yarn workspaces focus --all --production RUN if [ "$(uname -m)" = "armv7l" ]; then npm install bcrypt better-sqlite3 --build-from-source; fi -FROM alpine:3.17 as prod +FROM alpine:3.18 as prod RUN apk add --no-cache nodejs tini ARG USERNAME=actual @@ -17,10 +17,11 @@ RUN addgroup -S ${USERNAME} -g ${USER_GID} && adduser -S ${USERNAME} -G ${USERNA RUN mkdir /data && chown -R ${USERNAME}:${USERNAME} /data WORKDIR /app +ENV NODE_ENV production COPY --from=base /app/node_modules /app/node_modules -ADD package.json app.js ./ -ADD src ./src -ADD migrations ./migrations +COPY package.json app.js ./ +COPY src ./src +COPY migrations ./migrations ENTRYPOINT ["/sbin/tini","-g", "--"] EXPOSE 5006 CMD ["node", "app.js"] diff --git a/packages/sync-server/docker/stable-ubuntu.Dockerfile b/packages/sync-server/docker/stable-ubuntu.Dockerfile index eb02aad66f5..0f20d19258f 100644 --- a/packages/sync-server/docker/stable-ubuntu.Dockerfile +++ b/packages/sync-server/docker/stable-ubuntu.Dockerfile @@ -1,12 +1,12 @@ -FROM node:18-bullseye as base +FROM node:18-bookworm as base RUN apt-get update && apt-get install -y openssl WORKDIR /app -ADD .yarn ./.yarn -ADD yarn.lock package.json .yarnrc.yml ./ +COPY .yarn ./.yarn +COPY yarn.lock package.json .yarnrc.yml ./ RUN if [ "$(uname -m)" = "armv7l" ]; then yarn config set taskPoolConcurrency 2; yarn config set networkConcurrency 5; fi RUN yarn workspaces focus --all --production -FROM node:18-bullseye-slim as prod +FROM node:18-bookworm-slim as prod RUN apt-get update && apt-get install tini && apt-get clean -y && rm -rf /var/lib/apt/lists/* ARG USERNAME=actual @@ -17,10 +17,11 @@ RUN groupadd --gid $USER_GID $USERNAME \ RUN mkdir /data && chown -R ${USERNAME}:${USERNAME} /data WORKDIR /app +ENV NODE_ENV production COPY --from=base /app/node_modules /app/node_modules -ADD package.json app.js ./ -ADD src ./src -ADD migrations ./migrations +COPY package.json app.js ./ +COPY src ./src +COPY migrations ./migrations ENTRYPOINT ["/usr/bin/tini","-g", "--"] EXPOSE 5006 CMD ["node", "app.js"] diff --git a/packages/sync-server/jest.global-setup.js b/packages/sync-server/jest.global-setup.js index 36f53cf1cc5..524054dd5dd 100644 --- a/packages/sync-server/jest.global-setup.js +++ b/packages/sync-server/jest.global-setup.js @@ -1,10 +1,100 @@ import getAccountDb from './src/account-db.js'; import runMigrations from './src/migrations.js'; +const GENERIC_ADMIN_ID = 'genericAdmin'; +const GENERIC_USER_ID = 'genericUser'; +const ADMIN_ROLE_ID = 'ADMIN'; +const BASIC_ROLE_ID = 'BASIC'; + +const createUser = (userId, userName, role, owner = 0, enabled = 1) => { + const missingParams = []; + if (!userId) missingParams.push('userId'); + if (!userName) missingParams.push('userName'); + if (!role) missingParams.push('role'); + if (missingParams.length > 0) { + throw new Error(`Missing required parameters: ${missingParams.join(', ')}`); + } + + if ( + typeof userId !== 'string' || + typeof userName !== 'string' || + typeof role !== 'string' + ) { + throw new Error( + 'Invalid parameter types. userId, userName, and role must be strings', + ); + } + + try { + getAccountDb().mutate( + 'INSERT INTO users (id, user_name, display_name, enabled, owner, role) VALUES (?, ?, ?, ?, ?, ?)', + [userId, userName, userName, enabled, owner, role], + ); + } catch (error) { + console.error(`Error creating user ${userName}:`, error); + throw error; + } +}; + +const setSessionUser = (userId, token = 'valid-token') => { + if (!userId) { + throw new Error('userId is required'); + } + + try { + const db = getAccountDb(); + const session = db.first('SELECT token FROM sessions WHERE token = ?', [ + token, + ]); + if (!session) { + throw new Error(`Session not found for token: ${token}`); + } + + db.mutate('UPDATE sessions SET user_id = ? WHERE token = ?', [ + userId, + token, + ]); + } catch (error) { + console.error(`Error updating session for user ${userId}:`, error); + throw error; + } +}; + export default async function setup() { + const NEVER_EXPIRES = -1; // or consider using a far future timestamp + await runMigrations(); + createUser(GENERIC_ADMIN_ID, 'admin', ADMIN_ROLE_ID, 1); + // Insert a fake "valid-token" fixture that can be reused const db = getAccountDb(); - await db.mutate('INSERT INTO sessions (token) VALUES (?)', ['valid-token']); + try { + await db.mutate('BEGIN TRANSACTION'); + + await db.mutate('DELETE FROM sessions'); + await db.mutate( + 'INSERT INTO sessions (token, expires_at, user_id) VALUES (?, ?, ?)', + ['valid-token', NEVER_EXPIRES, 'genericAdmin'], + ); + await db.mutate( + 'INSERT INTO sessions (token, expires_at, user_id) VALUES (?, ?, ?)', + ['valid-token-admin', NEVER_EXPIRES, 'genericAdmin'], + ); + + await db.mutate( + 'INSERT INTO sessions (token, expires_at, user_id) VALUES (?, ?, ?)', + ['valid-token-user', NEVER_EXPIRES, 'genericUser'], + ); + + await db.mutate('COMMIT'); + } catch (error) { + await db.mutate('ROLLBACK'); + throw new Error(`Failed to setup test sessions: ${error.message}`); + } + + setSessionUser('genericAdmin'); + setSessionUser('genericAdmin', 'valid-token-admin'); + + createUser(GENERIC_USER_ID, 'user', BASIC_ROLE_ID, 1); } diff --git a/packages/sync-server/migrations/1718889148000-openid.js b/packages/sync-server/migrations/1718889148000-openid.js new file mode 100644 index 00000000000..b170aea05dd --- /dev/null +++ b/packages/sync-server/migrations/1718889148000-openid.js @@ -0,0 +1,41 @@ +import getAccountDb from '../src/account-db.js'; + +export const up = async function () { + await getAccountDb().exec( + ` + BEGIN TRANSACTION; + CREATE TABLE auth_new + (method TEXT PRIMARY KEY, + display_name TEXT, + extra_data TEXT, active INTEGER); + + INSERT INTO auth_new (method, display_name, extra_data, active) + SELECT 'password', 'Password', password, 1 FROM auth; + DROP TABLE auth; + ALTER TABLE auth_new RENAME TO auth; + + CREATE TABLE pending_openid_requests + (state TEXT PRIMARY KEY, + code_verifier TEXT, + return_url TEXT, + expiry_time INTEGER); + COMMIT;`, + ); +}; + +export const down = async function () { + await getAccountDb().exec( + ` + BEGIN TRANSACTION; + ALTER TABLE auth RENAME TO auth_temp; + CREATE TABLE auth + (password TEXT); + INSERT INTO auth (password) + SELECT extra_data FROM auth_temp WHERE method = 'password'; + DROP TABLE auth_temp; + + DROP TABLE pending_openid_requests; + COMMIT; + `, + ); +}; diff --git a/packages/sync-server/migrations/1719409568000-multiuser.js b/packages/sync-server/migrations/1719409568000-multiuser.js new file mode 100644 index 00000000000..345da8ebd9a --- /dev/null +++ b/packages/sync-server/migrations/1719409568000-multiuser.js @@ -0,0 +1,104 @@ +import getAccountDb from '../src/account-db.js'; + +export const up = async function () { + await getAccountDb().exec( + ` + BEGIN TRANSACTION; + + CREATE TABLE users + (id TEXT PRIMARY KEY, + user_name TEXT, + display_name TEXT, + role TEXT, + enabled INTEGER NOT NULL DEFAULT 1, + owner INTEGER NOT NULL DEFAULT 0); + + CREATE TABLE user_access + (user_id TEXT, + file_id TEXT, + PRIMARY KEY (user_id, file_id), + FOREIGN KEY (user_id) REFERENCES users(id), + FOREIGN KEY (file_id) REFERENCES files(id) + ); + + ALTER TABLE files + ADD COLUMN owner TEXT; + + DELETE FROM sessions; + + ALTER TABLE sessions + ADD COLUMN expires_at INTEGER; + + ALTER TABLE sessions + ADD COLUMN user_id TEXT; + + ALTER TABLE sessions + ADD COLUMN auth_method TEXT; + COMMIT; + `, + ); +}; + +export const down = async function () { + await getAccountDb().exec( + ` + BEGIN TRANSACTION; + + DROP TABLE IF EXISTS user_access; + + CREATE TABLE sessions_backup ( + token TEXT PRIMARY KEY + ); + + INSERT INTO sessions_backup (token) + SELECT token FROM sessions; + + DROP TABLE sessions; + + ALTER TABLE sessions_backup RENAME TO sessions; + + CREATE TABLE files_backup ( + id TEXT PRIMARY KEY, + group_id TEXT, + sync_version SMALLINT, + encrypt_meta TEXT, + encrypt_keyid TEXT, + encrypt_salt TEXT, + encrypt_test TEXT, + deleted BOOLEAN DEFAULT FALSE, + name TEXT + ); + + INSERT INTO files_backup ( + id, + group_id, + sync_version, + encrypt_meta, + encrypt_keyid, + encrypt_salt, + encrypt_test, + deleted, + name + ) + SELECT + id, + group_id, + sync_version, + encrypt_meta, + encrypt_keyid, + encrypt_salt, + encrypt_test, + deleted, + name + FROM files; + + DROP TABLE files; + + ALTER TABLE files_backup RENAME TO files; + + DROP TABLE IF EXISTS users; + + COMMIT; + `, + ); +}; diff --git a/packages/sync-server/package.json b/packages/sync-server/package.json index c402dfe4c3a..46e1018c6c4 100644 --- a/packages/sync-server/package.json +++ b/packages/sync-server/package.json @@ -36,6 +36,7 @@ "jws": "^4.0.0", "migrate": "^2.0.1", "nordigen-node": "^1.4.0", + "openid-client": "^5.4.2", "uuid": "^9.0.0", "winston": "^3.14.2" }, diff --git a/packages/sync-server/src/account-db.js b/packages/sync-server/src/account-db.js index cc2fe56746c..aa6678fd230 100644 --- a/packages/sync-server/src/account-db.js +++ b/packages/sync-server/src/account-db.js @@ -1,8 +1,9 @@ import { join } from 'node:path'; import openDatabase from './db.js'; import config from './load-config.js'; -import * as uuid from 'uuid'; import * as bcrypt from 'bcrypt'; +import { bootstrapPassword, loginWithPassword } from './accounts/password.js'; +import { bootstrapOpenId } from './accounts/openid.js'; let _accountDb; @@ -15,16 +16,29 @@ export default function getAccountDb() { return _accountDb; } -function hashPassword(password) { - return bcrypt.hashSync(password, 12); -} - export function needsBootstrap() { let accountDb = getAccountDb(); let rows = accountDb.all('SELECT * FROM auth'); return rows.length === 0; } +export function listLoginMethods() { + let accountDb = getAccountDb(); + let rows = accountDb.all('SELECT method, display_name, active FROM auth'); + return rows.map((r) => ({ + method: r.method, + active: r.active, + displayName: r.display_name, + })); +} + +export function getActiveLoginMethod() { + let accountDb = getAccountDb(); + let { method } = + accountDb.first('SELECT method FROM auth WHERE active = 1') || {}; + return method; +} + /* * Get the Login Method in the following order * req (the frontend can say which method in the case it wants to resort to forcing password auth) @@ -38,74 +52,154 @@ export function getLoginMethod(req) { ) { return req.body.loginMethod; } + return config.loginMethod || 'password'; } -export function bootstrap(password) { - if (password === undefined || password === '') { - return { error: 'invalid-password' }; +export async function bootstrap(loginSettings) { + if (!loginSettings) { + return { error: 'invalid-login-settings' }; } + const passEnabled = 'password' in loginSettings; + const openIdEnabled = 'openId' in loginSettings; + + const accountDb = getAccountDb(); + accountDb.mutate('BEGIN TRANSACTION'); + try { + const { countOfOwner } = + accountDb.first( + `SELECT count(*) as countOfOwner + FROM users + WHERE users.user_name <> '' and users.owner = 1`, + ) || {}; + + if (!openIdEnabled || countOfOwner > 0) { + if (!needsBootstrap()) { + accountDb.mutate('ROLLBACK'); + return { error: 'already-bootstrapped' }; + } + } + + if (!passEnabled && !openIdEnabled) { + accountDb.mutate('ROLLBACK'); + return { error: 'no-auth-method-selected' }; + } + + if (passEnabled && openIdEnabled) { + accountDb.mutate('ROLLBACK'); + return { error: 'max-one-method-allowed' }; + } + + if (passEnabled) { + let { error } = bootstrapPassword(loginSettings.password); + if (error) { + accountDb.mutate('ROLLBACK'); + return { error }; + } + } + + if (openIdEnabled) { + let { error } = await bootstrapOpenId(loginSettings.openId); + if (error) { + accountDb.mutate('ROLLBACK'); + return { error }; + } + } + + accountDb.mutate('COMMIT'); + return passEnabled ? loginWithPassword(loginSettings.password) : {}; + } catch (error) { + accountDb.mutate('ROLLBACK'); + throw error; + } +} - let accountDb = getAccountDb(); - let rows = accountDb.all('SELECT * FROM auth'); +export function isAdmin(userId) { + return hasPermission(userId, 'ADMIN'); +} - if (rows.length !== 0) { - return { error: 'already-bootstrapped' }; - } +export function hasPermission(userId, permission) { + return getUserPermission(userId) === permission; +} - // Hash the password. There's really not a strong need for this - // since this is a self-hosted instance owned by the user. - // However, just in case we do it. - let hashed = hashPassword(password); - accountDb.mutate('INSERT INTO auth (password) VALUES (?)', [hashed]); +export async function enableOpenID(loginSettings) { + if (!loginSettings || !loginSettings.openId) { + return { error: 'invalid-login-settings' }; + } - let token = uuid.v4(); - accountDb.mutate('INSERT INTO sessions (token) VALUES (?)', [token]); + let { error } = (await bootstrapOpenId(loginSettings.openId)) || {}; + if (error) { + return { error }; + } - return { token }; + getAccountDb().mutate('DELETE FROM sessions'); } -export function login(password) { - if (password === undefined || password === '') { - return { error: 'invalid-password' }; +export async function disableOpenID(loginSettings) { + if (!loginSettings || !loginSettings.password) { + return { error: 'invalid-login-settings' }; } let accountDb = getAccountDb(); - let row = accountDb.first('SELECT * FROM auth'); - - let confirmed = row && bcrypt.compareSync(password, row.password); + const { extra_data: passwordHash } = + accountDb.first('SELECT extra_data FROM auth WHERE method = ?', [ + 'password', + ]) || {}; - if (!confirmed) { + if (!passwordHash) { return { error: 'invalid-password' }; } - // Right now, tokens are permanent and there's just one in the - // system. In the future this should probably evolve to be a - // "session" that times out after a long time or something, and - // maybe each device has a different token - let sessionRow = accountDb.first('SELECT * FROM sessions'); - return { token: sessionRow.token }; -} - -export function changePassword(newPassword) { - if (newPassword === undefined || newPassword === '') { + if (!loginSettings?.password) { return { error: 'invalid-password' }; } - let accountDb = getAccountDb(); + if (passwordHash) { + let confirmed = bcrypt.compareSync(loginSettings.password, passwordHash); - let hashed = hashPassword(newPassword); - let token = uuid.v4(); + if (!confirmed) { + return { error: 'invalid-password' }; + } + } - // Note that this doesn't have a WHERE. This table only ever has 1 - // row (maybe that will change in the future? if so this will not work) - accountDb.mutate('UPDATE auth SET password = ?', [hashed]); - accountDb.mutate('UPDATE sessions SET token = ?', [token]); + let { error } = (await bootstrapPassword(loginSettings.password)) || {}; + if (error) { + return { error }; + } - return {}; + getAccountDb().mutate('DELETE FROM sessions'); + getAccountDb().mutate('DELETE FROM users WHERE user_name <> ?', ['']); + getAccountDb().mutate('DELETE FROM auth WHERE method = ?', ['openid']); } export function getSession(token) { let accountDb = getAccountDb(); return accountDb.first('SELECT * FROM sessions WHERE token = ?', [token]); } + +export function getUserInfo(userId) { + let accountDb = getAccountDb(); + return accountDb.first('SELECT * FROM users WHERE id = ?', [userId]); +} + +export function getUserPermission(userId) { + let accountDb = getAccountDb(); + const { role } = accountDb.first( + `SELECT role FROM users + WHERE users.id = ?`, + [userId], + ) || { role: '' }; + + return role; +} + +export function clearExpiredSessions() { + const clearThreshold = Math.floor(Date.now() / 1000) - 3600; + + const deletedSessions = getAccountDb().mutate( + 'DELETE FROM sessions WHERE expires_at <> -1 and expires_at < ?', + [clearThreshold], + ).changes; + + console.log(`Deleted ${deletedSessions} old sessions`); +} diff --git a/packages/sync-server/src/accounts/openid.js b/packages/sync-server/src/accounts/openid.js new file mode 100644 index 00000000000..784a6e5b38c --- /dev/null +++ b/packages/sync-server/src/accounts/openid.js @@ -0,0 +1,316 @@ +import getAccountDb, { clearExpiredSessions } from '../account-db.js'; +import * as uuid from 'uuid'; +import { generators, Issuer } from 'openid-client'; +import finalConfig from '../load-config.js'; +import { TOKEN_EXPIRATION_NEVER } from '../util/validate-user.js'; +import { + getUserByUsername, + transferAllFilesFromUser, +} from '../services/user-service.js'; + +export async function bootstrapOpenId(config) { + if (!('issuer' in config)) { + return { error: 'missing-issuer' }; + } + if (!('client_id' in config)) { + return { error: 'missing-client-id' }; + } + if (!('client_secret' in config)) { + return { error: 'missing-client-secret' }; + } + if (!('server_hostname' in config)) { + return { error: 'missing-server-hostname' }; + } + + try { + await setupOpenIdClient(config); + } catch (err) { + console.error('Error setting up OpenID client:', err); + return { error: 'configuration-error' }; + } + + let accountDb = getAccountDb(); + try { + accountDb.transaction(() => { + accountDb.mutate('DELETE FROM auth WHERE method = ?', ['openid']); + accountDb.mutate('UPDATE auth SET active = 0'); + accountDb.mutate( + "INSERT INTO auth (method, display_name, extra_data, active) VALUES ('openid', 'OpenID', ?, 1)", + [JSON.stringify(config)], + ); + }); + } catch (err) { + console.error('Error updating auth table:', err); + return { error: 'database-error' }; + } + + return {}; +} + +async function setupOpenIdClient(config) { + let issuer = + typeof config.issuer === 'string' + ? await Issuer.discover(config.issuer) + : new Issuer({ + issuer: config.issuer.name, + authorization_endpoint: config.issuer.authorization_endpoint, + token_endpoint: config.issuer.token_endpoint, + userinfo_endpoint: config.issuer.userinfo_endpoint, + }); + + const client = new issuer.Client({ + client_id: config.client_id, + client_secret: config.client_secret, + redirect_uri: new URL( + '/openid/callback', + config.server_hostname, + ).toString(), + validate_id_token: true, + }); + + return client; +} + +export async function loginWithOpenIdSetup(returnUrl) { + if (!returnUrl) { + return { error: 'return-url-missing' }; + } + if (!isValidRedirectUrl(returnUrl)) { + return { error: 'invalid-return-url' }; + } + + let accountDb = getAccountDb(); + let config = accountDb.first('SELECT extra_data FROM auth WHERE method = ?', [ + 'openid', + ]); + if (!config) { + return { error: 'openid-not-configured' }; + } + + try { + config = JSON.parse(config['extra_data']); + } catch (err) { + console.error('Error parsing OpenID configuration:', err); + return { error: 'openid-setup-failed' }; + } + + let client; + try { + client = await setupOpenIdClient(config); + } catch (err) { + console.error('Error setting up OpenID client:', err); + return { error: 'openid-setup-failed' }; + } + + const state = generators.state(); + const code_verifier = generators.codeVerifier(); + const code_challenge = generators.codeChallenge(code_verifier); + + const now_time = Date.now(); + const expiry_time = now_time + 300 * 1000; + + accountDb.mutate( + 'DELETE FROM pending_openid_requests WHERE expiry_time < ?', + [now_time], + ); + accountDb.mutate( + 'INSERT INTO pending_openid_requests (state, code_verifier, return_url, expiry_time) VALUES (?, ?, ?, ?)', + [state, code_verifier, returnUrl, expiry_time], + ); + + const url = client.authorizationUrl({ + response_type: 'code', + scope: 'openid email profile', + state, + code_challenge, + code_challenge_method: 'S256', + }); + + return { url }; +} + +export async function loginWithOpenIdFinalize(body) { + if (!body.code) { + return { error: 'missing-authorization-code' }; + } + if (!body.state) { + return { error: 'missing-state' }; + } + + let accountDb = getAccountDb(); + let config = accountDb.first( + "SELECT extra_data FROM auth WHERE method = 'openid' AND active = 1", + ); + if (!config) { + return { error: 'openid-not-configured' }; + } + try { + config = JSON.parse(config['extra_data']); + } catch (err) { + console.error('Error parsing OpenID configuration:', err); + return { error: 'openid-setup-failed' }; + } + let client; + try { + client = await setupOpenIdClient(config); + } catch (err) { + console.error('Error setting up OpenID client:', err); + return { error: 'openid-setup-failed' }; + } + + let pendingRequest = accountDb.first( + 'SELECT code_verifier, return_url FROM pending_openid_requests WHERE state = ? AND expiry_time > ?', + [body.state, Date.now()], + ); + + if (!pendingRequest) { + return { error: 'invalid-or-expired-state' }; + } + + let { code_verifier, return_url } = pendingRequest; + + try { + const params = { code: body.code, state: body.state }; + let tokenSet = await client.callback(client.redirect_uris[0], params, { + code_verifier, + state: body.state, + }); + const userInfo = await client.userinfo(tokenSet.access_token); + const identity = + userInfo.preferred_username ?? + userInfo.login ?? + userInfo.email ?? + userInfo.id ?? + userInfo.name ?? + 'default-username'; + if (identity == null) { + return { error: 'openid-grant-failed: no identification was found' }; + } + + let userId = null; + try { + accountDb.transaction(() => { + let { countUsersWithUserName } = accountDb.first( + 'SELECT count(*) as countUsersWithUserName FROM users WHERE user_name <> ?', + [''], + ); + if (countUsersWithUserName === 0) { + userId = uuid.v4(); + // Check if user was created by another transaction + const existingUser = accountDb.first( + 'SELECT id FROM users WHERE user_name = ?', + [identity], + ); + if (existingUser) { + throw new Error('user-already-exists'); + } + accountDb.mutate( + 'INSERT INTO users (id, user_name, display_name, enabled, owner, role) VALUES (?, ?, ?, 1, 1, ?)', + [ + userId, + identity, + userInfo.name ?? userInfo.email ?? identity, + 'ADMIN', + ], + ); + + const userFromPasswordMethod = getUserByUsername(''); + if (userFromPasswordMethod) { + transferAllFilesFromUser(userId, userFromPasswordMethod.user_id); + } + } else { + let { id: userIdFromDb, display_name: displayName } = + accountDb.first( + 'SELECT id, display_name FROM users WHERE user_name = ? and enabled = 1', + [identity], + ) || {}; + + if (userIdFromDb == null) { + throw new Error('openid-grant-failed'); + } + + if (!displayName && userInfo.name) { + accountDb.mutate('UPDATE users set display_name = ? WHERE id = ?', [ + userInfo.name, + userIdFromDb, + ]); + } + + userId = userIdFromDb; + } + }); + } catch (error) { + if (error.message === 'user-already-exists') { + return { error: 'user-already-exists' }; + } else if (error.message === 'openid-grant-failed') { + return { error: 'openid-grant-failed' }; + } else { + throw error; // Re-throw other unexpected errors + } + } + + const token = uuid.v4(); + + let expiration; + if (finalConfig.token_expiration === 'openid-provider') { + expiration = tokenSet.expires_at ?? TOKEN_EXPIRATION_NEVER; + } else if (finalConfig.token_expiration === 'never') { + expiration = TOKEN_EXPIRATION_NEVER; + } else if (typeof finalConfig.token_expiration === 'number') { + expiration = + Math.floor(Date.now() / 1000) + finalConfig.token_expiration * 60; + } else { + expiration = Math.floor(Date.now() / 1000) + 10 * 60; + } + + accountDb.mutate( + 'INSERT INTO sessions (token, expires_at, user_id, auth_method) VALUES (?, ?, ?, ?)', + [token, expiration, userId, 'openid'], + ); + + clearExpiredSessions(); + + return { url: `${return_url}/openid-cb?token=${token}` }; + } catch (err) { + console.error('OpenID grant failed:', err); + return { error: 'openid-grant-failed' }; + } +} + +export function getServerHostname() { + const auth = getAccountDb().first( + 'select * from auth WHERE method = ? and active = 1', + ['openid'], + ); + if (auth && auth.extra_data) { + try { + const openIdConfig = JSON.parse(auth.extra_data); + return openIdConfig.server_hostname; + } catch (error) { + console.error('Error parsing OpenID configuration:', error); + } + } + return null; +} + +export function isValidRedirectUrl(url) { + const serverHostname = getServerHostname(); + + if (!serverHostname) { + return false; + } + + try { + const redirectUrl = new URL(url); + const serverUrl = new URL(serverHostname); + + // Compare origin (protocol + hostname + port) + if (redirectUrl.origin === serverUrl.origin) { + return true; + } else { + return false; + } + } catch (err) { + return false; + } +} diff --git a/packages/sync-server/src/accounts/password.js b/packages/sync-server/src/accounts/password.js new file mode 100644 index 00000000000..e841697a7f7 --- /dev/null +++ b/packages/sync-server/src/accounts/password.js @@ -0,0 +1,124 @@ +import * as bcrypt from 'bcrypt'; +import getAccountDb, { clearExpiredSessions } from '../account-db.js'; +import * as uuid from 'uuid'; +import finalConfig from '../load-config.js'; +import { TOKEN_EXPIRATION_NEVER } from '../util/validate-user.js'; + +function isValidPassword(password) { + return password != null && password !== ''; +} + +function hashPassword(password) { + return bcrypt.hashSync(password, 12); +} + +export function bootstrapPassword(password) { + if (!isValidPassword(password)) { + return { error: 'invalid-password' }; + } + + let hashed = hashPassword(password); + let accountDb = getAccountDb(); + accountDb.transaction(() => { + accountDb.mutate('DELETE FROM auth WHERE method = ?', ['password']); + accountDb.mutate('UPDATE auth SET active = 0'); + accountDb.mutate( + "INSERT INTO auth (method, display_name, extra_data, active) VALUES ('password', 'Password', ?, 1)", + [hashed], + ); + }); + + return {}; +} + +export function loginWithPassword(password) { + if (!isValidPassword(password)) { + return { error: 'invalid-password' }; + } + + let accountDb = getAccountDb(); + const { extra_data: passwordHash } = + accountDb.first('SELECT extra_data FROM auth WHERE method = ?', [ + 'password', + ]) || {}; + + if (!passwordHash) { + return { error: 'invalid-password' }; + } + + let confirmed = bcrypt.compareSync(password, passwordHash); + + if (!confirmed) { + return { error: 'invalid-password' }; + } + + let sessionRow = accountDb.first( + 'SELECT * FROM sessions WHERE auth_method = ?', + ['password'], + ); + + let token = sessionRow ? sessionRow.token : uuid.v4(); + + let { totalOfUsers } = accountDb.first( + 'SELECT count(*) as totalOfUsers FROM users', + ); + let userId = null; + if (totalOfUsers === 0) { + userId = uuid.v4(); + accountDb.mutate( + 'INSERT INTO users (id, user_name, display_name, enabled, owner, role) VALUES (?, ?, ?, 1, 1, ?)', + [userId, '', '', 'ADMIN'], + ); + } else { + let { id: userIdFromDb } = accountDb.first( + 'SELECT id FROM users WHERE user_name = ?', + [''], + ); + + userId = userIdFromDb; + + if (!userId) { + return { error: 'user-not-found' }; + } + } + + let expiration = TOKEN_EXPIRATION_NEVER; + if ( + finalConfig.token_expiration != 'never' && + finalConfig.token_expiration != 'openid-provider' && + typeof finalConfig.token_expiration === 'number' + ) { + expiration = + Math.floor(Date.now() / 1000) + finalConfig.token_expiration * 60; + } + + if (!sessionRow) { + accountDb.mutate( + 'INSERT INTO sessions (token, expires_at, user_id, auth_method) VALUES (?, ?, ?, ?)', + [token, expiration, userId, 'password'], + ); + } else { + accountDb.mutate( + 'UPDATE sessions SET user_id = ?, expires_at = ? WHERE token = ?', + [userId, expiration, token], + ); + } + + clearExpiredSessions(); + + return { token }; +} + +export function changePassword(newPassword) { + let accountDb = getAccountDb(); + + if (!isValidPassword(newPassword)) { + return { error: 'invalid-password' }; + } + + let hashed = hashPassword(newPassword); + accountDb.mutate("UPDATE auth SET extra_data = ? WHERE method = 'password'", [ + hashed, + ]); + return {}; +} diff --git a/packages/sync-server/src/app-account.js b/packages/sync-server/src/app-account.js index cc18ea32ecb..057c97d060a 100644 --- a/packages/sync-server/src/app-account.js +++ b/packages/sync-server/src/app-account.js @@ -3,16 +3,21 @@ import { errorMiddleware, requestLoggerMiddleware, } from './util/middlewares.js'; -import validateUser, { validateAuthHeader } from './util/validate-user.js'; +import validateSession, { validateAuthHeader } from './util/validate-user.js'; import { bootstrap, - login, - changePassword, needsBootstrap, getLoginMethod, + listLoginMethods, + getUserInfo, + getActiveLoginMethod, } from './account-db.js'; +import { changePassword, loginWithPassword } from './accounts/password.js'; +import { isValidRedirectUrl, loginWithOpenIdSetup } from './accounts/openid.js'; let app = express(); +app.use(express.json()); +app.use(express.urlencoded({ extended: true })); app.use(errorMiddleware); app.use(requestLoggerMiddleware); export { app as handlers }; @@ -26,22 +31,30 @@ export { app as handlers }; app.get('/needs-bootstrap', (req, res) => { res.send({ status: 'ok', - data: { bootstrapped: !needsBootstrap(), loginMethod: getLoginMethod() }, + data: { + bootstrapped: !needsBootstrap(), + loginMethods: listLoginMethods(), + multiuser: getActiveLoginMethod() === 'openid', + }, }); }); -app.post('/bootstrap', (req, res) => { - let { error, token } = bootstrap(req.body.password); +app.post('/bootstrap', async (req, res) => { + let boot = await bootstrap(req.body); - if (error) { - res.status(400).send({ status: 'error', reason: error }); + if (boot?.error) { + res.status(400).send({ status: 'error', reason: boot?.error }); return; } + res.send({ status: 'ok', data: boot }); +}); - res.send({ status: 'ok', data: { token } }); +app.get('/login-methods', (req, res) => { + let methods = listLoginMethods(); + res.send({ status: 'ok', methods }); }); -app.post('/login', (req, res) => { +app.post('/login', async (req, res) => { let loginMethod = getLoginMethod(req); console.log('Logging in via ' + loginMethod); let tokenRes = null; @@ -56,7 +69,7 @@ app.post('/login', (req, res) => { return; } else { if (validateAuthHeader(req)) { - tokenRes = login(headerVal); + tokenRes = loginWithPassword(headerVal); } else { res.send({ status: 'error', reason: 'proxy-not-trusted' }); return; @@ -64,9 +77,25 @@ app.post('/login', (req, res) => { } break; } - case 'password': + case 'openid': { + if (!isValidRedirectUrl(req.body.return_url)) { + res + .status(400) + .send({ status: 'error', reason: 'Invalid redirect URL' }); + return; + } + + let { error, url } = await loginWithOpenIdSetup(req.body.return_url); + if (error) { + res.status(400).send({ status: 'error', reason: error }); + return; + } + res.send({ status: 'ok', data: { redirect_url: url } }); + return; + } + default: - tokenRes = login(req.body.password); + tokenRes = loginWithPassword(req.body.password); break; } let { error, token } = tokenRes; @@ -80,13 +109,13 @@ app.post('/login', (req, res) => { }); app.post('/change-password', (req, res) => { - let user = validateUser(req, res); - if (!user) return; + let session = validateSession(req, res); + if (!session) return; let { error } = changePassword(req.body.password); if (error) { - res.send({ status: 'error', reason: error }); + res.status(400).send({ status: 'error', reason: error }); return; } @@ -94,8 +123,24 @@ app.post('/change-password', (req, res) => { }); app.get('/validate', (req, res) => { - let user = validateUser(req, res); - if (user) { - res.send({ status: 'ok', data: { validated: true } }); + let session = validateSession(req, res); + if (session) { + const user = getUserInfo(session.user_id); + if (!user) { + res.status(400).send({ status: 'error', reason: 'User not found' }); + return; + } + + res.send({ + status: 'ok', + data: { + validated: true, + userName: user?.user_name, + permission: user?.role, + userId: session?.user_id, + displayName: user?.display_name, + loginMethod: session?.auth_method, + }, + }); } }); diff --git a/packages/sync-server/src/app-admin.js b/packages/sync-server/src/app-admin.js new file mode 100644 index 00000000000..f920a266be3 --- /dev/null +++ b/packages/sync-server/src/app-admin.js @@ -0,0 +1,409 @@ +import express from 'express'; +import * as uuid from 'uuid'; +import { + errorMiddleware, + requestLoggerMiddleware, + validateSessionMiddleware, +} from './util/middlewares.js'; +import validateSession from './util/validate-user.js'; +import { isAdmin } from './account-db.js'; +import * as UserService from './services/user-service.js'; + +let app = express(); +app.use(express.json()); +app.use(express.urlencoded({ extended: true })); +app.use(requestLoggerMiddleware); + +export { app as handlers }; + +app.get('/owner-created/', (req, res) => { + try { + const ownerCount = UserService.getOwnerCount(); + res.json(ownerCount > 0); + } catch (error) { + res.status(500).json({ error: 'Failed to retrieve owner count' }); + } +}); + +app.get('/users/', validateSessionMiddleware, (req, res) => { + const users = UserService.getAllUsers(); + res.json( + users.map((u) => ({ + ...u, + owner: u.owner === 1, + enabled: u.enabled === 1, + })), + ); +}); + +app.post('/users', validateSessionMiddleware, async (req, res) => { + if (!isAdmin(res.locals.user_id)) { + res.status(403).send({ + status: 'error', + reason: 'forbidden', + details: 'permission-not-found', + }); + return; + } + + const { userName, role, displayName, enabled } = req.body; + + if (!userName || !role) { + res.status(400).send({ + status: 'error', + reason: `${!userName ? 'user-cant-be-empty' : 'role-cant-be-empty'}`, + details: `${!userName ? 'Username' : 'Role'} cannot be empty`, + }); + return; + } + + const roleIdFromDb = UserService.validateRole(role); + if (!roleIdFromDb) { + res.status(400).send({ + status: 'error', + reason: 'role-does-not-exists', + details: 'Selected role does not exist', + }); + return; + } + + const userIdInDb = UserService.getUserByUsername(userName); + if (userIdInDb) { + res.status(400).send({ + status: 'error', + reason: 'user-already-exists', + details: `User ${userName} already exists`, + }); + return; + } + + const userId = uuid.v4(); + UserService.insertUser( + userId, + userName, + displayName || null, + enabled ? 1 : 0, + ); + + res.status(200).send({ status: 'ok', data: { id: userId } }); +}); + +app.patch('/users', validateSessionMiddleware, async (req, res) => { + if (!isAdmin(res.locals.user_id)) { + res.status(403).send({ + status: 'error', + reason: 'forbidden', + details: 'permission-not-found', + }); + return; + } + + const { id, userName, role, displayName, enabled } = req.body; + + if (!userName || !role) { + res.status(400).send({ + status: 'error', + reason: `${!userName ? 'user-cant-be-empty' : 'role-cant-be-empty'}`, + details: `${!userName ? 'Username' : 'Role'} cannot be empty`, + }); + return; + } + + const roleIdFromDb = UserService.validateRole(role); + if (!roleIdFromDb) { + res.status(400).send({ + status: 'error', + reason: 'role-does-not-exists', + details: 'Selected role does not exist', + }); + return; + } + + const userIdInDb = UserService.getUserById(id); + if (!userIdInDb) { + res.status(400).send({ + status: 'error', + reason: 'cannot-find-user-to-update', + details: `Cannot find user ${userName} to update`, + }); + return; + } + + UserService.updateUserWithRole( + userIdInDb, + userName, + displayName || null, + enabled ? 1 : 0, + role, + ); + + res.status(200).send({ status: 'ok', data: { id: userIdInDb } }); +}); + +app.delete('/users', validateSessionMiddleware, async (req, res) => { + if (!isAdmin(res.locals.user_id)) { + res.status(403).send({ + status: 'error', + reason: 'forbidden', + details: 'permission-not-found', + }); + return; + } + + const ids = req.body.ids; + let totalDeleted = 0; + ids.forEach((item) => { + const ownerId = UserService.getOwnerId(); + + if (item === ownerId) return; + + UserService.deleteUserAccess(item); + UserService.transferAllFilesFromUser(ownerId, item); + const usersDeleted = UserService.deleteUser(item); + totalDeleted += usersDeleted; + }); + + if (ids.length === totalDeleted) { + res + .status(200) + .send({ status: 'ok', data: { someDeletionsFailed: false } }); + } else { + res.status(400).send({ + status: 'error', + reason: 'not-all-deleted', + details: '', + }); + } +}); + +app.get('/access', validateSessionMiddleware, (req, res) => { + const fileId = req.query.fileId; + + const { granted } = UserService.checkFilePermission( + fileId, + res.locals.user_id, + ) || { + granted: 0, + }; + + if (granted === 0 && !isAdmin(res.locals.user_id)) { + res.status(403).send({ + status: 'error', + reason: 'forbidden', + details: 'permission-not-found', + }); + return false; + } + + const fileIdInDb = UserService.getFileById(fileId); + if (!fileIdInDb) { + res.status(404).send({ + status: 'error', + reason: 'invalid-file-id', + details: 'File not found at server', + }); + return false; + } + + const accesses = UserService.getUserAccess( + fileId, + res.locals.user_id, + isAdmin(res.locals.user_id), + ); + + res.json(accesses); +}); + +app.post('/access', (req, res) => { + const userAccess = req.body || {}; + const session = validateSession(req, res); + + if (!session) return; + + const { granted } = UserService.checkFilePermission( + userAccess.fileId, + session.user_id, + ) || { + granted: 0, + }; + + if (granted === 0 && !isAdmin(session.user_id)) { + res.status(400).send({ + status: 'error', + reason: 'file-denied', + details: "You don't have permissions over this file", + }); + return; + } + + const fileIdInDb = UserService.getFileById(userAccess.fileId); + if (!fileIdInDb) { + res.status(404).send({ + status: 'error', + reason: 'invalid-file-id', + details: 'File not found at server', + }); + return; + } + + if (!userAccess.userId) { + res.status(400).send({ + status: 'error', + reason: 'user-cant-be-empty', + details: 'User cannot be empty', + }); + return; + } + + if (UserService.countUserAccess(userAccess.fileId, userAccess.userId) > 0) { + res.status(400).send({ + status: 'error', + reason: 'user-already-have-access', + details: 'User already have access', + }); + return; + } + + UserService.addUserAccess(userAccess.userId, userAccess.fileId); + + res.status(200).send({ status: 'ok', data: {} }); +}); + +app.delete('/access', (req, res) => { + const fileId = req.query.fileId; + const session = validateSession(req, res); + if (!session) return; + + const { granted } = UserService.checkFilePermission( + fileId, + session.user_id, + ) || { + granted: 0, + }; + + if (granted === 0 && !isAdmin(session.user_id)) { + res.status(400).send({ + status: 'error', + reason: 'file-denied', + details: "You don't have permissions over this file", + }); + return; + } + + const fileIdInDb = UserService.getFileById(fileId); + if (!fileIdInDb) { + res.status(404).send({ + status: 'error', + reason: 'invalid-file-id', + details: 'File not found at server', + }); + return; + } + + const ids = req.body.ids; + let totalDeleted = UserService.deleteUserAccessByFileId(ids, fileId); + + if (ids.length === totalDeleted) { + res + .status(200) + .send({ status: 'ok', data: { someDeletionsFailed: false } }); + } else { + res.status(400).send({ + status: 'error', + reason: 'not-all-deleted', + details: '', + }); + } +}); + +app.get('/access/users', validateSessionMiddleware, async (req, res) => { + const fileId = req.query.fileId; + + const { granted } = UserService.checkFilePermission( + fileId, + res.locals.user_id, + ) || { + granted: 0, + }; + + if (granted === 0 && !isAdmin(res.locals.user_id)) { + res.status(400).send({ + status: 'error', + reason: 'file-denied', + details: "You don't have permissions over this file", + }); + return; + } + + const fileIdInDb = UserService.getFileById(fileId); + if (!fileIdInDb) { + res.status(404).send({ + status: 'error', + reason: 'invalid-file-id', + details: 'File not found at server', + }); + return; + } + + const users = UserService.getAllUserAccess(fileId); + res.json(users); +}); + +app.post( + '/access/transfer-ownership/', + validateSessionMiddleware, + (req, res) => { + const newUserOwner = req.body || {}; + + const { granted } = UserService.checkFilePermission( + newUserOwner.fileId, + res.locals.user_id, + ) || { + granted: 0, + }; + + if (granted === 0 && !isAdmin(res.locals.user_id)) { + res.status(400).send({ + status: 'error', + reason: 'file-denied', + details: "You don't have permissions over this file", + }); + return; + } + + const fileIdInDb = UserService.getFileById(newUserOwner.fileId); + if (!fileIdInDb) { + res.status(404).send({ + status: 'error', + reason: 'invalid-file-id', + details: 'File not found at server', + }); + return; + } + + if (!newUserOwner.newUserId) { + res.status(400).send({ + status: 'error', + reason: 'user-cant-be-empty', + details: 'Username cannot be empty', + }); + return; + } + + const newUserIdFromDb = UserService.getUserById(newUserOwner.newUserId); + if (newUserIdFromDb === 0) { + res.status(400).send({ + status: 'error', + reason: 'new-user-not-found', + details: 'New user not found', + }); + return; + } + + UserService.updateFileOwner(newUserOwner.newUserId, newUserOwner.fileId); + + res.status(200).send({ status: 'ok', data: {} }); + }, +); + +app.use(errorMiddleware); diff --git a/packages/sync-server/src/app-admin.test.js b/packages/sync-server/src/app-admin.test.js new file mode 100644 index 00000000000..29f22ec250c --- /dev/null +++ b/packages/sync-server/src/app-admin.test.js @@ -0,0 +1,380 @@ +import request from 'supertest'; +import { handlers as app } from './app-admin.js'; +import getAccountDb from './account-db.js'; +import { v4 as uuidv4 } from 'uuid'; + +const ADMIN_ROLE = 'ADMIN'; +const BASIC_ROLE = 'BASIC'; + +// Create user helper function +const createUser = (userId, userName, role, owner = 0, enabled = 1) => { + getAccountDb().mutate( + 'INSERT INTO users (id, user_name, display_name, enabled, owner, role) VALUES (?, ?, ?, ?, ?, ?)', + [userId, userName, `${userName} display`, enabled, owner, role], + ); +}; + +const deleteUser = (userId) => { + getAccountDb().mutate('DELETE FROM user_access WHERE user_id = ?', [userId]); + getAccountDb().mutate('DELETE FROM users WHERE id = ?', [userId]); +}; + +const createSession = (userId, sessionToken) => { + getAccountDb().mutate( + 'INSERT INTO sessions (token, user_id, expires_at) VALUES (?, ?, ?)', + [sessionToken, userId, Date.now() + 1000 * 60 * 60], // Expire in 1 hour + ); +}; + +const generateSessionToken = () => `token-${uuidv4()}`; + +describe('/admin', () => { + describe('/owner-created', () => { + it('should return 200 and true if an owner user is created', async () => { + const sessionToken = generateSessionToken(); + const adminId = uuidv4(); + createUser(adminId, 'admin', ADMIN_ROLE, 1); + createSession(adminId, sessionToken); + + const res = await request(app) + .get('/owner-created') + .set('x-actual-token', sessionToken); + + expect(res.statusCode).toEqual(200); + expect(res.body).toBe(true); + }); + }); + + describe('/users', () => { + describe('GET /users', () => { + let sessionUserId, testUserId, sessionToken; + + beforeEach(() => { + sessionUserId = uuidv4(); + testUserId = uuidv4(); + sessionToken = generateSessionToken(); + + createUser(sessionUserId, 'sessionUser', ADMIN_ROLE); + createSession(sessionUserId, sessionToken); + createUser(testUserId, 'testUser', ADMIN_ROLE); + }); + + afterEach(() => { + deleteUser(sessionUserId); + deleteUser(testUserId); + }); + + it('should return 200 and a list of users', async () => { + const res = await request(app) + .get('/users') + .set('x-actual-token', sessionToken); + + expect(res.statusCode).toEqual(200); + expect(res.body.length).toBeGreaterThan(0); + }); + }); + + describe('POST /users', () => { + let sessionUserId, sessionToken; + let createdUserId; + let duplicateUserId; + + beforeEach(() => { + sessionUserId = uuidv4(); + sessionToken = generateSessionToken(); + createUser(sessionUserId, 'sessionUser', ADMIN_ROLE); + createSession(sessionUserId, sessionToken); + }); + + afterEach(() => { + deleteUser(sessionUserId); + if (createdUserId) { + deleteUser(createdUserId); + createdUserId = null; + } + + if (duplicateUserId) { + deleteUser(duplicateUserId); + duplicateUserId = null; + } + }); + + it('should return 200 and create a new user', async () => { + const newUser = { + userName: 'user1', + displayName: 'User One', + enabled: 1, + owner: 0, + role: BASIC_ROLE, + }; + + const res = await request(app) + .post('/users') + .send(newUser) + .set('x-actual-token', sessionToken); + + expect(res.statusCode).toEqual(200); + expect(res.body.status).toBe('ok'); + expect(res.body.data).toHaveProperty('id'); + + createdUserId = res.body.data.id; + }); + + it('should return 400 if the user already exists', async () => { + const newUser = { + userName: 'user1', + displayName: 'User One', + enabled: 1, + owner: 0, + role: BASIC_ROLE, + }; + + let res = await request(app) + .post('/users') + .send(newUser) + .set('x-actual-token', sessionToken); + + duplicateUserId = res.body.data.id; + + res = await request(app) + .post('/users') + .send(newUser) + .set('x-actual-token', sessionToken); + + expect(res.statusCode).toEqual(400); + expect(res.body.status).toBe('error'); + expect(res.body.reason).toBe('user-already-exists'); + }); + }); + + describe('PATCH /users', () => { + let sessionUserId, testUserId, sessionToken; + + beforeEach(() => { + sessionUserId = uuidv4(); + testUserId = uuidv4(); + sessionToken = generateSessionToken(); + + createUser(sessionUserId, 'sessionUser', ADMIN_ROLE); + createSession(sessionUserId, sessionToken); + createUser(testUserId, 'testUser', ADMIN_ROLE); + }); + + afterEach(() => { + deleteUser(sessionUserId); + deleteUser(testUserId); + }); + + it('should return 200 and update an existing user', async () => { + const userToUpdate = { + id: testUserId, + userName: 'updatedUser', + displayName: 'Updated User', + enabled: true, + role: BASIC_ROLE, + }; + + const res = await request(app) + .patch('/users') + .send(userToUpdate) + .set('x-actual-token', sessionToken); + + expect(res.statusCode).toEqual(200); + expect(res.body.status).toBe('ok'); + expect(res.body.data.id).toBe(testUserId); + }); + + it('should return 400 if the user does not exist', async () => { + const userToUpdate = { + id: 'non-existing-id', + userName: 'nonexistinguser', + displayName: 'Non-existing User', + enabled: true, + role: BASIC_ROLE, + }; + + const res = await request(app) + .patch('/users') + .send(userToUpdate) + .set('x-actual-token', sessionToken); + + expect(res.statusCode).toEqual(400); + expect(res.body.status).toBe('error'); + expect(res.body.reason).toBe('cannot-find-user-to-update'); + }); + }); + + describe('POST /users/delete-all', () => { + let sessionUserId, testUserId, sessionToken; + + beforeEach(() => { + sessionUserId = uuidv4(); + testUserId = uuidv4(); + sessionToken = generateSessionToken(); + + createUser(sessionUserId, 'sessionUser', ADMIN_ROLE); + createSession(sessionUserId, sessionToken); + createUser(testUserId, 'testUser', ADMIN_ROLE); + }); + + afterEach(() => { + deleteUser(sessionUserId); + deleteUser(testUserId); + }); + + it('should return 200 and delete all specified users', async () => { + const userToDelete = { + ids: [testUserId], + }; + + const res = await request(app) + .delete('/users') + .send(userToDelete) + .set('x-actual-token', sessionToken); + + expect(res.statusCode).toEqual(200); + expect(res.body.status).toBe('ok'); + expect(res.body.data.someDeletionsFailed).toBe(false); + }); + + it('should return 400 if not all users are deleted', async () => { + const userToDelete = { + ids: ['non-existing-id'], + }; + + const res = await request(app) + .delete('/users') + .send(userToDelete) + .set('x-actual-token', sessionToken); + + expect(res.statusCode).toEqual(400); + expect(res.body.status).toBe('error'); + expect(res.body.reason).toBe('not-all-deleted'); + }); + }); + }); + + describe('/access', () => { + describe('POST /access', () => { + let sessionUserId, testUserId, fileId, sessionToken; + + beforeEach(() => { + sessionUserId = uuidv4(); + testUserId = uuidv4(); + fileId = uuidv4(); + sessionToken = generateSessionToken(); + + createUser(sessionUserId, 'sessionUser', ADMIN_ROLE); + createSession(sessionUserId, sessionToken); + createUser(testUserId, 'testUser', ADMIN_ROLE); + getAccountDb().mutate('INSERT INTO files (id, owner) VALUES (?, ?)', [ + fileId, + sessionUserId, + ]); + }); + + afterEach(() => { + deleteUser(sessionUserId); + deleteUser(testUserId); + getAccountDb().mutate('DELETE FROM files WHERE id = ?', [fileId]); + }); + + it('should return 200 and grant access to a user', async () => { + const newUserAccess = { + fileId, + userId: testUserId, + }; + + const res = await request(app) + .post('/access') + .send(newUserAccess) + .set('x-actual-token', sessionToken); + + expect(res.statusCode).toEqual(200); + expect(res.body.status).toBe('ok'); + }); + + it('should return 400 if the user already has access', async () => { + const newUserAccess = { + fileId, + userId: testUserId, + }; + + await request(app) + .post('/access') + .send(newUserAccess) + .set('x-actual-token', sessionToken); + + const res = await request(app) + .post('/access') + .send(newUserAccess) + .set('x-actual-token', sessionToken); + + expect(res.statusCode).toEqual(400); + expect(res.body.status).toBe('error'); + expect(res.body.reason).toBe('user-already-have-access'); + }); + }); + + describe('DELETE /access', () => { + let sessionUserId, testUserId, fileId, sessionToken; + + beforeEach(() => { + sessionUserId = uuidv4(); + testUserId = uuidv4(); + fileId = uuidv4(); + sessionToken = generateSessionToken(); + + createUser(sessionUserId, 'sessionUser', ADMIN_ROLE); + createSession(sessionUserId, sessionToken); + createUser(testUserId, 'testUser', ADMIN_ROLE); + getAccountDb().mutate('INSERT INTO files (id, owner) VALUES (?, ?)', [ + fileId, + sessionUserId, + ]); + getAccountDb().mutate( + 'INSERT INTO user_access (user_id, file_id) VALUES (?, ?)', + [testUserId, fileId], + ); + }); + + afterEach(() => { + deleteUser(sessionUserId); + deleteUser(testUserId); + getAccountDb().mutate('DELETE FROM files WHERE id = ?', [fileId]); + }); + + it('should return 200 and delete access for the specified user', async () => { + const deleteAccess = { + ids: [testUserId], + }; + + const res = await request(app) + .delete('/access') + .send(deleteAccess) + .query({ fileId }) + .set('x-actual-token', sessionToken); + + expect(res.statusCode).toEqual(200); + expect(res.body.status).toBe('ok'); + expect(res.body.data.someDeletionsFailed).toBe(false); + }); + + it('should return 400 if not all access deletions are successful', async () => { + const deleteAccess = { + ids: ['non-existing-id'], + }; + + const res = await request(app) + .delete('/access') + .send(deleteAccess) + .query({ fileId }) + .set('x-actual-token', sessionToken); + + expect(res.statusCode).toEqual(400); + expect(res.body.status).toBe('error'); + expect(res.body.reason).toBe('not-all-deleted'); + }); + }); + }); +}); diff --git a/packages/sync-server/src/app-gocardless/app-gocardless.js b/packages/sync-server/src/app-gocardless/app-gocardless.js index cdba0dca689..cabeffcd62d 100644 --- a/packages/sync-server/src/app-gocardless/app-gocardless.js +++ b/packages/sync-server/src/app-gocardless/app-gocardless.js @@ -14,7 +14,7 @@ import { handleError } from './util/handle-error.js'; import { sha256String } from '../util/hash.js'; import { requestLoggerMiddleware, - validateUserMiddleware, + validateSessionMiddleware, } from '../util/middlewares.js'; const app = express(); @@ -26,7 +26,7 @@ app.get('/link', function (req, res) { export { app as handlers }; app.use(express.json()); -app.use(validateUserMiddleware); +app.use(validateSessionMiddleware); app.post('/status', async (req, res) => { res.send({ diff --git a/packages/sync-server/src/app-gocardless/bank-factory.js b/packages/sync-server/src/app-gocardless/bank-factory.js index c7e2966e1d6..a38afa3bc45 100644 --- a/packages/sync-server/src/app-gocardless/bank-factory.js +++ b/packages/sync-server/src/app-gocardless/bank-factory.js @@ -26,6 +26,7 @@ import SparNordSpNoDK22 from './banks/sparnord-spnodk22.js'; import SpkKarlsruhekarsde66 from './banks/spk-karlsruhe-karsde66.js'; import SpkMarburgBiedenkopfHeladef1mar from './banks/spk-marburg-biedenkopf-heladef1mar.js'; import SpkWormsAlzeyRiedMalade51wor from './banks/spk-worms-alzey-ried-malade51wor.js'; +import SwedbankHabaLV22 from './banks/swedbank-habalv22.js'; import VirginNrnbgb22 from './banks/virgin_nrnbgb22.js'; export const banks = [ @@ -56,6 +57,7 @@ export const banks = [ SpkKarlsruhekarsde66, SpkMarburgBiedenkopfHeladef1mar, SpkWormsAlzeyRiedMalade51wor, + SwedbankHabaLV22, VirginNrnbgb22, ]; diff --git a/packages/sync-server/src/app-gocardless/banks/1822-direkt-heladef1822.js b/packages/sync-server/src/app-gocardless/banks/1822-direkt-heladef1822.js new file mode 100644 index 00000000000..74a2393547b --- /dev/null +++ b/packages/sync-server/src/app-gocardless/banks/1822-direkt-heladef1822.js @@ -0,0 +1,16 @@ +import Fallback from './integration-bank.js'; + +/** @type {import('./bank.interface.js').IBank} */ +export default { + ...Fallback, + + institutionIds: ['DIREKT_HELADEF1822'], + + normalizeTransaction(transaction, booked) { + transaction.remittanceInformationUnstructured = + transaction.remittanceInformationUnstructured ?? + transaction.remittanceInformationStructured; + + return Fallback.normalizeTransaction(transaction, booked); + }, +}; diff --git a/packages/sync-server/src/app-gocardless/banks/ing-pl-ingbplpw.js b/packages/sync-server/src/app-gocardless/banks/ing-pl-ingbplpw.js index 13928fbe2e3..05b7b9ddea3 100644 --- a/packages/sync-server/src/app-gocardless/banks/ing-pl-ingbplpw.js +++ b/packages/sync-server/src/app-gocardless/banks/ing-pl-ingbplpw.js @@ -27,7 +27,7 @@ export default { return { ...transaction, payeeName: formatPayeeName(transaction), - date: transaction.bookingDate || transaction.valueDate, + date: transaction.valueDate ?? transaction.bookingDate, }; }, diff --git a/packages/sync-server/src/app-gocardless/banks/swedbank-habalv22.js b/packages/sync-server/src/app-gocardless/banks/swedbank-habalv22.js new file mode 100644 index 00000000000..87c745cd1cd --- /dev/null +++ b/packages/sync-server/src/app-gocardless/banks/swedbank-habalv22.js @@ -0,0 +1,51 @@ +import d from 'date-fns'; + +import Fallback from './integration-bank.js'; + +/** @type {import('./bank.interface.js').IBank} */ +export default { + ...Fallback, + + institutionIds: ['SWEDBANK_HABALV22'], + + accessValidForDays: 90, + + /** + * The actual transaction date for card transactions is only available in the remittanceInformationUnstructured field when the transaction is booked. + */ + normalizeTransaction(transaction, booked) { + const isCardTransaction = + transaction.remittanceInformationUnstructured?.startsWith('PIRKUMS'); + + if (isCardTransaction) { + if (!booked && !transaction.creditorName) { + const creditorNameMatch = + transaction.remittanceInformationUnstructured?.match( + /PIRKUMS [\d*]+ \d{2}\.\d{2}\.\d{2} \d{2}:\d{2} [\d.]+ \w{3} \(\d+\) (.+)/, + ); + + if (creditorNameMatch) { + transaction = { + ...transaction, + creditorName: creditorNameMatch[1], + }; + } + } + + const dateMatch = transaction.remittanceInformationUnstructured?.match( + /PIRKUMS [\d*]+ (\d{2}\.\d{2}\.\d{4})/, + ); + + if (dateMatch) { + const extractedDate = d.parse(dateMatch[1], 'dd.MM.yyyy', new Date()); + + transaction = { + ...transaction, + bookingDate: d.format(extractedDate, 'yyyy-MM-dd'), + }; + } + } + + return Fallback.normalizeTransaction(transaction, booked); + }, +}; diff --git a/packages/sync-server/src/app-gocardless/banks/tests/swedbank-habalv22.spec.js b/packages/sync-server/src/app-gocardless/banks/tests/swedbank-habalv22.spec.js new file mode 100644 index 00000000000..91e2e01217b --- /dev/null +++ b/packages/sync-server/src/app-gocardless/banks/tests/swedbank-habalv22.spec.js @@ -0,0 +1,62 @@ +import SwedbankHabaLV22 from '../swedbank-habalv22.js'; + +describe('#normalizeTransaction', () => { + const bookedCardTransaction = { + transactionId: '2024102900000000-1', + bookingDate: '2024-10-29', + valueDate: '2024-10-29', + transactionAmount: { + amount: '-22.99', + currency: 'EUR', + }, + creditorName: 'SOME CREDITOR NAME', + remittanceInformationUnstructured: + 'PIRKUMS 424242******4242 28.10.2024 22.99 EUR (111111) SOME CREDITOR NAME', + bankTransactionCode: 'PMNT-CCRD-POSD', + internalTransactionId: 'fa000f86afb2cc7678bcff0000000000', + }; + + it('extracts card transaction date', () => { + expect( + SwedbankHabaLV22.normalizeTransaction(bookedCardTransaction, true) + .bookingDate, + ).toEqual('2024-10-28'); + + expect( + SwedbankHabaLV22.normalizeTransaction(bookedCardTransaction, true).date, + ).toEqual('2024-10-28'); + }); + + it.each([ + ['regular text', 'Some info'], + ['partial card text', 'PIRKUMS xxx'], + ['null value', null], + ])('normalizes non-card transaction with %s', (_, remittanceInfo) => { + const transaction = { + ...bookedCardTransaction, + remittanceInformationUnstructured: remittanceInfo, + }; + const normalized = SwedbankHabaLV22.normalizeTransaction(transaction, true); + + expect(normalized.bookingDate).toEqual('2024-10-29'); + expect(normalized.date).toEqual('2024-10-29'); + }); + + const pendingCardTransaction = { + transactionId: '2024102900000000-1', + valueDate: '2024-10-29', + transactionAmount: { + amount: '-22.99', + currency: 'EUR', + }, + remittanceInformationUnstructured: + 'PIRKUMS 424242******4242 28.10.24 13:37 22.99 EUR (111111) SOME CREDITOR NAME', + }; + + it('extracts pending card transaction creditor name', () => { + expect( + SwedbankHabaLV22.normalizeTransaction(pendingCardTransaction, false) + .creditorName, + ).toEqual('SOME CREDITOR NAME'); + }); +}); diff --git a/packages/sync-server/src/app-openid.js b/packages/sync-server/src/app-openid.js new file mode 100644 index 00000000000..931484074c3 --- /dev/null +++ b/packages/sync-server/src/app-openid.js @@ -0,0 +1,101 @@ +import express from 'express'; +import { + errorMiddleware, + requestLoggerMiddleware, + validateSessionMiddleware, +} from './util/middlewares.js'; +import { disableOpenID, enableOpenID, isAdmin } from './account-db.js'; +import { + isValidRedirectUrl, + loginWithOpenIdFinalize, +} from './accounts/openid.js'; +import * as UserService from './services/user-service.js'; + +let app = express(); +app.use(express.json()); +app.use(express.urlencoded({ extended: true })); +app.use(requestLoggerMiddleware); +export { app as handlers }; + +app.post('/enable', validateSessionMiddleware, async (req, res) => { + if (!isAdmin(res.locals.user_id)) { + res.status(403).send({ + status: 'error', + reason: 'forbidden', + details: 'permission-not-found', + }); + return; + } + + let { error } = (await enableOpenID(req.body)) || {}; + + if (error) { + res.status(500).send({ status: 'error', reason: error }); + return; + } + res.send({ status: 'ok' }); +}); + +app.post('/disable', validateSessionMiddleware, async (req, res) => { + if (!isAdmin(res.locals.user_id)) { + res.status(403).send({ + status: 'error', + reason: 'forbidden', + details: 'permission-not-found', + }); + return; + } + + let { error } = (await disableOpenID(req.body)) || {}; + + if (error) { + res.status(500).send({ status: 'error', reason: error }); + return; + } + res.send({ status: 'ok' }); +}); + +app.get('/config', async (req, res) => { + const { cnt: ownerCount } = UserService.getOwnerCount() || {}; + + if (ownerCount > 0) { + res.status(400).send({ status: 'error', reason: 'already-bootstraped' }); + return; + } + + const auth = UserService.getOpenIDConfig(); + + if (!auth) { + res + .status(500) + .send({ status: 'error', reason: 'OpenID configuration not found' }); + return; + } + + try { + const openIdConfig = JSON.parse(auth.extra_data); + res.send({ openId: openIdConfig }); + } catch (error) { + res + .status(500) + .send({ status: 'error', reason: 'Invalid OpenID configuration' }); + } +}); + +app.get('/callback', async (req, res) => { + let { error, url } = await loginWithOpenIdFinalize(req.query); + + if (error) { + res.status(400).send({ status: 'error', reason: error }); + return; + } + + if (!isValidRedirectUrl(url)) { + res.status(400).send({ status: 'error', reason: 'Invalid redirect URL' }); + return; + } + + res.redirect(url); +}); + +app.use(errorMiddleware); diff --git a/packages/sync-server/src/app-secrets.js b/packages/sync-server/src/app-secrets.js index 3f842d8cb72..3c06bf8a083 100644 --- a/packages/sync-server/src/app-secrets.js +++ b/packages/sync-server/src/app-secrets.js @@ -1,8 +1,9 @@ import express from 'express'; import { secretsService } from './services/secrets-service.js'; +import getAccountDb, { isAdmin } from './account-db.js'; import { requestLoggerMiddleware, - validateUserMiddleware, + validateSessionMiddleware, } from './util/middlewares.js'; const app = express(); @@ -10,12 +11,39 @@ const app = express(); export { app as handlers }; app.use(express.json()); app.use(requestLoggerMiddleware); - -app.use(validateUserMiddleware); +app.use(validateSessionMiddleware); app.post('/', async (req, res) => { + let method; + try { + const result = getAccountDb().first( + 'SELECT method FROM auth WHERE active = 1', + ); + method = result?.method; + } catch (error) { + console.error('Failed to fetch auth method:', error); + return res.status(500).send({ + status: 'error', + reason: 'database-error', + details: 'Failed to validate authentication method', + }); + } const { name, value } = req.body; + if (method === 'openid') { + let canSaveSecrets = isAdmin(res.locals.user_id); + + if (!canSaveSecrets) { + res.status(403).send({ + status: 'error', + reason: 'not-admin', + details: 'You have to be admin to set secrets', + }); + + return; + } + } + secretsService.set(name, value); res.status(200).send({ status: 'ok' }); diff --git a/packages/sync-server/src/app-simplefin/app-simplefin.js b/packages/sync-server/src/app-simplefin/app-simplefin.js index 7c123166f16..44bc5d53bda 100644 --- a/packages/sync-server/src/app-simplefin/app-simplefin.js +++ b/packages/sync-server/src/app-simplefin/app-simplefin.js @@ -202,7 +202,7 @@ function getAccountResponse(results, accountId, startDate) { let dateToUse = 0; - if (trans.posted == 0) { + if (trans.pending ?? trans.posted == 0) { newTrans.booked = false; dateToUse = trans.transacted_at; } else { @@ -210,12 +210,13 @@ function getAccountResponse(results, accountId, startDate) { dateToUse = trans.posted; } - newTrans.bookingDate = getDate(new Date(dateToUse * 1000)); - if (newTrans.bookingDate < startDate) { + const transactionDate = new Date(dateToUse * 1000); + + if (transactionDate < startDate) { continue; } - newTrans.date = newTrans.bookingDate; + newTrans.date = getDate(transactionDate); newTrans.payeeName = trans.payee; newTrans.remittanceInformationUnstructured = trans.description; newTrans.transactionAmount = { amount: trans.amount, currency: 'USD' }; @@ -266,6 +267,10 @@ function parseAccessKey(accessKey) { let username = null; let password = null; let baseUrl = null; + if (!accessKey || !accessKey.match(/^.*\/\/.*:.*@.*$/)) { + console.log(`Invalid SimpleFIN access key: ${accessKey}`); + throw new Error(`Invalid access key`); + } [scheme, rest] = accessKey.split('//'); [auth, rest] = rest.split('@'); [username, password] = auth.split(':'); diff --git a/packages/sync-server/src/app-sync.js b/packages/sync-server/src/app-sync.js index 79303216603..92fdf301336 100644 --- a/packages/sync-server/src/app-sync.js +++ b/packages/sync-server/src/app-sync.js @@ -5,14 +5,14 @@ import * as uuid from 'uuid'; import { errorMiddleware, requestLoggerMiddleware, - validateUserMiddleware, + validateSessionMiddleware, } from './util/middlewares.js'; -import getAccountDb from './account-db.js'; import { getPathForUserFile, getPathForGroupFile } from './util/paths.js'; import * as simpleSync from './sync-simple.js'; import { SyncProtoBuf } from '@actual-app/crdt'; +import getAccountDb from './account-db.js'; import { File, FilesService, @@ -25,13 +25,13 @@ import { } from './app-sync/validation.js'; const app = express(); +app.use(validateSessionMiddleware); app.use(errorMiddleware); app.use(requestLoggerMiddleware); app.use(express.raw({ type: 'application/actual-sync' })); app.use(express.raw({ type: 'application/encrypted-file' })); app.use(express.json()); -app.use(validateUserMiddleware); export { app as handlers }; const OK_RESPONSE = { status: 'ok' }; @@ -113,6 +113,8 @@ app.post('/sync', async (req, res) => { }); app.post('/user-get-key', (req, res) => { + if (!res.locals) return; + let { fileId } = req.body; const filesService = new FilesService(getAccountDb()); @@ -246,6 +248,11 @@ app.post('/upload-user-file', async (req, res) => { syncVersion: syncFormatVersion, name: name, encryptMeta: encryptMeta, + owner: + res.locals.user_id || + (() => { + throw new Error('User ID is required for file creation'); + })(), }), ); @@ -305,7 +312,7 @@ app.post('/update-user-filename', (req, res) => { app.get('/list-user-files', (req, res) => { const fileService = new FilesService(getAccountDb()); - const rows = fileService.find(); + const rows = fileService.find({ userId: res.locals.user_id }); res.send({ status: 'ok', data: rows.map((row) => ({ @@ -314,6 +321,13 @@ app.get('/list-user-files', (req, res) => { groupId: row.groupId, name: row.name, encryptKeyId: row.encryptKeyId, + owner: row.owner, + usersWithAccess: fileService + .findUsersWithAccess(row.id) + .map((access) => ({ + ...access, + owner: access.userId === row.owner, + })), })), }); }); @@ -349,6 +363,12 @@ app.get('/get-user-file-info', (req, res) => { groupId: file.groupId, name: file.name, encryptMeta: file.encryptMeta ? JSON.parse(file.encryptMeta) : null, + usersWithAccess: fileService + .findUsersWithAccess(file.id) + .map((access) => ({ + ...access, + owner: access.userId === file.owner, + })), }, }); }); diff --git a/packages/sync-server/src/app-sync.test.js b/packages/sync-server/src/app-sync.test.js index b81d5f14460..3d3dd10bdee 100644 --- a/packages/sync-server/src/app-sync.test.js +++ b/packages/sync-server/src/app-sync.test.js @@ -1,11 +1,20 @@ import fs from 'node:fs'; import request from 'supertest'; import { handlers as app } from './app-sync.js'; -import getAccountDb from './account-db.js'; import { getPathForUserFile } from './util/paths.js'; +import getAccountDb from './account-db.js'; import { SyncProtoBuf } from '@actual-app/crdt'; import crypto from 'node:crypto'; +const ADMIN_ROLE = 'ADMIN'; + +const createUser = (userId, userName, role, owner = 0, enabled = 1) => { + getAccountDb().mutate( + 'INSERT INTO users (id, user_name, display_name, enabled, owner, role) VALUES (?, ?, ?, ?, ?, ?)', + [userId, userName, `${userName} display`, enabled, owner, role], + ); +}; + describe('/user-get-key', () => { it('returns 401 if the user is not authenticated', async () => { const res = await request(app).post('/user-get-key'); @@ -25,8 +34,8 @@ describe('/user-get-key', () => { const encrypt_test = 'test-encrypt-test'; getAccountDb().mutate( - 'INSERT INTO files (id, encrypt_salt, encrypt_keyid, encrypt_test) VALUES (?, ?, ?, ?)', - [fileId, encrypt_salt, encrypt_keyid, encrypt_test], + 'INSERT INTO files (id, encrypt_salt, encrypt_keyid, encrypt_test, owner) VALUES (?, ?, ?, ?, ?)', + [fileId, encrypt_salt, encrypt_keyid, encrypt_test, 'genericAdmin'], ); const res = await request(app) @@ -135,8 +144,13 @@ describe('/reset-user-file', () => { // Use addMockFile to insert a mock file into the database getAccountDb().mutate( - 'INSERT INTO files (id, group_id, deleted) VALUES (?, ?, FALSE)', - [fileId, groupId], + 'INSERT INTO files (id, group_id, deleted, owner) VALUES (?, ?, FALSE, ?)', + [fileId, groupId, 'genericAdmin'], + ); + + getAccountDb().mutate( + 'INSERT INTO user_access (file_id, user_id) VALUES (?, ?)', + [fileId, 'genericAdmin'], ); const res = await request(app) @@ -518,6 +532,7 @@ describe('/list-user-files', () => { }); it('returns a list of user files for an authenticated user', async () => { + createUser('fileListAdminId', 'admin', ADMIN_ROLE, 1); const fileId1 = crypto.randomBytes(16).toString('hex'); const fileId2 = crypto.randomBytes(16).toString('hex'); const fileName1 = 'file1.txt'; @@ -525,12 +540,12 @@ describe('/list-user-files', () => { // Insert mock files into the database getAccountDb().mutate( - 'INSERT INTO files (id, name, deleted) VALUES (?, ?, FALSE)', - [fileId1, fileName1], + 'INSERT INTO files (id, name, deleted, owner) VALUES (?, ?, FALSE, ?)', + [fileId1, fileName1, ''], ); getAccountDb().mutate( - 'INSERT INTO files (id, name, deleted) VALUES (?, ?, FALSE)', - [fileId2, fileName2], + 'INSERT INTO files (id, name, deleted, owner) VALUES (?, ?, FALSE, ?)', + [fileId2, fileName2, ''], ); const res = await request(app) @@ -601,6 +616,7 @@ describe('/get-user-file-info', () => { groupId: fileInfo.group_id, name: fileInfo.name, encryptMeta: { key: 'value' }, + usersWithAccess: [], }, }); }); @@ -830,8 +846,8 @@ describe('/sync', () => { function addMockFile(fileId, groupId, keyId, encryptMeta, syncVersion) { getAccountDb().mutate( - 'INSERT INTO files (id, group_id, encrypt_keyid, encrypt_meta, sync_version) VALUES (?, ?, ?,?, ?)', - [fileId, groupId, keyId, encryptMeta, syncVersion], + 'INSERT INTO files (id, group_id, encrypt_keyid, encrypt_meta, sync_version, owner) VALUES (?, ?, ?,?, ?, ?)', + [fileId, groupId, keyId, encryptMeta, syncVersion, 'genericAdmin'], ); } diff --git a/packages/sync-server/src/app-sync/services/files-service.js b/packages/sync-server/src/app-sync/services/files-service.js index 63fe7f373b6..a01ba417236 100644 --- a/packages/sync-server/src/app-sync/services/files-service.js +++ b/packages/sync-server/src/app-sync/services/files-service.js @@ -1,4 +1,4 @@ -import getAccountDb from '../../account-db.js'; +import getAccountDb, { isAdmin } from '../../account-db.js'; import { FileNotFound, GenericFileError } from '../errors.js'; class FileBase { @@ -11,6 +11,7 @@ class FileBase { encryptMeta, syncVersion, deleted, + owner, ) { this.name = name; this.groupId = groupId; @@ -20,6 +21,7 @@ class FileBase { this.encryptMeta = encryptMeta; this.syncVersion = syncVersion; this.deleted = typeof deleted === 'boolean' ? deleted : Boolean(deleted); + this.owner = owner; } } @@ -34,6 +36,7 @@ class File extends FileBase { encryptMeta = null, syncVersion = null, deleted = false, + owner = null, }) { super( name, @@ -44,6 +47,7 @@ class File extends FileBase { encryptMeta, syncVersion, deleted, + owner, ); this.id = id; } @@ -64,6 +68,7 @@ class FileUpdate extends FileBase { encryptMeta = undefined, syncVersion = undefined, deleted = undefined, + owner = undefined, }) { super( name, @@ -74,6 +79,7 @@ class FileUpdate extends FileBase { encryptMeta, syncVersion, deleted, + owner, ); } } @@ -99,7 +105,7 @@ class FilesService { set(file) { const deletedInt = boolToInt(file.deleted); this.accountDb.mutate( - 'INSERT INTO files (id, group_id, sync_version, name, encrypt_meta, encrypt_salt, encrypt_test, encrypt_keyid, deleted) VALUES (?, ?, ?, ?, ?, ?, ?,? ,?)', + 'INSERT INTO files (id, group_id, sync_version, name, encrypt_meta, encrypt_salt, encrypt_test, encrypt_keyid, deleted, owner) VALUES (?, ?, ?, ?, ?, ?, ?, ? ,?, ?)', [ file.id, file.groupId, @@ -110,14 +116,53 @@ class FilesService { file.encrypt_test, file.encrypt_keyid, deletedInt, + file.owner, ], ); } - find(limit = 1000) { - return this.accountDb - .all('SELECT * FROM files WHERE deleted = 0 LIMIT ?', [limit]) - .map(this.validate); + find({ userId, limit = 1000 }) { + const canSeeAll = isAdmin(userId); + + return ( + canSeeAll + ? this.accountDb.all('SELECT * FROM files WHERE deleted = 0 LIMIT ?', [ + limit, + ]) + : this.accountDb.all( + `SELECT files.* + FROM files + WHERE files.owner = ? and deleted = 0 + UNION + SELECT files.* + FROM files + JOIN user_access + ON user_access.file_id = files.id + AND user_access.user_id = ? + WHERE files.deleted = 0 LIMIT ?`, + [userId, userId, limit], + ) + ).map(this.validate); + } + + findUsersWithAccess(fileId) { + const userAccess = + this.accountDb.all( + `SELECT UA.user_id as userId, users.display_name displayName, users.user_name userName + FROM files + JOIN user_access UA ON UA.file_id = files.id + JOIN users on users.id = UA.user_id + WHERE files.id = ? + UNION ALL + SELECT users.id, users.display_name, users.user_name + FROM files + JOIN users on users.id = files.owner + WHERE files.id = ? + `, + [fileId, fileId], + ) || []; + + return userAccess; } update(id, fileUpdate) { @@ -188,6 +233,7 @@ class FilesService { encryptMeta: rawFile.encrypt_meta, syncVersion: rawFile.sync_version, deleted: Boolean(rawFile.deleted), + owner: rawFile.owner, }); } } diff --git a/packages/sync-server/src/app-sync/tests/services/files-service.test.js b/packages/sync-server/src/app-sync/tests/services/files-service.test.js index 7d3dadd6d11..9807d99a0d4 100644 --- a/packages/sync-server/src/app-sync/tests/services/files-service.test.js +++ b/packages/sync-server/src/app-sync/tests/services/files-service.test.js @@ -28,6 +28,7 @@ describe('FilesService', () => { }; const clearDatabase = () => { + accountDb.mutate('DELETE FROM user_access'); accountDb.mutate('DELETE FROM files'); }; @@ -123,7 +124,7 @@ describe('FilesService', () => { ); test('find should return a list of files', () => { - const files = filesService.find(); + const files = filesService.find({ userId: 'genericAdmin' }); expect(files.length).toBe(1); expect(files[0]).toEqual( new File({ @@ -152,11 +153,14 @@ describe('FilesService', () => { }), ); // Make sure that the file was inserted - const allFiles = filesService.find(); + const allFiles = filesService.find({ userId: 'genericAdmin' }); expect(allFiles.length).toBe(2); // Limit the number of files returned - const limitedFiles = filesService.find(1); + const limitedFiles = filesService.find({ + userId: 'genericAdmin', + limit: 1, + }); expect(limitedFiles.length).toBe(1); }); @@ -188,6 +192,37 @@ describe('FilesService', () => { ); }); + test('find should return only files accessible to the user', () => { + filesService.set( + new File({ + id: crypto.randomBytes(16).toString('hex'), + groupId: 'group2', + syncVersion: 1, + name: 'file2', + encryptMeta: '{"key":"value2"}', + deleted: false, + owner: 'genericAdmin', + }), + ); + + filesService.set( + new File({ + id: crypto.randomBytes(16).toString('hex'), + groupId: 'group2', + syncVersion: 1, + name: 'file2', + encryptMeta: '{"key":"value2"}', + deleted: false, + owner: 'genericUser', + }), + ); + + expect(filesService.find({ userId: 'genericUser' })).toHaveLength(1); + expect( + filesService.find({ userId: 'genericAdmin' }).length, + ).toBeGreaterThan(1); + }); + test.each([['update-group', null]])( 'update should modify a single attribute with groupId = $groupId', (newGroupId) => { diff --git a/packages/sync-server/src/app.js b/packages/sync-server/src/app.js index 6dbf3506d3e..80504f14d41 100644 --- a/packages/sync-server/src/app.js +++ b/packages/sync-server/src/app.js @@ -11,6 +11,8 @@ import * as syncApp from './app-sync.js'; import * as goCardlessApp from './app-gocardless/app-gocardless.js'; import * as simpleFinApp from './app-simplefin/app-simplefin.js'; import * as secretApp from './app-secrets.js'; +import * as adminApp from './app-admin.js'; +import * as openidApp from './app-openid.js'; const app = express(); @@ -48,6 +50,9 @@ app.use('/gocardless', goCardlessApp.handlers); app.use('/simplefin', simpleFinApp.handlers); app.use('/secret', secretApp.handlers); +app.use('/admin', adminApp.handlers); +app.use('/openid', openidApp.handlers); + app.get('/mode', (req, res) => { res.send(config.mode); }); @@ -83,5 +88,6 @@ export default async function run() { } else { app.listen(config.port, config.hostname); } + console.log('Listening on ' + config.hostname + ':' + config.port + '...'); } diff --git a/packages/sync-server/src/config-types.ts b/packages/sync-server/src/config-types.ts index 8be7ba49de2..778982d59a4 100644 --- a/packages/sync-server/src/config-types.ts +++ b/packages/sync-server/src/config-types.ts @@ -2,7 +2,7 @@ import { ServerOptions } from 'https'; export interface Config { mode: 'test' | 'development'; - loginMethod: 'password' | 'header'; + loginMethod: 'password' | 'header' | 'openid'; trustedProxies: string[]; dataDir: string; projectRoot: string; @@ -20,4 +20,19 @@ export interface Config { syncEncryptedFileSizeLimitMB: number; fileSizeLimitMB: number; }; + openId?: { + issuer: + | string + | { + name: string; + authorization_endpoint: string; + token_endpoint: string; + userinfo_endpoint: string; + }; + client_id: string; + client_secret: string; + server_hostname: string; + }; + multiuser: boolean; + token_expiration?: 'never' | 'openid-provider' | number; } diff --git a/packages/sync-server/src/load-config.js b/packages/sync-server/src/load-config.js index 19696d59df5..9c8ee34f90c 100644 --- a/packages/sync-server/src/load-config.js +++ b/packages/sync-server/src/load-config.js @@ -77,6 +77,8 @@ let defaultConfig = { fileSizeLimitMB: 20, }, projectRoot, + multiuser: false, + token_expiration: 'never', }; /** @type {import('./config-types.js').Config} */ @@ -105,6 +107,15 @@ const finalConfig = { loginMethod: process.env.ACTUAL_LOGIN_METHOD ? process.env.ACTUAL_LOGIN_METHOD.toLowerCase() : config.loginMethod, + multiuser: process.env.ACTUAL_MULTIUSER + ? (() => { + const value = process.env.ACTUAL_MULTIUSER.toLowerCase(); + if (!['true', 'false'].includes(value)) { + throw new Error('ACTUAL_MULTIUSER must be either "true" or "false"'); + } + return value === 'true'; + })() + : config.multiuser, trustedProxies: process.env.ACTUAL_TRUSTED_PROXIES ? process.env.ACTUAL_TRUSTED_PROXIES.split(',').map((q) => q.trim()) : config.trustedProxies, @@ -139,6 +150,55 @@ const finalConfig = { config.upload.fileSizeLimitMB, } : config.upload, + openId: (() => { + if ( + !process.env.ACTUAL_OPENID_DISCOVERY_URL && + !process.env.ACTUAL_OPENID_AUTHORIZATION_ENDPOINT + ) { + return config.openId; + } + const baseConfig = process.env.ACTUAL_OPENID_DISCOVERY_URL + ? { issuer: process.env.ACTUAL_OPENID_DISCOVERY_URL } + : { + ...(() => { + const required = { + authorization_endpoint: + process.env.ACTUAL_OPENID_AUTHORIZATION_ENDPOINT, + token_endpoint: process.env.ACTUAL_OPENID_TOKEN_ENDPOINT, + userinfo_endpoint: process.env.ACTUAL_OPENID_USERINFO_ENDPOINT, + }; + const missing = Object.entries(required) + .filter(([_, value]) => !value) + .map(([key]) => key); + if (missing.length > 0) { + throw new Error( + `Missing required OpenID configuration: ${missing.join(', ')}`, + ); + } + return {}; + })(), + issuer: { + name: process.env.ACTUAL_OPENID_PROVIDER_NAME, + authorization_endpoint: + process.env.ACTUAL_OPENID_AUTHORIZATION_ENDPOINT, + token_endpoint: process.env.ACTUAL_OPENID_TOKEN_ENDPOINT, + userinfo_endpoint: process.env.ACTUAL_OPENID_USERINFO_ENDPOINT, + }, + }; + return { + ...baseConfig, + client_id: + process.env.ACTUAL_OPENID_CLIENT_ID ?? config.openId?.client_id, + client_secret: + process.env.ACTUAL_OPENID_CLIENT_SECRET ?? config.openId?.client_secret, + server_hostname: + process.env.ACTUAL_OPENID_SERVER_HOSTNAME ?? + config.openId?.server_hostname, + }; + })(), + token_expiration: process.env.ACTUAL_TOKEN_EXPIRATION + ? process.env.ACTUAL_TOKEN_EXPIRATION + : config.token_expiration, }; debug(`using port ${finalConfig.port}`); debug(`using hostname ${finalConfig.hostname}`); diff --git a/packages/sync-server/src/scripts/reset-password.js b/packages/sync-server/src/scripts/reset-password.js index 26d5b163871..142269a9f7f 100644 --- a/packages/sync-server/src/scripts/reset-password.js +++ b/packages/sync-server/src/scripts/reset-password.js @@ -1,4 +1,5 @@ -import { needsBootstrap, bootstrap, changePassword } from '../account-db.js'; +import { bootstrap, needsBootstrap } from '../account-db.js'; +import { changePassword } from '../accounts/password.js'; import { promptPassword } from '../util/prompt.js'; if (needsBootstrap()) { @@ -6,31 +7,45 @@ if (needsBootstrap()) { 'It looks like you don’t have a password set yet. Let’s set one up now!', ); - promptPassword().then((password) => { - let { error } = bootstrap(password); + try { + const password = await promptPassword(); + const { error } = await bootstrap({ password }); if (error) { console.log('Error setting password:', error); console.log( 'Please report this as an issue: https://github.com/actualbudget/actual-server/issues', ); - } else { - console.log('Password set!'); + process.exit(1); } - }); + console.log('Password set!'); + } catch (err) { + console.log('Unexpected error:', err); + console.log( + 'Please report this as an issue: https://github.com/actualbudget/actual-server/issues', + ); + process.exit(1); + } } else { console.log('It looks like you already have a password set. Let’s reset it!'); - promptPassword().then((password) => { - let { error } = changePassword(password); + try { + const password = await promptPassword(); + const { error } = await changePassword(password); if (error) { console.log('Error changing password:', error); console.log( 'Please report this as an issue: https://github.com/actualbudget/actual-server/issues', ); - } else { - console.log('Password changed!'); - console.log( - 'Note: you will need to log in with the new password on any browsers or devices that are currently logged in.', - ); + process.exit(1); } - }); + console.log('Password changed!'); + console.log( + 'Note: you will need to log in with the new password on any browsers or devices that are currently logged in.', + ); + } catch (err) { + console.log('Unexpected error:', err); + console.log( + 'Please report this as an issue: https://github.com/actualbudget/actual-server/issues', + ); + process.exit(1); + } } diff --git a/packages/sync-server/src/services/user-service.js b/packages/sync-server/src/services/user-service.js new file mode 100644 index 00000000000..ee353780cab --- /dev/null +++ b/packages/sync-server/src/services/user-service.js @@ -0,0 +1,261 @@ +import getAccountDb from '../account-db.js'; + +export function getUserByUsername(userName) { + if (!userName || typeof userName !== 'string') { + return null; + } + const { id } = + getAccountDb().first('SELECT id FROM users WHERE user_name = ?', [ + userName, + ]) || {}; + return id || null; +} + +export function getUserById(userId) { + if (!userId) { + return null; + } + const { id } = + getAccountDb().first('SELECT * FROM users WHERE id = ?', [userId]) || {}; + return id || null; +} + +export function getFileById(fileId) { + if (!fileId) { + return null; + } + const { id } = + getAccountDb().first('SELECT * FROM files WHERE files.id = ?', [fileId]) || + {}; + return id || null; +} + +export function validateRole(roleId) { + const possibleRoles = ['BASIC', 'ADMIN']; + return possibleRoles.some((a) => a === roleId); +} + +export function getOwnerCount() { + const { ownerCount } = getAccountDb().first( + `SELECT count(*) as ownerCount FROM users WHERE users.user_name <> '' and users.owner = 1`, + ) || { ownerCount: 0 }; + return ownerCount; +} + +export function getOwnerId() { + const { id } = + getAccountDb().first( + `SELECT users.id FROM users WHERE users.user_name <> '' and users.owner = 1`, + ) || {}; + return id; +} + +export function getFileOwnerId(fileId) { + const { owner } = + getAccountDb().first(`SELECT files.owner FROM files WHERE files.id = ?`, [ + fileId, + ]) || {}; + return owner; +} + +export function getAllUsers() { + return getAccountDb().all( + `SELECT users.id, user_name as userName, display_name as displayName, enabled, ifnull(owner,0) as owner, role + FROM users + WHERE users.user_name <> ''`, + ); +} + +export function insertUser(userId, userName, displayName, enabled, role) { + getAccountDb().mutate( + 'INSERT INTO users (id, user_name, display_name, enabled, owner, role) VALUES (?, ?, ?, ?, 0, ?)', + [userId, userName, displayName, enabled, role], + ); +} + +export function updateUser(userId, userName, displayName, enabled) { + if (!userId || !userName) { + throw new Error('Invalid user parameters'); + } + try { + getAccountDb().mutate( + 'UPDATE users SET user_name = ?, display_name = ?, enabled = ? WHERE id = ?', + [userName, displayName, enabled, userId], + ); + } catch (error) { + throw new Error(`Failed to update user: ${error.message}`); + } +} + +export function updateUserWithRole( + userId, + userName, + displayName, + enabled, + roleId, +) { + getAccountDb().transaction(() => { + getAccountDb().mutate( + 'UPDATE users SET user_name = ?, display_name = ?, enabled = ?, role = ? WHERE id = ?', + [userName, displayName, enabled, roleId, userId], + ); + }); +} + +export function deleteUser(userId) { + return getAccountDb().mutate('DELETE FROM users WHERE id = ? and owner = 0', [ + userId, + ]).changes; +} +export function deleteUserAccess(userId) { + try { + return getAccountDb().mutate('DELETE FROM user_access WHERE user_id = ?', [ + userId, + ]).changes; + } catch (error) { + throw new Error(`Failed to delete user access: ${error.message}`); + } +} + +export function transferAllFilesFromUser(ownerId, oldUserId) { + if (!ownerId || !oldUserId) { + throw new Error('Invalid user IDs'); + } + try { + getAccountDb().transaction(() => { + const ownerExists = getUserById(ownerId); + if (!ownerExists) { + throw new Error('New owner not found'); + } + getAccountDb().mutate('UPDATE files set owner = ? WHERE owner = ?', [ + ownerId, + oldUserId, + ]); + }); + } catch (error) { + throw new Error(`Failed to transfer files: ${error.message}`); + } +} + +export function updateFileOwner(ownerId, fileId) { + if (!ownerId || !fileId) { + throw new Error('Invalid parameters'); + } + try { + const result = getAccountDb().mutate( + 'UPDATE files set owner = ? WHERE id = ?', + [ownerId, fileId], + ); + if (result.changes === 0) { + throw new Error('File not found'); + } + } catch (error) { + throw new Error(`Failed to update file owner: ${error.message}`); + } +} + +export function getUserAccess(fileId, userId, isAdmin) { + return getAccountDb().all( + `SELECT users.id as userId, user_name as userName, files.owner, display_name as displayName + FROM users + JOIN user_access ON user_access.user_id = users.id + JOIN files ON files.id = user_access.file_id + WHERE files.id = ? and (files.owner = ? OR 1 = ?)`, + [fileId, userId, isAdmin ? 1 : 0], + ); +} + +export function countUserAccess(fileId, userId) { + const { accessCount } = + getAccountDb().first( + `SELECT COUNT(*) as accessCount + FROM files + WHERE files.id = ? AND (files.owner = ? OR EXISTS ( + SELECT 1 FROM user_access + WHERE user_access.user_id = ? AND user_access.file_id = ?) + )`, + [fileId, userId, userId, fileId], + ) || {}; + + return accessCount || 0; +} + +export function checkFilePermission(fileId, userId) { + return ( + getAccountDb().first( + `SELECT 1 as granted + FROM files + WHERE files.id = ? and (files.owner = ?)`, + [fileId, userId], + ) || { granted: 0 } + ); +} + +export function addUserAccess(userId, fileId) { + if (!userId || !fileId) { + throw new Error('Invalid parameters'); + } + try { + const userExists = getUserById(userId); + const fileExists = getFileById(fileId); + if (!userExists || !fileExists) { + throw new Error('User or file not found'); + } + getAccountDb().mutate( + 'INSERT INTO user_access (user_id, file_id) VALUES (?, ?)', + [userId, fileId], + ); + } catch (error) { + if (error.message.includes('UNIQUE constraint')) { + throw new Error('Access already exists'); + } + throw new Error(`Failed to add user access: ${error.message}`); + } +} + +export function deleteUserAccessByFileId(userIds, fileId) { + if (!Array.isArray(userIds) || userIds.length === 0) { + throw new Error('The provided userIds must be a non-empty array.'); + } + + const CHUNK_SIZE = 999; + let totalChanges = 0; + + try { + getAccountDb().transaction(() => { + for (let i = 0; i < userIds.length; i += CHUNK_SIZE) { + const chunk = userIds.slice(i, i + CHUNK_SIZE); + const placeholders = chunk.map(() => '?').join(','); + + const sql = `DELETE FROM user_access WHERE user_id IN (${placeholders}) AND file_id = ?`; + + const result = getAccountDb().mutate(sql, [...chunk, fileId]); + totalChanges += result.changes; + } + }); + } catch (error) { + throw new Error(`Failed to delete user access: ${error.message}`); + } + + return totalChanges; +} + +export function getAllUserAccess(fileId) { + return getAccountDb().all( + `SELECT users.id as userId, user_name as userName, display_name as displayName, + CASE WHEN user_access.file_id IS NULL THEN 0 ELSE 1 END as haveAccess, + CASE WHEN files.id IS NULL THEN 0 ELSE 1 END as owner + FROM users + LEFT JOIN user_access ON user_access.file_id = ? and user_access.user_id = users.id + LEFT JOIN files ON files.id = ? and files.owner = users.id + WHERE users.enabled = 1 AND users.user_name <> ''`, + [fileId, fileId], + ); +} + +export function getOpenIDConfig() { + return ( + getAccountDb().first(`SELECT * FROM auth WHERE method = ?`, ['openid']) || + null + ); +} diff --git a/packages/sync-server/src/util/middlewares.js b/packages/sync-server/src/util/middlewares.js index 5bbec6946ad..f8d6b4f1897 100644 --- a/packages/sync-server/src/util/middlewares.js +++ b/packages/sync-server/src/util/middlewares.js @@ -1,4 +1,4 @@ -import validateUser from './validate-user.js'; +import validateSession from './validate-user.js'; import * as winston from 'winston'; import * as expressWinston from 'express-winston'; @@ -31,11 +31,13 @@ async function errorMiddleware(err, req, res, next) { * @param {import('express').Response} res * @param {import('express').NextFunction} next */ -const validateUserMiddleware = async (req, res, next) => { - let user = await validateUser(req, res); - if (!user) { +const validateSessionMiddleware = async (req, res, next) => { + let session = await validateSession(req, res); + if (!session) { return; } + + res.locals = session; next(); }; @@ -53,4 +55,4 @@ const requestLoggerMiddleware = expressWinston.logger({ ), }); -export { validateUserMiddleware, errorMiddleware, requestLoggerMiddleware }; +export { validateSessionMiddleware, errorMiddleware, requestLoggerMiddleware }; diff --git a/packages/sync-server/src/util/validate-user.js b/packages/sync-server/src/util/validate-user.js index 117fb779ba1..a84389e6f64 100644 --- a/packages/sync-server/src/util/validate-user.js +++ b/packages/sync-server/src/util/validate-user.js @@ -1,13 +1,16 @@ -import { getSession } from '../account-db.js'; import config from '../load-config.js'; import proxyaddr from 'proxy-addr'; import ipaddr from 'ipaddr.js'; +import { getSession } from '../account-db.js'; + +export const TOKEN_EXPIRATION_NEVER = -1; +const MS_PER_SECOND = 1000; /** * @param {import('express').Request} req * @param {import('express').Response} res */ -export default function validateUser(req, res) { +export default function validateSession(req, res) { let { token } = req.body || {}; if (!token) { @@ -26,6 +29,18 @@ export default function validateUser(req, res) { return null; } + if ( + session.expires_at !== TOKEN_EXPIRATION_NEVER && + session.expires_at * MS_PER_SECOND <= Date.now() + ) { + res.status(401); + res.send({ + status: 'error', + reason: 'token-expired', + }); + return null; + } + return session; } diff --git a/packages/sync-server/tsconfig.json b/packages/sync-server/tsconfig.json index 2a35116984d..cb09bd65f4b 100644 --- a/packages/sync-server/tsconfig.json +++ b/packages/sync-server/tsconfig.json @@ -17,5 +17,6 @@ "module": "node16", "outDir": "build" }, + "include": ["src/**/*.js", "types/global.d.ts"], "exclude": ["node_modules", "build", "./app-plaid.js", "coverage"], } diff --git a/packages/sync-server/upcoming-release-notes/479.md b/packages/sync-server/upcoming-release-notes/479.md new file mode 100644 index 00000000000..17fd70b70eb --- /dev/null +++ b/packages/sync-server/upcoming-release-notes/479.md @@ -0,0 +1,6 @@ +--- +category: Maintenance +authors: [rare-magma] +--- + +Updates the docker images base version and set node_env env variable to production diff --git a/packages/sync-server/upcoming-release-notes/484.md b/packages/sync-server/upcoming-release-notes/484.md new file mode 100644 index 00000000000..2d7c9e07c43 --- /dev/null +++ b/packages/sync-server/upcoming-release-notes/484.md @@ -0,0 +1,6 @@ +--- +category: Enhancements +authors: [matt-fidd] +--- + +Add support for `1822-DIREKT-HELADEF1822` transaction information diff --git a/packages/sync-server/upcoming-release-notes/485.md b/packages/sync-server/upcoming-release-notes/485.md new file mode 100644 index 00000000000..3fd1ff078df --- /dev/null +++ b/packages/sync-server/upcoming-release-notes/485.md @@ -0,0 +1,6 @@ +--- +category: Enhancements +authors: [psybers] +--- + +Check if SimpleFIN accessKey is in the correct format. diff --git a/packages/sync-server/upcoming-release-notes/490.md b/packages/sync-server/upcoming-release-notes/490.md new file mode 100644 index 00000000000..850e6140aea --- /dev/null +++ b/packages/sync-server/upcoming-release-notes/490.md @@ -0,0 +1,6 @@ +--- +category: Enhancements +authors: [dmednis] +--- + +Add support for "SWEDBANK_HABALV22" transaction date \ No newline at end of file diff --git a/packages/sync-server/upcoming-release-notes/493.md b/packages/sync-server/upcoming-release-notes/493.md new file mode 100644 index 00000000000..b8767a9a774 --- /dev/null +++ b/packages/sync-server/upcoming-release-notes/493.md @@ -0,0 +1,6 @@ +--- +category: Enhancements +authors: [matt-fidd] +--- + +GoCardless: `ING_PL_INGBPLPW` should prefer valueDate over bookingDate diff --git a/packages/sync-server/upcoming-release-notes/494.md b/packages/sync-server/upcoming-release-notes/494.md new file mode 100644 index 00000000000..9efdee05c2a --- /dev/null +++ b/packages/sync-server/upcoming-release-notes/494.md @@ -0,0 +1,6 @@ +--- +category: Bugfix +authors: [matt-fidd] +--- + +Prefer using the SimpleFin pending flag to set cleared status diff --git a/packages/sync-server/upcoming-release-notes/497.md b/packages/sync-server/upcoming-release-notes/497.md new file mode 100644 index 00000000000..0836318c8a0 --- /dev/null +++ b/packages/sync-server/upcoming-release-notes/497.md @@ -0,0 +1,6 @@ +--- +category: Enhancements +authors: [dmednis] +--- + +Improve support for "SWEDBANK_HABALV22" transaction date & enrich creditor name for pending transactions \ No newline at end of file diff --git a/packages/sync-server/upcoming-release-notes/498.md b/packages/sync-server/upcoming-release-notes/498.md new file mode 100644 index 00000000000..e1b8c807de7 --- /dev/null +++ b/packages/sync-server/upcoming-release-notes/498.md @@ -0,0 +1,6 @@ +--- +category: Features +authors: [apilat, lelemm] +--- + +Add support for authentication using OpenID Connect. diff --git a/packages/sync-server/upcoming-release-notes/504.md b/packages/sync-server/upcoming-release-notes/504.md new file mode 100644 index 00000000000..5f3364eba19 --- /dev/null +++ b/packages/sync-server/upcoming-release-notes/504.md @@ -0,0 +1,6 @@ +--- +category: Bugfix +authors: [matt-fidd] +--- + +Fix bug in batch SimpleFIN startDate logic From 53f10546325eb464b40e04637ed4c44762561ce7 Mon Sep 17 00:00:00 2001 From: Mike Clark <mclarkgb@gmail.com> Date: Sat, 23 Nov 2024 13:03:34 +0000 Subject: [PATCH 48/55] yarn lock --- yarn.lock | 34 ++++++++++++++++++++++++++++++++++ 1 file changed, 34 insertions(+) diff --git a/yarn.lock b/yarn.lock index 179845d7bdc..05f4e3875e9 100644 --- a/yarn.lock +++ b/yarn.lock @@ -7613,6 +7613,7 @@ __metadata: jws: "npm:^4.0.0" migrate: "npm:^2.0.1" nordigen-node: "npm:^1.4.0" + openid-client: "npm:^5.4.2" prettier: "npm:^2.8.3" supertest: "npm:^6.3.1" typescript: "npm:^4.9.5" @@ -15210,6 +15211,13 @@ __metadata: languageName: node linkType: hard +"jose@npm:^4.15.9": + version: 4.15.9 + resolution: "jose@npm:4.15.9" + checksum: 10/256234b6f85cdc080b1331f2d475bd58c8ccf459cb20f70ac5e4200b271bce10002b1c2f8e5b96dd975d83065ae5a586d52cdf89d28471d56de5d297992f9905 + languageName: node + linkType: hard + "js-tokens@npm:^3.0.0 || ^4.0.0, js-tokens@npm:^4.0.0": version: 4.0.0 resolution: "js-tokens@npm:4.0.0" @@ -17364,6 +17372,13 @@ __metadata: languageName: node linkType: hard +"object-hash@npm:^2.2.0": + version: 2.2.0 + resolution: "object-hash@npm:2.2.0" + checksum: 10/dee06b6271bf5769ae5f1a7386fdd52c1f18aae9fcb0b8d4bb1232f2d743d06cb5b662be42378b60a1c11829f96f3f86834a16bbaa57a085763295fff8b93e27 + languageName: node + linkType: hard + "object-inspect@npm:^1.13.1": version: 1.13.2 resolution: "object-inspect@npm:1.13.2" @@ -17445,6 +17460,13 @@ __metadata: languageName: node linkType: hard +"oidc-token-hash@npm:^5.0.3": + version: 5.0.3 + resolution: "oidc-token-hash@npm:5.0.3" + checksum: 10/35fa19aea9ff2c509029ec569d74b778c8a215b92bd5e6e9bc4ebbd7ab035f44304ff02430a6397c3fb7c1d15ebfa467807ca0bcd31d06ba610b47798287d303 + languageName: node + linkType: hard + "on-finished@npm:2.4.1": version: 2.4.1 resolution: "on-finished@npm:2.4.1" @@ -17526,6 +17548,18 @@ __metadata: languageName: node linkType: hard +"openid-client@npm:^5.4.2": + version: 5.7.1 + resolution: "openid-client@npm:5.7.1" + dependencies: + jose: "npm:^4.15.9" + lru-cache: "npm:^6.0.0" + object-hash: "npm:^2.2.0" + oidc-token-hash: "npm:^5.0.3" + checksum: 10/188a875ab1824010bde85b6755f31401d4b0bcf6edffe5f149b1e67fc886c692658121c0c3cc04db84be33138c0e9e2e7d829e6997adf489f23a32ea7e745151 + languageName: node + linkType: hard + "optionator@npm:^0.8.1": version: 0.8.3 resolution: "optionator@npm:0.8.3" From c2aad5037fd8747ea74a738add0269add1fab5a9 Mon Sep 17 00:00:00 2001 From: Mike Clark <mclarkgb@gmail.com> Date: Fri, 6 Dec 2024 20:19:11 +0000 Subject: [PATCH 49/55] updated server --- packages/sync-server/.yarnrc.yml | 3 + .../migrations/1719409568000-multiuser.js | 27 +- packages/sync-server/package.json | 4 +- packages/sync-server/src/account-db.js | 4 +- packages/sync-server/src/accounts/openid.js | 3 +- packages/sync-server/src/app-account.js | 3 +- .../src/app-gocardless/bank-factory.js | 4 +- .../banks/HANSEATIC_HSTBDEHH.js | 10 + packages/sync-server/yarn.lock | 6481 +++++++++++++++++ 9 files changed, 6524 insertions(+), 15 deletions(-) create mode 100644 packages/sync-server/.yarnrc.yml create mode 100644 packages/sync-server/src/app-gocardless/banks/HANSEATIC_HSTBDEHH.js create mode 100644 packages/sync-server/yarn.lock diff --git a/packages/sync-server/.yarnrc.yml b/packages/sync-server/.yarnrc.yml new file mode 100644 index 00000000000..fd5296c36ec --- /dev/null +++ b/packages/sync-server/.yarnrc.yml @@ -0,0 +1,3 @@ +nodeLinker: node-modules + +yarnPath: .yarn/releases/yarn-4.3.1.cjs diff --git a/packages/sync-server/migrations/1719409568000-multiuser.js b/packages/sync-server/migrations/1719409568000-multiuser.js index 345da8ebd9a..228bcdc89da 100644 --- a/packages/sync-server/migrations/1719409568000-multiuser.js +++ b/packages/sync-server/migrations/1719409568000-multiuser.js @@ -1,10 +1,12 @@ import getAccountDb from '../src/account-db.js'; +import * as uuid from 'uuid'; export const up = async function () { - await getAccountDb().exec( - ` - BEGIN TRANSACTION; - + const accountDb = getAccountDb(); + + accountDb.transaction(() => { + accountDb.exec( + ` CREATE TABLE users (id TEXT PRIMARY KEY, user_name TEXT, @@ -24,8 +26,6 @@ export const up = async function () { ALTER TABLE files ADD COLUMN owner TEXT; - DELETE FROM sessions; - ALTER TABLE sessions ADD COLUMN expires_at INTEGER; @@ -34,9 +34,20 @@ export const up = async function () { ALTER TABLE sessions ADD COLUMN auth_method TEXT; - COMMIT; `, - ); + ); + + const userId = uuid.v4(); + accountDb.mutate( + 'INSERT INTO users (id, user_name, display_name, enabled, owner, role) VALUES (?, ?, ?, 1, 1, ?)', + [userId, '', '', 'ADMIN'], + ); + + accountDb.mutate( + 'UPDATE sessions SET user_id = ?, expires_at = ?, auth_method = ? WHERE auth_method IS NULL', + [userId, -1, 'password'], + ); + }); }; export const down = async function () { diff --git a/packages/sync-server/package.json b/packages/sync-server/package.json index 46e1018c6c4..db910718278 100644 --- a/packages/sync-server/package.json +++ b/packages/sync-server/package.json @@ -1,6 +1,6 @@ { "name": "actual-sync", - "version": "24.11.0", + "version": "24.12.0", "license": "MIT", "description": "actual syncing server", "type": "module", @@ -21,7 +21,7 @@ }, "dependencies": { "@actual-app/crdt": "2.1.0", - "@actual-app/web": "24.11.0", + "@actual-app/web": "24.12.0", "bcrypt": "^5.1.1", "better-sqlite3": "^9.6.0", "body-parser": "^1.20.3", diff --git a/packages/sync-server/src/account-db.js b/packages/sync-server/src/account-db.js index aa6678fd230..c8c30bf0b6d 100644 --- a/packages/sync-server/src/account-db.js +++ b/packages/sync-server/src/account-db.js @@ -53,7 +53,9 @@ export function getLoginMethod(req) { return req.body.loginMethod; } - return config.loginMethod || 'password'; + const activeMethod = getActiveLoginMethod(); + + return config.loginMethod || activeMethod || 'password'; } export async function bootstrap(loginSettings) { diff --git a/packages/sync-server/src/accounts/openid.js b/packages/sync-server/src/accounts/openid.js index 784a6e5b38c..18169c5950e 100644 --- a/packages/sync-server/src/accounts/openid.js +++ b/packages/sync-server/src/accounts/openid.js @@ -304,8 +304,7 @@ export function isValidRedirectUrl(url) { const redirectUrl = new URL(url); const serverUrl = new URL(serverHostname); - // Compare origin (protocol + hostname + port) - if (redirectUrl.origin === serverUrl.origin) { + if (redirectUrl.hostname === serverUrl.hostname) { return true; } else { return false; diff --git a/packages/sync-server/src/app-account.js b/packages/sync-server/src/app-account.js index 057c97d060a..d1867d2a490 100644 --- a/packages/sync-server/src/app-account.js +++ b/packages/sync-server/src/app-account.js @@ -33,7 +33,8 @@ app.get('/needs-bootstrap', (req, res) => { status: 'ok', data: { bootstrapped: !needsBootstrap(), - loginMethods: listLoginMethods(), + loginMethod: getLoginMethod(), + availableLoginMethods: listLoginMethods(), multiuser: getActiveLoginMethod() === 'openid', }, }); diff --git a/packages/sync-server/src/app-gocardless/bank-factory.js b/packages/sync-server/src/app-gocardless/bank-factory.js index a38afa3bc45..aaa4f9304d9 100644 --- a/packages/sync-server/src/app-gocardless/bank-factory.js +++ b/packages/sync-server/src/app-gocardless/bank-factory.js @@ -9,6 +9,7 @@ import CBCcregbebb from './banks/cbc_cregbebb.js'; import DanskeBankDabNO22 from './banks/danskebank-dabno22.js'; import EasybankBawaatww from './banks/easybank-bawaatww.js'; import Fortuneo from './banks/FORTUNEO_FTNOFRP1XXX.js'; +import HanseaticBank from './banks/HANSEATIC_HSTBDEHH.js'; import IngIngbrobu from './banks/ing-ingbrobu.js'; import IngIngddeff from './banks/ing-ingddeff.js'; import IngPlIngbplpw from './banks/ing-pl-ingbplpw.js'; @@ -41,6 +42,7 @@ export const banks = [ DanskeBankDabNO22, EasybankBawaatww, Fortuneo, + HanseaticBank, IngIngbrobu, IngIngddeff, IngPlIngbplpw, @@ -80,7 +82,7 @@ export const BANKS_WITH_LIMITED_HISTORY = [ 'FINECO_FEBIITM2XXX', 'FINECO_UK_FEBIITM2XXX', 'HYPE_BUSINESS_HYEEIT22', - 'HYPE_HYEEIT2', + 'HYPE_HYEEIT22', 'ILLIMITY_ITTPIT2M', 'INDUSTRA_MULTLV2X', 'JEKYLL_JEYKLL002', diff --git a/packages/sync-server/src/app-gocardless/banks/HANSEATIC_HSTBDEHH.js b/packages/sync-server/src/app-gocardless/banks/HANSEATIC_HSTBDEHH.js new file mode 100644 index 00000000000..143f92bf496 --- /dev/null +++ b/packages/sync-server/src/app-gocardless/banks/HANSEATIC_HSTBDEHH.js @@ -0,0 +1,10 @@ +import Fallback from './integration-bank.js'; + +/** @type {import('./bank.interface.js').IBank} */ +export default { + ...Fallback, + + institutionIds: ['HANSEATIC_HSTBDEHH'], + + accessValidForDays: 89, +}; diff --git a/packages/sync-server/yarn.lock b/packages/sync-server/yarn.lock new file mode 100644 index 00000000000..062e31e1d2f --- /dev/null +++ b/packages/sync-server/yarn.lock @@ -0,0 +1,6481 @@ +# This file is generated by running "yarn install" inside your project. +# Manual changes might be lost - proceed with caution! + +__metadata: + version: 8 + cacheKey: 10c0 + +"@actual-app/crdt@npm:2.1.0": + version: 2.1.0 + resolution: "@actual-app/crdt@npm:2.1.0" + dependencies: + google-protobuf: "npm:^3.12.0-rc.1" + murmurhash: "npm:^2.0.1" + uuid: "npm:^9.0.0" + checksum: 10c0/131050eb42218229eebe60a954ee55275380cff3b139a08b34e25f6056b621033f28a231ce6cc59022fdc80d475500fe70e5f134f9dc3250e37516e679922d5c + languageName: node + linkType: hard + +"@actual-app/web@npm:24.12.0": + version: 24.12.0 + resolution: "@actual-app/web@npm:24.12.0" + checksum: 10c0/865fd5898e8da6347759a65d557047b80cd0c4162601fd4f57eccd1d84289d84c9f2fe563a4b4547dc2f67353042fb94a6e9c0cb13cacfda0547f84aaba73ef6 + languageName: node + linkType: hard + +"@ampproject/remapping@npm:^2.2.0": + version: 2.3.0 + resolution: "@ampproject/remapping@npm:2.3.0" + dependencies: + "@jridgewell/gen-mapping": "npm:^0.3.5" + "@jridgewell/trace-mapping": "npm:^0.3.24" + checksum: 10c0/81d63cca5443e0f0c72ae18b544cc28c7c0ec2cea46e7cb888bb0e0f411a1191d0d6b7af798d54e30777d8d1488b2ec0732aac2be342d3d7d3ffd271c6f489ed + languageName: node + linkType: hard + +"@babel/code-frame@npm:^7.0.0, @babel/code-frame@npm:^7.12.13, @babel/code-frame@npm:^7.24.7": + version: 7.24.7 + resolution: "@babel/code-frame@npm:7.24.7" + dependencies: + "@babel/highlight": "npm:^7.24.7" + picocolors: "npm:^1.0.0" + checksum: 10c0/ab0af539473a9f5aeaac7047e377cb4f4edd255a81d84a76058595f8540784cc3fbe8acf73f1e073981104562490aabfb23008cd66dc677a456a4ed5390fdde6 + languageName: node + linkType: hard + +"@babel/compat-data@npm:^7.24.7": + version: 7.24.7 + resolution: "@babel/compat-data@npm:7.24.7" + checksum: 10c0/dcd93a5632b04536498fbe2be5af1057f635fd7f7090483d8e797878559037e5130b26862ceb359acbae93ed27e076d395ddb4663db6b28a665756ffd02d324f + languageName: node + linkType: hard + +"@babel/core@npm:^7.11.6, @babel/core@npm:^7.12.3, @babel/core@npm:^7.23.9": + version: 7.24.7 + resolution: "@babel/core@npm:7.24.7" + dependencies: + "@ampproject/remapping": "npm:^2.2.0" + "@babel/code-frame": "npm:^7.24.7" + "@babel/generator": "npm:^7.24.7" + "@babel/helper-compilation-targets": "npm:^7.24.7" + "@babel/helper-module-transforms": "npm:^7.24.7" + "@babel/helpers": "npm:^7.24.7" + "@babel/parser": "npm:^7.24.7" + "@babel/template": "npm:^7.24.7" + "@babel/traverse": "npm:^7.24.7" + "@babel/types": "npm:^7.24.7" + convert-source-map: "npm:^2.0.0" + debug: "npm:^4.1.0" + gensync: "npm:^1.0.0-beta.2" + json5: "npm:^2.2.3" + semver: "npm:^6.3.1" + checksum: 10c0/4004ba454d3c20a46ea66264e06c15b82e9f6bdc35f88819907d24620da70dbf896abac1cb4cc4b6bb8642969e45f4d808497c9054a1388a386cf8c12e9b9e0d + languageName: node + linkType: hard + +"@babel/generator@npm:^7.24.7, @babel/generator@npm:^7.7.2": + version: 7.24.7 + resolution: "@babel/generator@npm:7.24.7" + dependencies: + "@babel/types": "npm:^7.24.7" + "@jridgewell/gen-mapping": "npm:^0.3.5" + "@jridgewell/trace-mapping": "npm:^0.3.25" + jsesc: "npm:^2.5.1" + checksum: 10c0/06b1f3350baf527a3309e50ffd7065f7aee04dd06e1e7db794ddfde7fe9d81f28df64edd587173f8f9295496a7ddb74b9a185d4bf4de7bb619e6d4ec45c8fd35 + languageName: node + linkType: hard + +"@babel/helper-annotate-as-pure@npm:^7.24.7": + version: 7.24.7 + resolution: "@babel/helper-annotate-as-pure@npm:7.24.7" + dependencies: + "@babel/types": "npm:^7.24.7" + checksum: 10c0/4679f7df4dffd5b3e26083ae65228116c3da34c3fff2c11ae11b259a61baec440f51e30fd236f7a0435b9d471acd93d0bc5a95df8213cbf02b1e083503d81b9a + languageName: node + linkType: hard + +"@babel/helper-compilation-targets@npm:^7.24.7": + version: 7.24.7 + resolution: "@babel/helper-compilation-targets@npm:7.24.7" + dependencies: + "@babel/compat-data": "npm:^7.24.7" + "@babel/helper-validator-option": "npm:^7.24.7" + browserslist: "npm:^4.22.2" + lru-cache: "npm:^5.1.1" + semver: "npm:^6.3.1" + checksum: 10c0/1d580a9bcacefe65e6bf02ba1dafd7ab278269fef45b5e281d8354d95c53031e019890464e7f9351898c01502dd2e633184eb0bcda49ed2ecd538675ce310f51 + languageName: node + linkType: hard + +"@babel/helper-create-class-features-plugin@npm:^7.24.7": + version: 7.24.7 + resolution: "@babel/helper-create-class-features-plugin@npm:7.24.7" + dependencies: + "@babel/helper-annotate-as-pure": "npm:^7.24.7" + "@babel/helper-environment-visitor": "npm:^7.24.7" + "@babel/helper-function-name": "npm:^7.24.7" + "@babel/helper-member-expression-to-functions": "npm:^7.24.7" + "@babel/helper-optimise-call-expression": "npm:^7.24.7" + "@babel/helper-replace-supers": "npm:^7.24.7" + "@babel/helper-skip-transparent-expression-wrappers": "npm:^7.24.7" + "@babel/helper-split-export-declaration": "npm:^7.24.7" + semver: "npm:^6.3.1" + peerDependencies: + "@babel/core": ^7.0.0 + checksum: 10c0/6b7b47d70b41c00f39f86790cff67acf2bce0289d52a7c182b28e797f4e0e6d69027e3d06eccf1d54dddc2e5dde1df663bb1932437e5f447aeb8635d8d64a6ab + languageName: node + linkType: hard + +"@babel/helper-environment-visitor@npm:^7.24.7": + version: 7.24.7 + resolution: "@babel/helper-environment-visitor@npm:7.24.7" + dependencies: + "@babel/types": "npm:^7.24.7" + checksum: 10c0/36ece78882b5960e2d26abf13cf15ff5689bf7c325b10a2895a74a499e712de0d305f8d78bb382dd3c05cfba7e47ec98fe28aab5674243e0625cd38438dd0b2d + languageName: node + linkType: hard + +"@babel/helper-function-name@npm:^7.24.7": + version: 7.24.7 + resolution: "@babel/helper-function-name@npm:7.24.7" + dependencies: + "@babel/template": "npm:^7.24.7" + "@babel/types": "npm:^7.24.7" + checksum: 10c0/e5e41e6cf86bd0f8bf272cbb6e7c5ee0f3e9660414174435a46653efba4f2479ce03ce04abff2aa2ef9359cf057c79c06cb7b134a565ad9c0e8a50dcdc3b43c4 + languageName: node + linkType: hard + +"@babel/helper-hoist-variables@npm:^7.24.7": + version: 7.24.7 + resolution: "@babel/helper-hoist-variables@npm:7.24.7" + dependencies: + "@babel/types": "npm:^7.24.7" + checksum: 10c0/19ee37563bbd1219f9d98991ad0e9abef77803ee5945fd85aa7aa62a67c69efca9a801696a1b58dda27f211e878b3327789e6fd2a6f6c725ccefe36774b5ce95 + languageName: node + linkType: hard + +"@babel/helper-member-expression-to-functions@npm:^7.24.7": + version: 7.24.7 + resolution: "@babel/helper-member-expression-to-functions@npm:7.24.7" + dependencies: + "@babel/traverse": "npm:^7.24.7" + "@babel/types": "npm:^7.24.7" + checksum: 10c0/9638c1d33cf6aba028461ccd3db6061c76ff863ca0d5013dd9a088bf841f2f77c46956493f9da18355c16759449d23b74cc1de4da357ade5c5c34c858f840f0a + languageName: node + linkType: hard + +"@babel/helper-module-imports@npm:^7.24.7": + version: 7.24.7 + resolution: "@babel/helper-module-imports@npm:7.24.7" + dependencies: + "@babel/traverse": "npm:^7.24.7" + "@babel/types": "npm:^7.24.7" + checksum: 10c0/97c57db6c3eeaea31564286e328a9fb52b0313c5cfcc7eee4bc226aebcf0418ea5b6fe78673c0e4a774512ec6c86e309d0f326e99d2b37bfc16a25a032498af0 + languageName: node + linkType: hard + +"@babel/helper-module-transforms@npm:^7.24.7": + version: 7.24.7 + resolution: "@babel/helper-module-transforms@npm:7.24.7" + dependencies: + "@babel/helper-environment-visitor": "npm:^7.24.7" + "@babel/helper-module-imports": "npm:^7.24.7" + "@babel/helper-simple-access": "npm:^7.24.7" + "@babel/helper-split-export-declaration": "npm:^7.24.7" + "@babel/helper-validator-identifier": "npm:^7.24.7" + peerDependencies: + "@babel/core": ^7.0.0 + checksum: 10c0/4f311755fcc3b4cbdb689386309cdb349cf0575a938f0b9ab5d678e1a81bbb265aa34ad93174838245f2ac7ff6d5ddbd0104638a75e4e961958ed514355687b6 + languageName: node + linkType: hard + +"@babel/helper-optimise-call-expression@npm:^7.24.7": + version: 7.24.7 + resolution: "@babel/helper-optimise-call-expression@npm:7.24.7" + dependencies: + "@babel/types": "npm:^7.24.7" + checksum: 10c0/ca6a9884705dea5c95a8b3ce132d1e3f2ae951ff74987d400d1d9c215dae9c0f9e29924d8f8e131e116533d182675bc261927be72f6a9a2968eaeeaa51eb1d0f + languageName: node + linkType: hard + +"@babel/helper-plugin-utils@npm:^7.0.0, @babel/helper-plugin-utils@npm:^7.10.4, @babel/helper-plugin-utils@npm:^7.12.13, @babel/helper-plugin-utils@npm:^7.14.5, @babel/helper-plugin-utils@npm:^7.24.7, @babel/helper-plugin-utils@npm:^7.8.0": + version: 7.24.7 + resolution: "@babel/helper-plugin-utils@npm:7.24.7" + checksum: 10c0/c3d38cd9b3520757bb4a279255cc3f956fc0ac1c193964bd0816ebd5c86e30710be8e35252227e0c9d9e0f4f56d9b5f916537f2bc588084b0988b4787a967d31 + languageName: node + linkType: hard + +"@babel/helper-replace-supers@npm:^7.24.7": + version: 7.24.7 + resolution: "@babel/helper-replace-supers@npm:7.24.7" + dependencies: + "@babel/helper-environment-visitor": "npm:^7.24.7" + "@babel/helper-member-expression-to-functions": "npm:^7.24.7" + "@babel/helper-optimise-call-expression": "npm:^7.24.7" + peerDependencies: + "@babel/core": ^7.0.0 + checksum: 10c0/0e133bb03371dee78e519c334a09c08e1493103a239d9628db0132dfaac3fc16380479ca3c590d278a9b71b624030a338c18ebbfe6d430ebb2e4653775c4b3e3 + languageName: node + linkType: hard + +"@babel/helper-simple-access@npm:^7.24.7": + version: 7.24.7 + resolution: "@babel/helper-simple-access@npm:7.24.7" + dependencies: + "@babel/traverse": "npm:^7.24.7" + "@babel/types": "npm:^7.24.7" + checksum: 10c0/7230e419d59a85f93153415100a5faff23c133d7442c19e0cd070da1784d13cd29096ee6c5a5761065c44e8164f9f80e3a518c41a0256df39e38f7ad6744fed7 + languageName: node + linkType: hard + +"@babel/helper-skip-transparent-expression-wrappers@npm:^7.24.7": + version: 7.24.7 + resolution: "@babel/helper-skip-transparent-expression-wrappers@npm:7.24.7" + dependencies: + "@babel/traverse": "npm:^7.24.7" + "@babel/types": "npm:^7.24.7" + checksum: 10c0/e3a9b8ac9c262ac976a1bcb5fe59694db5e6f0b4f9e7bdba5c7693b8b5e28113c23bdaa60fe8d3ec32a337091b67720b2053bcb3d5655f5406536c3d0584242b + languageName: node + linkType: hard + +"@babel/helper-split-export-declaration@npm:^7.24.7": + version: 7.24.7 + resolution: "@babel/helper-split-export-declaration@npm:7.24.7" + dependencies: + "@babel/types": "npm:^7.24.7" + checksum: 10c0/0254577d7086bf09b01bbde98f731d4fcf4b7c3fa9634fdb87929801307c1f6202a1352e3faa5492450fa8da4420542d44de604daf540704ff349594a78184f6 + languageName: node + linkType: hard + +"@babel/helper-string-parser@npm:^7.24.7": + version: 7.24.7 + resolution: "@babel/helper-string-parser@npm:7.24.7" + checksum: 10c0/47840c7004e735f3dc93939c77b099bb41a64bf3dda0cae62f60e6f74a5ff80b63e9b7cf77b5ec25a324516381fc994e1f62f922533236a8e3a6af57decb5e1e + languageName: node + linkType: hard + +"@babel/helper-validator-identifier@npm:^7.24.7": + version: 7.24.7 + resolution: "@babel/helper-validator-identifier@npm:7.24.7" + checksum: 10c0/87ad608694c9477814093ed5b5c080c2e06d44cb1924ae8320474a74415241223cc2a725eea2640dd783ff1e3390e5f95eede978bc540e870053152e58f1d651 + languageName: node + linkType: hard + +"@babel/helper-validator-option@npm:^7.24.7": + version: 7.24.7 + resolution: "@babel/helper-validator-option@npm:7.24.7" + checksum: 10c0/21aea2b7bc5cc8ddfb828741d5c8116a84cbc35b4a3184ec53124f08e09746f1f67a6f9217850188995ca86059a7942e36d8965a6730784901def777b7e8a436 + languageName: node + linkType: hard + +"@babel/helpers@npm:^7.24.7": + version: 7.24.7 + resolution: "@babel/helpers@npm:7.24.7" + dependencies: + "@babel/template": "npm:^7.24.7" + "@babel/types": "npm:^7.24.7" + checksum: 10c0/aa8e230f6668773e17e141dbcab63e935c514b4b0bf1fed04d2eaefda17df68e16b61a56573f7f1d4d1e605ce6cc162b5f7e9fdf159fde1fd9b77c920ae47d27 + languageName: node + linkType: hard + +"@babel/highlight@npm:^7.24.7": + version: 7.24.7 + resolution: "@babel/highlight@npm:7.24.7" + dependencies: + "@babel/helper-validator-identifier": "npm:^7.24.7" + chalk: "npm:^2.4.2" + js-tokens: "npm:^4.0.0" + picocolors: "npm:^1.0.0" + checksum: 10c0/674334c571d2bb9d1c89bdd87566383f59231e16bcdcf5bb7835babdf03c9ae585ca0887a7b25bdf78f303984af028df52831c7989fecebb5101cc132da9393a + languageName: node + linkType: hard + +"@babel/parser@npm:^7.1.0, @babel/parser@npm:^7.14.7, @babel/parser@npm:^7.20.7, @babel/parser@npm:^7.23.9, @babel/parser@npm:^7.24.7": + version: 7.24.7 + resolution: "@babel/parser@npm:7.24.7" + bin: + parser: ./bin/babel-parser.js + checksum: 10c0/8b244756872185a1c6f14b979b3535e682ff08cb5a2a5fd97cc36c017c7ef431ba76439e95e419d43000c5b07720495b00cf29a7f0d9a483643d08802b58819b + languageName: node + linkType: hard + +"@babel/plugin-syntax-async-generators@npm:^7.8.4": + version: 7.8.4 + resolution: "@babel/plugin-syntax-async-generators@npm:7.8.4" + dependencies: + "@babel/helper-plugin-utils": "npm:^7.8.0" + peerDependencies: + "@babel/core": ^7.0.0-0 + checksum: 10c0/d13efb282838481348c71073b6be6245b35d4f2f964a8f71e4174f235009f929ef7613df25f8d2338e2d3e44bc4265a9f8638c6aaa136d7a61fe95985f9725c8 + languageName: node + linkType: hard + +"@babel/plugin-syntax-bigint@npm:^7.8.3": + version: 7.8.3 + resolution: "@babel/plugin-syntax-bigint@npm:7.8.3" + dependencies: + "@babel/helper-plugin-utils": "npm:^7.8.0" + peerDependencies: + "@babel/core": ^7.0.0-0 + checksum: 10c0/686891b81af2bc74c39013655da368a480f17dd237bf9fbc32048e5865cb706d5a8f65438030da535b332b1d6b22feba336da8fa931f663b6b34e13147d12dde + languageName: node + linkType: hard + +"@babel/plugin-syntax-class-properties@npm:^7.8.3": + version: 7.12.13 + resolution: "@babel/plugin-syntax-class-properties@npm:7.12.13" + dependencies: + "@babel/helper-plugin-utils": "npm:^7.12.13" + peerDependencies: + "@babel/core": ^7.0.0-0 + checksum: 10c0/95168fa186416195280b1264fb18afcdcdcea780b3515537b766cb90de6ce042d42dd6a204a39002f794ae5845b02afb0fd4861a3308a861204a55e68310a120 + languageName: node + linkType: hard + +"@babel/plugin-syntax-import-meta@npm:^7.8.3": + version: 7.10.4 + resolution: "@babel/plugin-syntax-import-meta@npm:7.10.4" + dependencies: + "@babel/helper-plugin-utils": "npm:^7.10.4" + peerDependencies: + "@babel/core": ^7.0.0-0 + checksum: 10c0/0b08b5e4c3128523d8e346f8cfc86824f0da2697b1be12d71af50a31aff7a56ceb873ed28779121051475010c28d6146a6bfea8518b150b71eeb4e46190172ee + languageName: node + linkType: hard + +"@babel/plugin-syntax-json-strings@npm:^7.8.3": + version: 7.8.3 + resolution: "@babel/plugin-syntax-json-strings@npm:7.8.3" + dependencies: + "@babel/helper-plugin-utils": "npm:^7.8.0" + peerDependencies: + "@babel/core": ^7.0.0-0 + checksum: 10c0/e98f31b2ec406c57757d115aac81d0336e8434101c224edd9a5c93cefa53faf63eacc69f3138960c8b25401315af03df37f68d316c151c4b933136716ed6906e + languageName: node + linkType: hard + +"@babel/plugin-syntax-jsx@npm:^7.24.7, @babel/plugin-syntax-jsx@npm:^7.7.2": + version: 7.24.7 + resolution: "@babel/plugin-syntax-jsx@npm:7.24.7" + dependencies: + "@babel/helper-plugin-utils": "npm:^7.24.7" + peerDependencies: + "@babel/core": ^7.0.0-0 + checksum: 10c0/f44d927a9ae8d5ef016ff5b450e1671e56629ddc12e56b938e41fd46e141170d9dfc9a53d6cb2b9a20a7dd266a938885e6a3981c60c052a2e1daed602ac80e51 + languageName: node + linkType: hard + +"@babel/plugin-syntax-logical-assignment-operators@npm:^7.8.3": + version: 7.10.4 + resolution: "@babel/plugin-syntax-logical-assignment-operators@npm:7.10.4" + dependencies: + "@babel/helper-plugin-utils": "npm:^7.10.4" + peerDependencies: + "@babel/core": ^7.0.0-0 + checksum: 10c0/2594cfbe29411ad5bc2ad4058de7b2f6a8c5b86eda525a993959438615479e59c012c14aec979e538d60a584a1a799b60d1b8942c3b18468cb9d99b8fd34cd0b + languageName: node + linkType: hard + +"@babel/plugin-syntax-nullish-coalescing-operator@npm:^7.8.3": + version: 7.8.3 + resolution: "@babel/plugin-syntax-nullish-coalescing-operator@npm:7.8.3" + dependencies: + "@babel/helper-plugin-utils": "npm:^7.8.0" + peerDependencies: + "@babel/core": ^7.0.0-0 + checksum: 10c0/2024fbb1162899094cfc81152449b12bd0cc7053c6d4bda8ac2852545c87d0a851b1b72ed9560673cbf3ef6248257262c3c04aabf73117215c1b9cc7dd2542ce + languageName: node + linkType: hard + +"@babel/plugin-syntax-numeric-separator@npm:^7.8.3": + version: 7.10.4 + resolution: "@babel/plugin-syntax-numeric-separator@npm:7.10.4" + dependencies: + "@babel/helper-plugin-utils": "npm:^7.10.4" + peerDependencies: + "@babel/core": ^7.0.0-0 + checksum: 10c0/c55a82b3113480942c6aa2fcbe976ff9caa74b7b1109ff4369641dfbc88d1da348aceb3c31b6ed311c84d1e7c479440b961906c735d0ab494f688bf2fd5b9bb9 + languageName: node + linkType: hard + +"@babel/plugin-syntax-object-rest-spread@npm:^7.8.3": + version: 7.8.3 + resolution: "@babel/plugin-syntax-object-rest-spread@npm:7.8.3" + dependencies: + "@babel/helper-plugin-utils": "npm:^7.8.0" + peerDependencies: + "@babel/core": ^7.0.0-0 + checksum: 10c0/ee1eab52ea6437e3101a0a7018b0da698545230015fc8ab129d292980ec6dff94d265e9e90070e8ae5fed42f08f1622c14c94552c77bcac784b37f503a82ff26 + languageName: node + linkType: hard + +"@babel/plugin-syntax-optional-catch-binding@npm:^7.8.3": + version: 7.8.3 + resolution: "@babel/plugin-syntax-optional-catch-binding@npm:7.8.3" + dependencies: + "@babel/helper-plugin-utils": "npm:^7.8.0" + peerDependencies: + "@babel/core": ^7.0.0-0 + checksum: 10c0/27e2493ab67a8ea6d693af1287f7e9acec206d1213ff107a928e85e173741e1d594196f99fec50e9dde404b09164f39dec5864c767212154ffe1caa6af0bc5af + languageName: node + linkType: hard + +"@babel/plugin-syntax-optional-chaining@npm:^7.8.3": + version: 7.8.3 + resolution: "@babel/plugin-syntax-optional-chaining@npm:7.8.3" + dependencies: + "@babel/helper-plugin-utils": "npm:^7.8.0" + peerDependencies: + "@babel/core": ^7.0.0-0 + checksum: 10c0/46edddf2faa6ebf94147b8e8540dfc60a5ab718e2de4d01b2c0bdf250a4d642c2bd47cbcbb739febcb2bf75514dbcefad3c52208787994b8d0f8822490f55e81 + languageName: node + linkType: hard + +"@babel/plugin-syntax-top-level-await@npm:^7.8.3": + version: 7.14.5 + resolution: "@babel/plugin-syntax-top-level-await@npm:7.14.5" + dependencies: + "@babel/helper-plugin-utils": "npm:^7.14.5" + peerDependencies: + "@babel/core": ^7.0.0-0 + checksum: 10c0/14bf6e65d5bc1231ffa9def5f0ef30b19b51c218fcecaa78cd1bdf7939dfdf23f90336080b7f5196916368e399934ce5d581492d8292b46a2fb569d8b2da106f + languageName: node + linkType: hard + +"@babel/plugin-syntax-typescript@npm:^7.24.7, @babel/plugin-syntax-typescript@npm:^7.7.2": + version: 7.24.7 + resolution: "@babel/plugin-syntax-typescript@npm:7.24.7" + dependencies: + "@babel/helper-plugin-utils": "npm:^7.24.7" + peerDependencies: + "@babel/core": ^7.0.0-0 + checksum: 10c0/cdabd2e8010fb0ad15b49c2c270efc97c4bfe109ead36c7bbcf22da7a74bc3e49702fc4f22f12d2d6049e8e22a5769258df1fd05f0420ae45e11bdd5bc07805a + languageName: node + linkType: hard + +"@babel/plugin-transform-modules-commonjs@npm:^7.24.7": + version: 7.24.7 + resolution: "@babel/plugin-transform-modules-commonjs@npm:7.24.7" + dependencies: + "@babel/helper-module-transforms": "npm:^7.24.7" + "@babel/helper-plugin-utils": "npm:^7.24.7" + "@babel/helper-simple-access": "npm:^7.24.7" + peerDependencies: + "@babel/core": ^7.0.0-0 + checksum: 10c0/9442292b3daf6a5076cdc3c4c32bf423bda824ccaeb0dd0dc8b3effaa1fecfcb0130ae6e647fef12a5d5ff25bcc99a0d6bfc6d24a7525345e1bcf46fcdf81752 + languageName: node + linkType: hard + +"@babel/plugin-transform-typescript@npm:^7.24.7": + version: 7.24.7 + resolution: "@babel/plugin-transform-typescript@npm:7.24.7" + dependencies: + "@babel/helper-annotate-as-pure": "npm:^7.24.7" + "@babel/helper-create-class-features-plugin": "npm:^7.24.7" + "@babel/helper-plugin-utils": "npm:^7.24.7" + "@babel/plugin-syntax-typescript": "npm:^7.24.7" + peerDependencies: + "@babel/core": ^7.0.0-0 + checksum: 10c0/e8dacdc153a4c4599014b66eb01b94e3dc933d58d4f0cc3039c1a8f432e77b9df14f34a61964e014b975bf466f3fefd8c4768b3e887d3da1be9dc942799bdfdf + languageName: node + linkType: hard + +"@babel/preset-typescript@npm:^7.20.2": + version: 7.24.7 + resolution: "@babel/preset-typescript@npm:7.24.7" + dependencies: + "@babel/helper-plugin-utils": "npm:^7.24.7" + "@babel/helper-validator-option": "npm:^7.24.7" + "@babel/plugin-syntax-jsx": "npm:^7.24.7" + "@babel/plugin-transform-modules-commonjs": "npm:^7.24.7" + "@babel/plugin-transform-typescript": "npm:^7.24.7" + peerDependencies: + "@babel/core": ^7.0.0-0 + checksum: 10c0/986bc0978eedb4da33aba8e1e13a3426dd1829515313b7e8f4ba5d8c18aff1663b468939d471814e7acf4045d326ae6cff37239878d169ac3fe53a8fde71f8ee + languageName: node + linkType: hard + +"@babel/runtime@npm:^7.21.0": + version: 7.24.7 + resolution: "@babel/runtime@npm:7.24.7" + dependencies: + regenerator-runtime: "npm:^0.14.0" + checksum: 10c0/b6fa3ec61a53402f3c1d75f4d808f48b35e0dfae0ec8e2bb5c6fc79fb95935da75766e0ca534d0f1c84871f6ae0d2ebdd950727cfadb745a2cdbef13faef5513 + languageName: node + linkType: hard + +"@babel/template@npm:^7.24.7, @babel/template@npm:^7.3.3": + version: 7.24.7 + resolution: "@babel/template@npm:7.24.7" + dependencies: + "@babel/code-frame": "npm:^7.24.7" + "@babel/parser": "npm:^7.24.7" + "@babel/types": "npm:^7.24.7" + checksum: 10c0/95b0b3ee80fcef685b7f4426f5713a855ea2cd5ac4da829b213f8fb5afe48a2a14683c2ea04d446dbc7f711c33c5cd4a965ef34dcbe5bc387c9e966b67877ae3 + languageName: node + linkType: hard + +"@babel/traverse@npm:^7.24.7": + version: 7.24.7 + resolution: "@babel/traverse@npm:7.24.7" + dependencies: + "@babel/code-frame": "npm:^7.24.7" + "@babel/generator": "npm:^7.24.7" + "@babel/helper-environment-visitor": "npm:^7.24.7" + "@babel/helper-function-name": "npm:^7.24.7" + "@babel/helper-hoist-variables": "npm:^7.24.7" + "@babel/helper-split-export-declaration": "npm:^7.24.7" + "@babel/parser": "npm:^7.24.7" + "@babel/types": "npm:^7.24.7" + debug: "npm:^4.3.1" + globals: "npm:^11.1.0" + checksum: 10c0/a5135e589c3f1972b8877805f50a084a04865ccb1d68e5e1f3b94a8841b3485da4142e33413d8fd76bc0e6444531d3adf1f59f359c11ffac452b743d835068ab + languageName: node + linkType: hard + +"@babel/types@npm:^7.0.0, @babel/types@npm:^7.20.7, @babel/types@npm:^7.24.7, @babel/types@npm:^7.3.3, @babel/types@npm:^7.8.3": + version: 7.24.7 + resolution: "@babel/types@npm:7.24.7" + dependencies: + "@babel/helper-string-parser": "npm:^7.24.7" + "@babel/helper-validator-identifier": "npm:^7.24.7" + to-fast-properties: "npm:^2.0.0" + checksum: 10c0/d9ecbfc3eb2b05fb1e6eeea546836ac30d990f395ef3fe3f75ced777a222c3cfc4489492f72e0ce3d9a5a28860a1ce5f81e66b88cf5088909068b3ff4fab72c1 + languageName: node + linkType: hard + +"@bcoe/v8-coverage@npm:^0.2.3": + version: 0.2.3 + resolution: "@bcoe/v8-coverage@npm:0.2.3" + checksum: 10c0/6b80ae4cb3db53f486da2dc63b6e190a74c8c3cca16bb2733f234a0b6a9382b09b146488ae08e2b22cf00f6c83e20f3e040a2f7894f05c045c946d6a090b1d52 + languageName: node + linkType: hard + +"@colors/colors@npm:1.6.0, @colors/colors@npm:^1.6.0": + version: 1.6.0 + resolution: "@colors/colors@npm:1.6.0" + checksum: 10c0/9328a0778a5b0db243af54455b79a69e3fb21122d6c15ef9e9fcc94881d8d17352d8b2b2590f9bdd46fac5c2d6c1636dcfc14358a20c70e22daf89e1a759b629 + languageName: node + linkType: hard + +"@dabh/diagnostics@npm:^2.0.2": + version: 2.0.3 + resolution: "@dabh/diagnostics@npm:2.0.3" + dependencies: + colorspace: "npm:1.1.x" + enabled: "npm:2.0.x" + kuler: "npm:^2.0.0" + checksum: 10c0/a5133df8492802465ed01f2f0a5784585241a1030c362d54a602ed1839816d6c93d71dde05cf2ddb4fd0796238c19774406bd62fa2564b637907b495f52425fe + languageName: node + linkType: hard + +"@eslint-community/eslint-utils@npm:^4.2.0": + version: 4.4.0 + resolution: "@eslint-community/eslint-utils@npm:4.4.0" + dependencies: + eslint-visitor-keys: "npm:^3.3.0" + peerDependencies: + eslint: ^6.0.0 || ^7.0.0 || >=8.0.0 + checksum: 10c0/7e559c4ce59cd3a06b1b5a517b593912e680a7f981ae7affab0d01d709e99cd5647019be8fafa38c350305bc32f1f7d42c7073edde2ab536c745e365f37b607e + languageName: node + linkType: hard + +"@eslint-community/regexpp@npm:^4.4.0, @eslint-community/regexpp@npm:^4.6.1": + version: 4.10.1 + resolution: "@eslint-community/regexpp@npm:4.10.1" + checksum: 10c0/f59376025d0c91dd9fdf18d33941df499292a3ecba3e9889c360f3f6590197d30755604588786cdca0f9030be315a26b206014af4b65c0ff85b4ec49043de780 + languageName: node + linkType: hard + +"@eslint/eslintrc@npm:^2.1.4": + version: 2.1.4 + resolution: "@eslint/eslintrc@npm:2.1.4" + dependencies: + ajv: "npm:^6.12.4" + debug: "npm:^4.3.2" + espree: "npm:^9.6.0" + globals: "npm:^13.19.0" + ignore: "npm:^5.2.0" + import-fresh: "npm:^3.2.1" + js-yaml: "npm:^4.1.0" + minimatch: "npm:^3.1.2" + strip-json-comments: "npm:^3.1.1" + checksum: 10c0/32f67052b81768ae876c84569ffd562491ec5a5091b0c1e1ca1e0f3c24fb42f804952fdd0a137873bc64303ba368a71ba079a6f691cee25beee9722d94cc8573 + languageName: node + linkType: hard + +"@eslint/js@npm:8.57.0": + version: 8.57.0 + resolution: "@eslint/js@npm:8.57.0" + checksum: 10c0/9a518bb8625ba3350613903a6d8c622352ab0c6557a59fe6ff6178bf882bf57123f9d92aa826ee8ac3ee74b9c6203fe630e9ee00efb03d753962dcf65ee4bd94 + languageName: node + linkType: hard + +"@humanwhocodes/config-array@npm:^0.11.14": + version: 0.11.14 + resolution: "@humanwhocodes/config-array@npm:0.11.14" + dependencies: + "@humanwhocodes/object-schema": "npm:^2.0.2" + debug: "npm:^4.3.1" + minimatch: "npm:^3.0.5" + checksum: 10c0/66f725b4ee5fdd8322c737cb5013e19fac72d4d69c8bf4b7feb192fcb83442b035b92186f8e9497c220e58b2d51a080f28a73f7899bc1ab288c3be172c467541 + languageName: node + linkType: hard + +"@humanwhocodes/module-importer@npm:^1.0.1": + version: 1.0.1 + resolution: "@humanwhocodes/module-importer@npm:1.0.1" + checksum: 10c0/909b69c3b86d482c26b3359db16e46a32e0fb30bd306a3c176b8313b9e7313dba0f37f519de6aa8b0a1921349e505f259d19475e123182416a506d7f87e7f529 + languageName: node + linkType: hard + +"@humanwhocodes/object-schema@npm:^2.0.2": + version: 2.0.3 + resolution: "@humanwhocodes/object-schema@npm:2.0.3" + checksum: 10c0/80520eabbfc2d32fe195a93557cef50dfe8c8905de447f022675aaf66abc33ae54098f5ea78548d925aa671cd4ab7c7daa5ad704fe42358c9b5e7db60f80696c + languageName: node + linkType: hard + +"@isaacs/cliui@npm:^8.0.2": + version: 8.0.2 + resolution: "@isaacs/cliui@npm:8.0.2" + dependencies: + string-width: "npm:^5.1.2" + string-width-cjs: "npm:string-width@^4.2.0" + strip-ansi: "npm:^7.0.1" + strip-ansi-cjs: "npm:strip-ansi@^6.0.1" + wrap-ansi: "npm:^8.1.0" + wrap-ansi-cjs: "npm:wrap-ansi@^7.0.0" + checksum: 10c0/b1bf42535d49f11dc137f18d5e4e63a28c5569de438a221c369483731e9dac9fb797af554e8bf02b6192d1e5eba6e6402cf93900c3d0ac86391d00d04876789e + languageName: node + linkType: hard + +"@istanbuljs/load-nyc-config@npm:^1.0.0": + version: 1.1.0 + resolution: "@istanbuljs/load-nyc-config@npm:1.1.0" + dependencies: + camelcase: "npm:^5.3.1" + find-up: "npm:^4.1.0" + get-package-type: "npm:^0.1.0" + js-yaml: "npm:^3.13.1" + resolve-from: "npm:^5.0.0" + checksum: 10c0/dd2a8b094887da5a1a2339543a4933d06db2e63cbbc2e288eb6431bd832065df0c099d091b6a67436e71b7d6bf85f01ce7c15f9253b4cbebcc3b9a496165ba42 + languageName: node + linkType: hard + +"@istanbuljs/schema@npm:^0.1.2, @istanbuljs/schema@npm:^0.1.3": + version: 0.1.3 + resolution: "@istanbuljs/schema@npm:0.1.3" + checksum: 10c0/61c5286771676c9ca3eb2bd8a7310a9c063fb6e0e9712225c8471c582d157392c88f5353581c8c9adbe0dff98892317d2fdfc56c3499aa42e0194405206a963a + languageName: node + linkType: hard + +"@jest/console@npm:^29.7.0": + version: 29.7.0 + resolution: "@jest/console@npm:29.7.0" + dependencies: + "@jest/types": "npm:^29.6.3" + "@types/node": "npm:*" + chalk: "npm:^4.0.0" + jest-message-util: "npm:^29.7.0" + jest-util: "npm:^29.7.0" + slash: "npm:^3.0.0" + checksum: 10c0/7be408781d0a6f657e969cbec13b540c329671819c2f57acfad0dae9dbfe2c9be859f38fe99b35dba9ff1536937dc6ddc69fdcd2794812fa3c647a1619797f6c + languageName: node + linkType: hard + +"@jest/core@npm:^29.7.0": + version: 29.7.0 + resolution: "@jest/core@npm:29.7.0" + dependencies: + "@jest/console": "npm:^29.7.0" + "@jest/reporters": "npm:^29.7.0" + "@jest/test-result": "npm:^29.7.0" + "@jest/transform": "npm:^29.7.0" + "@jest/types": "npm:^29.6.3" + "@types/node": "npm:*" + ansi-escapes: "npm:^4.2.1" + chalk: "npm:^4.0.0" + ci-info: "npm:^3.2.0" + exit: "npm:^0.1.2" + graceful-fs: "npm:^4.2.9" + jest-changed-files: "npm:^29.7.0" + jest-config: "npm:^29.7.0" + jest-haste-map: "npm:^29.7.0" + jest-message-util: "npm:^29.7.0" + jest-regex-util: "npm:^29.6.3" + jest-resolve: "npm:^29.7.0" + jest-resolve-dependencies: "npm:^29.7.0" + jest-runner: "npm:^29.7.0" + jest-runtime: "npm:^29.7.0" + jest-snapshot: "npm:^29.7.0" + jest-util: "npm:^29.7.0" + jest-validate: "npm:^29.7.0" + jest-watcher: "npm:^29.7.0" + micromatch: "npm:^4.0.4" + pretty-format: "npm:^29.7.0" + slash: "npm:^3.0.0" + strip-ansi: "npm:^6.0.0" + peerDependencies: + node-notifier: ^8.0.1 || ^9.0.0 || ^10.0.0 + peerDependenciesMeta: + node-notifier: + optional: true + checksum: 10c0/934f7bf73190f029ac0f96662c85cd276ec460d407baf6b0dbaec2872e157db4d55a7ee0b1c43b18874602f662b37cb973dda469a4e6d88b4e4845b521adeeb2 + languageName: node + linkType: hard + +"@jest/environment@npm:^29.7.0": + version: 29.7.0 + resolution: "@jest/environment@npm:29.7.0" + dependencies: + "@jest/fake-timers": "npm:^29.7.0" + "@jest/types": "npm:^29.6.3" + "@types/node": "npm:*" + jest-mock: "npm:^29.7.0" + checksum: 10c0/c7b1b40c618f8baf4d00609022d2afa086d9c6acc706f303a70bb4b67275868f620ad2e1a9efc5edd418906157337cce50589a627a6400bbdf117d351b91ef86 + languageName: node + linkType: hard + +"@jest/expect-utils@npm:^29.7.0": + version: 29.7.0 + resolution: "@jest/expect-utils@npm:29.7.0" + dependencies: + jest-get-type: "npm:^29.6.3" + checksum: 10c0/60b79d23a5358dc50d9510d726443316253ecda3a7fb8072e1526b3e0d3b14f066ee112db95699b7a43ad3f0b61b750c72e28a5a1cac361d7a2bb34747fa938a + languageName: node + linkType: hard + +"@jest/expect@npm:^29.7.0": + version: 29.7.0 + resolution: "@jest/expect@npm:29.7.0" + dependencies: + expect: "npm:^29.7.0" + jest-snapshot: "npm:^29.7.0" + checksum: 10c0/b41f193fb697d3ced134349250aed6ccea075e48c4f803159db102b826a4e473397c68c31118259868fd69a5cba70e97e1c26d2c2ff716ca39dc73a2ccec037e + languageName: node + linkType: hard + +"@jest/fake-timers@npm:^29.7.0": + version: 29.7.0 + resolution: "@jest/fake-timers@npm:29.7.0" + dependencies: + "@jest/types": "npm:^29.6.3" + "@sinonjs/fake-timers": "npm:^10.0.2" + "@types/node": "npm:*" + jest-message-util: "npm:^29.7.0" + jest-mock: "npm:^29.7.0" + jest-util: "npm:^29.7.0" + checksum: 10c0/cf0a8bcda801b28dc2e2b2ba36302200ee8104a45ad7a21e6c234148932f826cb3bc57c8df3b7b815aeea0861d7b6ca6f0d4778f93b9219398ef28749e03595c + languageName: node + linkType: hard + +"@jest/globals@npm:^29.7.0": + version: 29.7.0 + resolution: "@jest/globals@npm:29.7.0" + dependencies: + "@jest/environment": "npm:^29.7.0" + "@jest/expect": "npm:^29.7.0" + "@jest/types": "npm:^29.6.3" + jest-mock: "npm:^29.7.0" + checksum: 10c0/a385c99396878fe6e4460c43bd7bb0a5cc52befb462cc6e7f2a3810f9e7bcce7cdeb51908fd530391ee452dc856c98baa2c5f5fa8a5b30b071d31ef7f6955cea + languageName: node + linkType: hard + +"@jest/reporters@npm:^29.7.0": + version: 29.7.0 + resolution: "@jest/reporters@npm:29.7.0" + dependencies: + "@bcoe/v8-coverage": "npm:^0.2.3" + "@jest/console": "npm:^29.7.0" + "@jest/test-result": "npm:^29.7.0" + "@jest/transform": "npm:^29.7.0" + "@jest/types": "npm:^29.6.3" + "@jridgewell/trace-mapping": "npm:^0.3.18" + "@types/node": "npm:*" + chalk: "npm:^4.0.0" + collect-v8-coverage: "npm:^1.0.0" + exit: "npm:^0.1.2" + glob: "npm:^7.1.3" + graceful-fs: "npm:^4.2.9" + istanbul-lib-coverage: "npm:^3.0.0" + istanbul-lib-instrument: "npm:^6.0.0" + istanbul-lib-report: "npm:^3.0.0" + istanbul-lib-source-maps: "npm:^4.0.0" + istanbul-reports: "npm:^3.1.3" + jest-message-util: "npm:^29.7.0" + jest-util: "npm:^29.7.0" + jest-worker: "npm:^29.7.0" + slash: "npm:^3.0.0" + string-length: "npm:^4.0.1" + strip-ansi: "npm:^6.0.0" + v8-to-istanbul: "npm:^9.0.1" + peerDependencies: + node-notifier: ^8.0.1 || ^9.0.0 || ^10.0.0 + peerDependenciesMeta: + node-notifier: + optional: true + checksum: 10c0/a754402a799541c6e5aff2c8160562525e2a47e7d568f01ebfc4da66522de39cbb809bbb0a841c7052e4270d79214e70aec3c169e4eae42a03bc1a8a20cb9fa2 + languageName: node + linkType: hard + +"@jest/schemas@npm:^29.6.3": + version: 29.6.3 + resolution: "@jest/schemas@npm:29.6.3" + dependencies: + "@sinclair/typebox": "npm:^0.27.8" + checksum: 10c0/b329e89cd5f20b9278ae1233df74016ebf7b385e0d14b9f4c1ad18d096c4c19d1e687aa113a9c976b16ec07f021ae53dea811fb8c1248a50ac34fbe009fdf6be + languageName: node + linkType: hard + +"@jest/source-map@npm:^29.6.3": + version: 29.6.3 + resolution: "@jest/source-map@npm:29.6.3" + dependencies: + "@jridgewell/trace-mapping": "npm:^0.3.18" + callsites: "npm:^3.0.0" + graceful-fs: "npm:^4.2.9" + checksum: 10c0/a2f177081830a2e8ad3f2e29e20b63bd40bade294880b595acf2fc09ec74b6a9dd98f126a2baa2bf4941acd89b13a4ade5351b3885c224107083a0059b60a219 + languageName: node + linkType: hard + +"@jest/test-result@npm:^29.7.0": + version: 29.7.0 + resolution: "@jest/test-result@npm:29.7.0" + dependencies: + "@jest/console": "npm:^29.7.0" + "@jest/types": "npm:^29.6.3" + "@types/istanbul-lib-coverage": "npm:^2.0.0" + collect-v8-coverage: "npm:^1.0.0" + checksum: 10c0/7de54090e54a674ca173470b55dc1afdee994f2d70d185c80236003efd3fa2b753fff51ffcdda8e2890244c411fd2267529d42c4a50a8303755041ee493e6a04 + languageName: node + linkType: hard + +"@jest/test-sequencer@npm:^29.7.0": + version: 29.7.0 + resolution: "@jest/test-sequencer@npm:29.7.0" + dependencies: + "@jest/test-result": "npm:^29.7.0" + graceful-fs: "npm:^4.2.9" + jest-haste-map: "npm:^29.7.0" + slash: "npm:^3.0.0" + checksum: 10c0/593a8c4272797bb5628984486080cbf57aed09c7cfdc0a634e8c06c38c6bef329c46c0016e84555ee55d1cd1f381518cf1890990ff845524c1123720c8c1481b + languageName: node + linkType: hard + +"@jest/transform@npm:^29.7.0": + version: 29.7.0 + resolution: "@jest/transform@npm:29.7.0" + dependencies: + "@babel/core": "npm:^7.11.6" + "@jest/types": "npm:^29.6.3" + "@jridgewell/trace-mapping": "npm:^0.3.18" + babel-plugin-istanbul: "npm:^6.1.1" + chalk: "npm:^4.0.0" + convert-source-map: "npm:^2.0.0" + fast-json-stable-stringify: "npm:^2.1.0" + graceful-fs: "npm:^4.2.9" + jest-haste-map: "npm:^29.7.0" + jest-regex-util: "npm:^29.6.3" + jest-util: "npm:^29.7.0" + micromatch: "npm:^4.0.4" + pirates: "npm:^4.0.4" + slash: "npm:^3.0.0" + write-file-atomic: "npm:^4.0.2" + checksum: 10c0/7f4a7f73dcf45dfdf280c7aa283cbac7b6e5a904813c3a93ead7e55873761fc20d5c4f0191d2019004fac6f55f061c82eb3249c2901164ad80e362e7a7ede5a6 + languageName: node + linkType: hard + +"@jest/types@npm:^29.6.3": + version: 29.6.3 + resolution: "@jest/types@npm:29.6.3" + dependencies: + "@jest/schemas": "npm:^29.6.3" + "@types/istanbul-lib-coverage": "npm:^2.0.0" + "@types/istanbul-reports": "npm:^3.0.0" + "@types/node": "npm:*" + "@types/yargs": "npm:^17.0.8" + chalk: "npm:^4.0.0" + checksum: 10c0/ea4e493dd3fb47933b8ccab201ae573dcc451f951dc44ed2a86123cd8541b82aa9d2b1031caf9b1080d6673c517e2dcc25a44b2dc4f3fbc37bfc965d444888c0 + languageName: node + linkType: hard + +"@jridgewell/gen-mapping@npm:^0.3.5": + version: 0.3.5 + resolution: "@jridgewell/gen-mapping@npm:0.3.5" + dependencies: + "@jridgewell/set-array": "npm:^1.2.1" + "@jridgewell/sourcemap-codec": "npm:^1.4.10" + "@jridgewell/trace-mapping": "npm:^0.3.24" + checksum: 10c0/1be4fd4a6b0f41337c4f5fdf4afc3bd19e39c3691924817108b82ffcb9c9e609c273f936932b9fba4b3a298ce2eb06d9bff4eb1cc3bd81c4f4ee1b4917e25feb + languageName: node + linkType: hard + +"@jridgewell/resolve-uri@npm:^3.1.0": + version: 3.1.2 + resolution: "@jridgewell/resolve-uri@npm:3.1.2" + checksum: 10c0/d502e6fb516b35032331406d4e962c21fe77cdf1cbdb49c6142bcbd9e30507094b18972778a6e27cbad756209cfe34b1a27729e6fa08a2eb92b33943f680cf1e + languageName: node + linkType: hard + +"@jridgewell/set-array@npm:^1.2.1": + version: 1.2.1 + resolution: "@jridgewell/set-array@npm:1.2.1" + checksum: 10c0/2a5aa7b4b5c3464c895c802d8ae3f3d2b92fcbe84ad12f8d0bfbb1f5ad006717e7577ee1fd2eac00c088abe486c7adb27976f45d2941ff6b0b92b2c3302c60f4 + languageName: node + linkType: hard + +"@jridgewell/sourcemap-codec@npm:^1.4.10, @jridgewell/sourcemap-codec@npm:^1.4.14": + version: 1.4.15 + resolution: "@jridgewell/sourcemap-codec@npm:1.4.15" + checksum: 10c0/0c6b5ae663087558039052a626d2d7ed5208da36cfd707dcc5cea4a07cfc918248403dcb5989a8f7afaf245ce0573b7cc6fd94c4a30453bd10e44d9363940ba5 + languageName: node + linkType: hard + +"@jridgewell/trace-mapping@npm:^0.3.12, @jridgewell/trace-mapping@npm:^0.3.18, @jridgewell/trace-mapping@npm:^0.3.24, @jridgewell/trace-mapping@npm:^0.3.25": + version: 0.3.25 + resolution: "@jridgewell/trace-mapping@npm:0.3.25" + dependencies: + "@jridgewell/resolve-uri": "npm:^3.1.0" + "@jridgewell/sourcemap-codec": "npm:^1.4.14" + checksum: 10c0/3d1ce6ebc69df9682a5a8896b414c6537e428a1d68b02fcc8363b04284a8ca0df04d0ee3013132252ab14f2527bc13bea6526a912ecb5658f0e39fd2860b4df4 + languageName: node + linkType: hard + +"@mapbox/node-pre-gyp@npm:^1.0.11": + version: 1.0.11 + resolution: "@mapbox/node-pre-gyp@npm:1.0.11" + dependencies: + detect-libc: "npm:^2.0.0" + https-proxy-agent: "npm:^5.0.0" + make-dir: "npm:^3.1.0" + node-fetch: "npm:^2.6.7" + nopt: "npm:^5.0.0" + npmlog: "npm:^5.0.1" + rimraf: "npm:^3.0.2" + semver: "npm:^7.3.5" + tar: "npm:^6.1.11" + bin: + node-pre-gyp: bin/node-pre-gyp + checksum: 10c0/2b24b93c31beca1c91336fa3b3769fda98e202fb7f9771f0f4062588d36dcc30fcf8118c36aa747fa7f7610d8cf601872bdaaf62ce7822bb08b545d1bbe086cc + languageName: node + linkType: hard + +"@nodelib/fs.scandir@npm:2.1.5": + version: 2.1.5 + resolution: "@nodelib/fs.scandir@npm:2.1.5" + dependencies: + "@nodelib/fs.stat": "npm:2.0.5" + run-parallel: "npm:^1.1.9" + checksum: 10c0/732c3b6d1b1e967440e65f284bd06e5821fedf10a1bea9ed2bb75956ea1f30e08c44d3def9d6a230666574edbaf136f8cfd319c14fd1f87c66e6a44449afb2eb + languageName: node + linkType: hard + +"@nodelib/fs.stat@npm:2.0.5, @nodelib/fs.stat@npm:^2.0.2": + version: 2.0.5 + resolution: "@nodelib/fs.stat@npm:2.0.5" + checksum: 10c0/88dafe5e3e29a388b07264680dc996c17f4bda48d163a9d4f5c1112979f0ce8ec72aa7116122c350b4e7976bc5566dc3ddb579be1ceaacc727872eb4ed93926d + languageName: node + linkType: hard + +"@nodelib/fs.walk@npm:^1.2.3, @nodelib/fs.walk@npm:^1.2.8": + version: 1.2.8 + resolution: "@nodelib/fs.walk@npm:1.2.8" + dependencies: + "@nodelib/fs.scandir": "npm:2.1.5" + fastq: "npm:^1.6.0" + checksum: 10c0/db9de047c3bb9b51f9335a7bb46f4fcfb6829fb628318c12115fbaf7d369bfce71c15b103d1fc3b464812d936220ee9bc1c8f762d032c9f6be9acc99249095b1 + languageName: node + linkType: hard + +"@npmcli/agent@npm:^2.0.0": + version: 2.2.2 + resolution: "@npmcli/agent@npm:2.2.2" + dependencies: + agent-base: "npm:^7.1.0" + http-proxy-agent: "npm:^7.0.0" + https-proxy-agent: "npm:^7.0.1" + lru-cache: "npm:^10.0.1" + socks-proxy-agent: "npm:^8.0.3" + checksum: 10c0/325e0db7b287d4154ecd164c0815c08007abfb07653cc57bceded17bb7fd240998a3cbdbe87d700e30bef494885eccc725ab73b668020811d56623d145b524ae + languageName: node + linkType: hard + +"@npmcli/fs@npm:^3.1.0": + version: 3.1.1 + resolution: "@npmcli/fs@npm:3.1.1" + dependencies: + semver: "npm:^7.3.5" + checksum: 10c0/c37a5b4842bfdece3d14dfdb054f73fe15ed2d3da61b34ff76629fb5b1731647c49166fd2a8bf8b56fcfa51200382385ea8909a3cbecdad612310c114d3f6c99 + languageName: node + linkType: hard + +"@pkgjs/parseargs@npm:^0.11.0": + version: 0.11.0 + resolution: "@pkgjs/parseargs@npm:0.11.0" + checksum: 10c0/5bd7576bb1b38a47a7fc7b51ac9f38748e772beebc56200450c4a817d712232b8f1d3ef70532c80840243c657d491cf6a6be1e3a214cff907645819fdc34aadd + languageName: node + linkType: hard + +"@sinclair/typebox@npm:^0.27.8": + version: 0.27.8 + resolution: "@sinclair/typebox@npm:0.27.8" + checksum: 10c0/ef6351ae073c45c2ac89494dbb3e1f87cc60a93ce4cde797b782812b6f97da0d620ae81973f104b43c9b7eaa789ad20ba4f6a1359f1cc62f63729a55a7d22d4e + languageName: node + linkType: hard + +"@sinonjs/commons@npm:^3.0.0": + version: 3.0.1 + resolution: "@sinonjs/commons@npm:3.0.1" + dependencies: + type-detect: "npm:4.0.8" + checksum: 10c0/1227a7b5bd6c6f9584274db996d7f8cee2c8c350534b9d0141fc662eaf1f292ea0ae3ed19e5e5271c8fd390d27e492ca2803acd31a1978be2cdc6be0da711403 + languageName: node + linkType: hard + +"@sinonjs/fake-timers@npm:^10.0.2": + version: 10.3.0 + resolution: "@sinonjs/fake-timers@npm:10.3.0" + dependencies: + "@sinonjs/commons": "npm:^3.0.0" + checksum: 10c0/2e2fb6cc57f227912814085b7b01fede050cd4746ea8d49a1e44d5a0e56a804663b0340ae2f11af7559ea9bf4d087a11f2f646197a660ea3cb04e19efc04aa63 + languageName: node + linkType: hard + +"@types/babel__core@npm:^7.1.14": + version: 7.20.5 + resolution: "@types/babel__core@npm:7.20.5" + dependencies: + "@babel/parser": "npm:^7.20.7" + "@babel/types": "npm:^7.20.7" + "@types/babel__generator": "npm:*" + "@types/babel__template": "npm:*" + "@types/babel__traverse": "npm:*" + checksum: 10c0/bdee3bb69951e833a4b811b8ee9356b69a61ed5b7a23e1a081ec9249769117fa83aaaf023bb06562a038eb5845155ff663e2d5c75dd95c1d5ccc91db012868ff + languageName: node + linkType: hard + +"@types/babel__generator@npm:*": + version: 7.6.8 + resolution: "@types/babel__generator@npm:7.6.8" + dependencies: + "@babel/types": "npm:^7.0.0" + checksum: 10c0/f0ba105e7d2296bf367d6e055bb22996886c114261e2cb70bf9359556d0076c7a57239d019dee42bb063f565bade5ccb46009bce2044b2952d964bf9a454d6d2 + languageName: node + linkType: hard + +"@types/babel__template@npm:*": + version: 7.4.4 + resolution: "@types/babel__template@npm:7.4.4" + dependencies: + "@babel/parser": "npm:^7.1.0" + "@babel/types": "npm:^7.0.0" + checksum: 10c0/cc84f6c6ab1eab1427e90dd2b76ccee65ce940b778a9a67be2c8c39e1994e6f5bbc8efa309f6cea8dc6754994524cd4d2896558df76d92e7a1f46ecffee7112b + languageName: node + linkType: hard + +"@types/babel__traverse@npm:*, @types/babel__traverse@npm:^7.0.6": + version: 7.20.6 + resolution: "@types/babel__traverse@npm:7.20.6" + dependencies: + "@babel/types": "npm:^7.20.7" + checksum: 10c0/7ba7db61a53e28cac955aa99af280d2600f15a8c056619c05b6fc911cbe02c61aa4f2823299221b23ce0cce00b294c0e5f618ec772aa3f247523c2e48cf7b888 + languageName: node + linkType: hard + +"@types/bcrypt@npm:^5.0.2": + version: 5.0.2 + resolution: "@types/bcrypt@npm:5.0.2" + dependencies: + "@types/node": "npm:*" + checksum: 10c0/dd7f05e183b9b1fc08ec499069febf197ab8e9c720766b5bbb5628395082e248f9a444c60882fe7788361fcadc302e21e055ab9c26a300f100e08791c353e6aa + languageName: node + linkType: hard + +"@types/better-sqlite3@npm:^7.6.7": + version: 7.6.10 + resolution: "@types/better-sqlite3@npm:7.6.10" + dependencies: + "@types/node": "npm:*" + checksum: 10c0/11c4da950e0e1a31270e8c7d98ba34fa5a28fbd3280ffa75945983291d2ec5bc87a9b3b378c21c042249a415d557066a0431da568b83ff9e1bac53eddf4f5adc + languageName: node + linkType: hard + +"@types/body-parser@npm:*": + version: 1.19.5 + resolution: "@types/body-parser@npm:1.19.5" + dependencies: + "@types/connect": "npm:*" + "@types/node": "npm:*" + checksum: 10c0/aebeb200f25e8818d8cf39cd0209026750d77c9b85381cdd8deeb50913e4d18a1ebe4b74ca9b0b4d21952511eeaba5e9fbbf739b52731a2061e206ec60d568df + languageName: node + linkType: hard + +"@types/connect@npm:*": + version: 3.4.38 + resolution: "@types/connect@npm:3.4.38" + dependencies: + "@types/node": "npm:*" + checksum: 10c0/2e1cdba2c410f25649e77856505cd60223250fa12dff7a503e492208dbfdd25f62859918f28aba95315251fd1f5e1ffbfca1e25e73037189ab85dd3f8d0a148c + languageName: node + linkType: hard + +"@types/cookiejar@npm:^2.1.5": + version: 2.1.5 + resolution: "@types/cookiejar@npm:2.1.5" + checksum: 10c0/af38c3d84aebb3ccc6e46fb6afeeaac80fb26e63a487dd4db5a8b87e6ad3d4b845ba1116b2ae90d6f886290a36200fa433d8b1f6fe19c47da6b81872ce9a2764 + languageName: node + linkType: hard + +"@types/cors@npm:^2.8.13": + version: 2.8.17 + resolution: "@types/cors@npm:2.8.17" + dependencies: + "@types/node": "npm:*" + checksum: 10c0/457364c28c89f3d9ed34800e1de5c6eaaf344d1bb39af122f013322a50bc606eb2aa6f63de4e41a7a08ba7ef454473926c94a830636723da45bf786df032696d + languageName: node + linkType: hard + +"@types/express-actuator@npm:^1.8.0": + version: 1.8.3 + resolution: "@types/express-actuator@npm:1.8.3" + dependencies: + "@types/express": "npm:*" + checksum: 10c0/e7a5ffae28aa89c636edc75594adbf63bc3a0f86d27d0bdd4567e8ac91d6de8d0b76b85009ab65a9714625cfc443374eab227b11e58cb4450093500123eef0a2 + languageName: node + linkType: hard + +"@types/express-serve-static-core@npm:^4.17.33": + version: 4.19.3 + resolution: "@types/express-serve-static-core@npm:4.19.3" + dependencies: + "@types/node": "npm:*" + "@types/qs": "npm:*" + "@types/range-parser": "npm:*" + "@types/send": "npm:*" + checksum: 10c0/5d2a1fb96a17a8e0e8c59325dfeb6d454bbc5c9b9b6796eec0397ddf9dbd262892040d5da3d72b5d7148f34bb3fcd438faf1b37fcba8c5a03e75fae491ad1edf + languageName: node + linkType: hard + +"@types/express@npm:*, @types/express@npm:^4.17.17": + version: 4.17.21 + resolution: "@types/express@npm:4.17.21" + dependencies: + "@types/body-parser": "npm:*" + "@types/express-serve-static-core": "npm:^4.17.33" + "@types/qs": "npm:*" + "@types/serve-static": "npm:*" + checksum: 10c0/12e562c4571da50c7d239e117e688dc434db1bac8be55613294762f84fd77fbd0658ccd553c7d3ab02408f385bc93980992369dd30e2ecd2c68c358e6af8fabf + languageName: node + linkType: hard + +"@types/graceful-fs@npm:^4.1.3": + version: 4.1.9 + resolution: "@types/graceful-fs@npm:4.1.9" + dependencies: + "@types/node": "npm:*" + checksum: 10c0/235d2fc69741448e853333b7c3d1180a966dd2b8972c8cbcd6b2a0c6cd7f8d582ab2b8e58219dbc62cce8f1b40aa317ff78ea2201cdd8249da5025adebed6f0b + languageName: node + linkType: hard + +"@types/http-errors@npm:*": + version: 2.0.4 + resolution: "@types/http-errors@npm:2.0.4" + checksum: 10c0/494670a57ad4062fee6c575047ad5782506dd35a6b9ed3894cea65830a94367bd84ba302eb3dde331871f6d70ca287bfedb1b2cf658e6132cd2cbd427ab56836 + languageName: node + linkType: hard + +"@types/istanbul-lib-coverage@npm:*, @types/istanbul-lib-coverage@npm:^2.0.0, @types/istanbul-lib-coverage@npm:^2.0.1": + version: 2.0.6 + resolution: "@types/istanbul-lib-coverage@npm:2.0.6" + checksum: 10c0/3948088654f3eeb45363f1db158354fb013b362dba2a5c2c18c559484d5eb9f6fd85b23d66c0a7c2fcfab7308d0a585b14dadaca6cc8bf89ebfdc7f8f5102fb7 + languageName: node + linkType: hard + +"@types/istanbul-lib-report@npm:*": + version: 3.0.3 + resolution: "@types/istanbul-lib-report@npm:3.0.3" + dependencies: + "@types/istanbul-lib-coverage": "npm:*" + checksum: 10c0/247e477bbc1a77248f3c6de5dadaae85ff86ac2d76c5fc6ab1776f54512a745ff2a5f791d22b942e3990ddbd40f3ef5289317c4fca5741bedfaa4f01df89051c + languageName: node + linkType: hard + +"@types/istanbul-reports@npm:^3.0.0": + version: 3.0.4 + resolution: "@types/istanbul-reports@npm:3.0.4" + dependencies: + "@types/istanbul-lib-report": "npm:*" + checksum: 10c0/1647fd402aced5b6edac87274af14ebd6b3a85447ef9ad11853a70fd92a98d35f81a5d3ea9fcb5dbb5834e800c6e35b64475e33fcae6bfa9acc70d61497c54ee + languageName: node + linkType: hard + +"@types/jest@npm:^29.2.3": + version: 29.5.12 + resolution: "@types/jest@npm:29.5.12" + dependencies: + expect: "npm:^29.0.0" + pretty-format: "npm:^29.0.0" + checksum: 10c0/25fc8e4c611fa6c4421e631432e9f0a6865a8cb07c9815ec9ac90d630271cad773b2ee5fe08066f7b95bebd18bb967f8ce05d018ee9ab0430f9dfd1d84665b6f + languageName: node + linkType: hard + +"@types/json-schema@npm:^7.0.9": + version: 7.0.15 + resolution: "@types/json-schema@npm:7.0.15" + checksum: 10c0/a996a745e6c5d60292f36731dd41341339d4eeed8180bb09226e5c8d23759067692b1d88e5d91d72ee83dfc00d3aca8e7bd43ea120516c17922cbcb7c3e252db + languageName: node + linkType: hard + +"@types/methods@npm:^1.1.4": + version: 1.1.4 + resolution: "@types/methods@npm:1.1.4" + checksum: 10c0/a78534d79c300718298bfff92facd07bf38429c66191f640c1db4c9cff1e36f819304298a96f7536b6512bfc398e5c3e6b831405e138cd774b88ad7be78d682a + languageName: node + linkType: hard + +"@types/mime@npm:^1": + version: 1.3.5 + resolution: "@types/mime@npm:1.3.5" + checksum: 10c0/c2ee31cd9b993804df33a694d5aa3fa536511a49f2e06eeab0b484fef59b4483777dbb9e42a4198a0809ffbf698081fdbca1e5c2218b82b91603dfab10a10fbc + languageName: node + linkType: hard + +"@types/node@npm:*": + version: 20.14.5 + resolution: "@types/node@npm:20.14.5" + dependencies: + undici-types: "npm:~5.26.4" + checksum: 10c0/06a8c304b5f7f190d4497807dc67ad09ee7b14ea2996bfdc823553c624698d8cab1ef9d16f8b764f20cb9eb11caa0e832787741e9ef70e1c89d620797ab28436 + languageName: node + linkType: hard + +"@types/node@npm:^17.0.45": + version: 17.0.45 + resolution: "@types/node@npm:17.0.45" + checksum: 10c0/0db377133d709b33a47892581a21a41cd7958f22723a3cc6c71d55ac018121382de42fbfc7970d5ae3e7819dbe5f40e1c6a5174aedf7e7964e9cb8fa72b580b0 + languageName: node + linkType: hard + +"@types/qs@npm:*": + version: 6.9.15 + resolution: "@types/qs@npm:6.9.15" + checksum: 10c0/49c5ff75ca3adb18a1939310042d273c9fc55920861bd8e5100c8a923b3cda90d759e1a95e18334092da1c8f7b820084687770c83a1ccef04fb2c6908117c823 + languageName: node + linkType: hard + +"@types/range-parser@npm:*": + version: 1.2.7 + resolution: "@types/range-parser@npm:1.2.7" + checksum: 10c0/361bb3e964ec5133fa40644a0b942279ed5df1949f21321d77de79f48b728d39253e5ce0408c9c17e4e0fd95ca7899da36841686393b9f7a1e209916e9381a3c + languageName: node + linkType: hard + +"@types/semver@npm:^7.3.12": + version: 7.5.8 + resolution: "@types/semver@npm:7.5.8" + checksum: 10c0/8663ff927234d1c5fcc04b33062cb2b9fcfbe0f5f351ed26c4d1e1581657deebd506b41ff7fdf89e787e3d33ce05854bc01686379b89e9c49b564c4cfa988efa + languageName: node + linkType: hard + +"@types/send@npm:*": + version: 0.17.4 + resolution: "@types/send@npm:0.17.4" + dependencies: + "@types/mime": "npm:^1" + "@types/node": "npm:*" + checksum: 10c0/7f17fa696cb83be0a104b04b424fdedc7eaba1c9a34b06027239aba513b398a0e2b7279778af521f516a397ced417c96960e5f50fcfce40c4bc4509fb1a5883c + languageName: node + linkType: hard + +"@types/serve-static@npm:*": + version: 1.15.7 + resolution: "@types/serve-static@npm:1.15.7" + dependencies: + "@types/http-errors": "npm:*" + "@types/node": "npm:*" + "@types/send": "npm:*" + checksum: 10c0/26ec864d3a626ea627f8b09c122b623499d2221bbf2f470127f4c9ebfe92bd8a6bb5157001372d4c4bd0dd37a1691620217d9dc4df5aa8f779f3fd996b1c60ae + languageName: node + linkType: hard + +"@types/stack-utils@npm:^2.0.0": + version: 2.0.3 + resolution: "@types/stack-utils@npm:2.0.3" + checksum: 10c0/1f4658385ae936330581bcb8aa3a066df03867d90281cdf89cc356d404bd6579be0f11902304e1f775d92df22c6dd761d4451c804b0a4fba973e06211e9bd77c + languageName: node + linkType: hard + +"@types/superagent@npm:*": + version: 8.1.7 + resolution: "@types/superagent@npm:8.1.7" + dependencies: + "@types/cookiejar": "npm:^2.1.5" + "@types/methods": "npm:^1.1.4" + "@types/node": "npm:*" + checksum: 10c0/4676d539f5feaaea9d39d7409c86ae9e15b92a43c28456aff9d9897e47e9fe5ebd3807600c5310f84fe5ebea30f3fe5e2b3b101a87821a478ca79e3a56fd8c9e + languageName: node + linkType: hard + +"@types/supertest@npm:^2.0.12": + version: 2.0.16 + resolution: "@types/supertest@npm:2.0.16" + dependencies: + "@types/superagent": "npm:*" + checksum: 10c0/e1b4a4d788c19cd92a3f2e6d0979fb0f679c49aefae2011895a4d9c35aa960d43463aca8783a0b3382bbf0b4eb7ceaf8752d7dc80b8f5a9644fa14e1b1bdbc90 + languageName: node + linkType: hard + +"@types/triple-beam@npm:^1.3.2": + version: 1.3.5 + resolution: "@types/triple-beam@npm:1.3.5" + checksum: 10c0/d5d7f25da612f6d79266f4f1bb9c1ef8f1684e9f60abab251e1261170631062b656ba26ff22631f2760caeafd372abc41e64867cde27fba54fafb73a35b9056a + languageName: node + linkType: hard + +"@types/uuid@npm:^9.0.0": + version: 9.0.8 + resolution: "@types/uuid@npm:9.0.8" + checksum: 10c0/b411b93054cb1d4361919579ef3508a1f12bf15b5fdd97337d3d351bece6c921b52b6daeef89b62340fd73fd60da407878432a1af777f40648cbe53a01723489 + languageName: node + linkType: hard + +"@types/yargs-parser@npm:*": + version: 21.0.3 + resolution: "@types/yargs-parser@npm:21.0.3" + checksum: 10c0/e71c3bd9d0b73ca82e10bee2064c384ab70f61034bbfb78e74f5206283fc16a6d85267b606b5c22cb2a3338373586786fed595b2009825d6a9115afba36560a0 + languageName: node + linkType: hard + +"@types/yargs@npm:^17.0.8": + version: 17.0.32 + resolution: "@types/yargs@npm:17.0.32" + dependencies: + "@types/yargs-parser": "npm:*" + checksum: 10c0/2095e8aad8a4e66b86147415364266b8d607a3b95b4239623423efd7e29df93ba81bb862784a6e08664f645cc1981b25fd598f532019174cd3e5e1e689e1cccf + languageName: node + linkType: hard + +"@typescript-eslint/eslint-plugin@npm:^5.51.0": + version: 5.62.0 + resolution: "@typescript-eslint/eslint-plugin@npm:5.62.0" + dependencies: + "@eslint-community/regexpp": "npm:^4.4.0" + "@typescript-eslint/scope-manager": "npm:5.62.0" + "@typescript-eslint/type-utils": "npm:5.62.0" + "@typescript-eslint/utils": "npm:5.62.0" + debug: "npm:^4.3.4" + graphemer: "npm:^1.4.0" + ignore: "npm:^5.2.0" + natural-compare-lite: "npm:^1.4.0" + semver: "npm:^7.3.7" + tsutils: "npm:^3.21.0" + peerDependencies: + "@typescript-eslint/parser": ^5.0.0 + eslint: ^6.0.0 || ^7.0.0 || ^8.0.0 + peerDependenciesMeta: + typescript: + optional: true + checksum: 10c0/3f40cb6bab5a2833c3544e4621b9fdacd8ea53420cadc1c63fac3b89cdf5c62be1e6b7bcf56976dede5db4c43830de298ced3db60b5494a3b961ca1b4bff9f2a + languageName: node + linkType: hard + +"@typescript-eslint/parser@npm:^5.51.0": + version: 5.62.0 + resolution: "@typescript-eslint/parser@npm:5.62.0" + dependencies: + "@typescript-eslint/scope-manager": "npm:5.62.0" + "@typescript-eslint/types": "npm:5.62.0" + "@typescript-eslint/typescript-estree": "npm:5.62.0" + debug: "npm:^4.3.4" + peerDependencies: + eslint: ^6.0.0 || ^7.0.0 || ^8.0.0 + peerDependenciesMeta: + typescript: + optional: true + checksum: 10c0/315194b3bf39beb9bd16c190956c46beec64b8371e18d6bb72002108b250983eb1e186a01d34b77eb4045f4941acbb243b16155fbb46881105f65e37dc9e24d4 + languageName: node + linkType: hard + +"@typescript-eslint/scope-manager@npm:5.62.0": + version: 5.62.0 + resolution: "@typescript-eslint/scope-manager@npm:5.62.0" + dependencies: + "@typescript-eslint/types": "npm:5.62.0" + "@typescript-eslint/visitor-keys": "npm:5.62.0" + checksum: 10c0/861253235576c1c5c1772d23cdce1418c2da2618a479a7de4f6114a12a7ca853011a1e530525d0931c355a8fd237b9cd828fac560f85f9623e24054fd024726f + languageName: node + linkType: hard + +"@typescript-eslint/type-utils@npm:5.62.0": + version: 5.62.0 + resolution: "@typescript-eslint/type-utils@npm:5.62.0" + dependencies: + "@typescript-eslint/typescript-estree": "npm:5.62.0" + "@typescript-eslint/utils": "npm:5.62.0" + debug: "npm:^4.3.4" + tsutils: "npm:^3.21.0" + peerDependencies: + eslint: "*" + peerDependenciesMeta: + typescript: + optional: true + checksum: 10c0/93112e34026069a48f0484b98caca1c89d9707842afe14e08e7390af51cdde87378df29d213d3bbd10a7cfe6f91b228031b56218515ce077bdb62ddea9d9f474 + languageName: node + linkType: hard + +"@typescript-eslint/types@npm:5.62.0": + version: 5.62.0 + resolution: "@typescript-eslint/types@npm:5.62.0" + checksum: 10c0/7febd3a7f0701c0b927e094f02e82d8ee2cada2b186fcb938bc2b94ff6fbad88237afc304cbaf33e82797078bbbb1baf91475f6400912f8b64c89be79bfa4ddf + languageName: node + linkType: hard + +"@typescript-eslint/typescript-estree@npm:5.62.0": + version: 5.62.0 + resolution: "@typescript-eslint/typescript-estree@npm:5.62.0" + dependencies: + "@typescript-eslint/types": "npm:5.62.0" + "@typescript-eslint/visitor-keys": "npm:5.62.0" + debug: "npm:^4.3.4" + globby: "npm:^11.1.0" + is-glob: "npm:^4.0.3" + semver: "npm:^7.3.7" + tsutils: "npm:^3.21.0" + peerDependenciesMeta: + typescript: + optional: true + checksum: 10c0/d7984a3e9d56897b2481940ec803cb8e7ead03df8d9cfd9797350be82ff765dfcf3cfec04e7355e1779e948da8f02bc5e11719d07a596eb1cb995c48a95e38cf + languageName: node + linkType: hard + +"@typescript-eslint/utils@npm:5.62.0": + version: 5.62.0 + resolution: "@typescript-eslint/utils@npm:5.62.0" + dependencies: + "@eslint-community/eslint-utils": "npm:^4.2.0" + "@types/json-schema": "npm:^7.0.9" + "@types/semver": "npm:^7.3.12" + "@typescript-eslint/scope-manager": "npm:5.62.0" + "@typescript-eslint/types": "npm:5.62.0" + "@typescript-eslint/typescript-estree": "npm:5.62.0" + eslint-scope: "npm:^5.1.1" + semver: "npm:^7.3.7" + peerDependencies: + eslint: ^6.0.0 || ^7.0.0 || ^8.0.0 + checksum: 10c0/f09b7d9952e4a205eb1ced31d7684dd55cee40bf8c2d78e923aa8a255318d97279825733902742c09d8690f37a50243f4c4d383ab16bd7aefaf9c4b438f785e1 + languageName: node + linkType: hard + +"@typescript-eslint/visitor-keys@npm:5.62.0": + version: 5.62.0 + resolution: "@typescript-eslint/visitor-keys@npm:5.62.0" + dependencies: + "@typescript-eslint/types": "npm:5.62.0" + eslint-visitor-keys: "npm:^3.3.0" + checksum: 10c0/7c3b8e4148e9b94d9b7162a596a1260d7a3efc4e65199693b8025c71c4652b8042501c0bc9f57654c1e2943c26da98c0f77884a746c6ae81389fcb0b513d995d + languageName: node + linkType: hard + +"@ungap/structured-clone@npm:^1.2.0": + version: 1.2.0 + resolution: "@ungap/structured-clone@npm:1.2.0" + checksum: 10c0/8209c937cb39119f44eb63cf90c0b73e7c754209a6411c707be08e50e29ee81356dca1a848a405c8bdeebfe2f5e4f831ad310ae1689eeef65e7445c090c6657d + languageName: node + linkType: hard + +"abbrev@npm:1": + version: 1.1.1 + resolution: "abbrev@npm:1.1.1" + checksum: 10c0/3f762677702acb24f65e813070e306c61fafe25d4b2583f9dfc935131f774863f3addd5741572ed576bd69cabe473c5af18e1e108b829cb7b6b4747884f726e6 + languageName: node + linkType: hard + +"abbrev@npm:^2.0.0": + version: 2.0.0 + resolution: "abbrev@npm:2.0.0" + checksum: 10c0/f742a5a107473946f426c691c08daba61a1d15942616f300b5d32fd735be88fef5cba24201757b6c407fd564555fb48c751cfa33519b2605c8a7aadd22baf372 + languageName: node + linkType: hard + +"accepts@npm:~1.3.8": + version: 1.3.8 + resolution: "accepts@npm:1.3.8" + dependencies: + mime-types: "npm:~2.1.34" + negotiator: "npm:0.6.3" + checksum: 10c0/3a35c5f5586cfb9a21163ca47a5f77ac34fa8ceb5d17d2fa2c0d81f41cbd7f8c6fa52c77e2c039acc0f4d09e71abdc51144246900f6bef5e3c4b333f77d89362 + languageName: node + linkType: hard + +"acorn-jsx@npm:^5.3.2": + version: 5.3.2 + resolution: "acorn-jsx@npm:5.3.2" + peerDependencies: + acorn: ^6.0.0 || ^7.0.0 || ^8.0.0 + checksum: 10c0/4c54868fbef3b8d58927d5e33f0a4de35f59012fe7b12cf9dfbb345fb8f46607709e1c4431be869a23fb63c151033d84c4198fa9f79385cec34fcb1dd53974c1 + languageName: node + linkType: hard + +"acorn@npm:^8.9.0": + version: 8.12.0 + resolution: "acorn@npm:8.12.0" + bin: + acorn: bin/acorn + checksum: 10c0/a19f9dead009d3b430fa3c253710b47778cdaace15b316de6de93a68c355507bc1072a9956372b6c990cbeeb167d4a929249d0faeb8ae4bb6911d68d53299549 + languageName: node + linkType: hard + +"actual-sync@workspace:.": + version: 0.0.0-use.local + resolution: "actual-sync@workspace:." + dependencies: + "@actual-app/crdt": "npm:2.1.0" + "@actual-app/web": "npm:24.12.0" + "@babel/preset-typescript": "npm:^7.20.2" + "@types/bcrypt": "npm:^5.0.2" + "@types/better-sqlite3": "npm:^7.6.7" + "@types/cors": "npm:^2.8.13" + "@types/express": "npm:^4.17.17" + "@types/express-actuator": "npm:^1.8.0" + "@types/jest": "npm:^29.2.3" + "@types/node": "npm:^17.0.45" + "@types/supertest": "npm:^2.0.12" + "@types/uuid": "npm:^9.0.0" + "@typescript-eslint/eslint-plugin": "npm:^5.51.0" + "@typescript-eslint/parser": "npm:^5.51.0" + bcrypt: "npm:^5.1.1" + better-sqlite3: "npm:^9.6.0" + body-parser: "npm:^1.20.3" + cors: "npm:^2.8.5" + date-fns: "npm:^2.30.0" + debug: "npm:^4.3.4" + eslint: "npm:^8.33.0" + eslint-plugin-prettier: "npm:^4.2.1" + express: "npm:4.20.0" + express-actuator: "npm:1.8.4" + express-rate-limit: "npm:^6.7.0" + express-response-size: "npm:^0.0.3" + express-winston: "npm:^4.2.0" + jest: "npm:^29.3.1" + jws: "npm:^4.0.0" + migrate: "npm:^2.0.1" + nordigen-node: "npm:^1.4.0" + openid-client: "npm:^5.4.2" + prettier: "npm:^2.8.3" + supertest: "npm:^6.3.1" + typescript: "npm:^4.9.5" + uuid: "npm:^9.0.0" + winston: "npm:^3.14.2" + languageName: unknown + linkType: soft + +"agent-base@npm:6": + version: 6.0.2 + resolution: "agent-base@npm:6.0.2" + dependencies: + debug: "npm:4" + checksum: 10c0/dc4f757e40b5f3e3d674bc9beb4f1048f4ee83af189bae39be99f57bf1f48dde166a8b0a5342a84b5944ee8e6ed1e5a9d801858f4ad44764e84957122fe46261 + languageName: node + linkType: hard + +"agent-base@npm:^7.0.2, agent-base@npm:^7.1.0, agent-base@npm:^7.1.1": + version: 7.1.1 + resolution: "agent-base@npm:7.1.1" + dependencies: + debug: "npm:^4.3.4" + checksum: 10c0/e59ce7bed9c63bf071a30cc471f2933862044c97fd9958967bfe22521d7a0f601ce4ed5a8c011799d0c726ca70312142ae193bbebb60f576b52be19d4a363b50 + languageName: node + linkType: hard + +"aggregate-error@npm:^3.0.0": + version: 3.1.0 + resolution: "aggregate-error@npm:3.1.0" + dependencies: + clean-stack: "npm:^2.0.0" + indent-string: "npm:^4.0.0" + checksum: 10c0/a42f67faa79e3e6687a4923050e7c9807db3848a037076f791d10e092677d65c1d2d863b7848560699f40fc0502c19f40963fb1cd1fb3d338a7423df8e45e039 + languageName: node + linkType: hard + +"ajv@npm:^6.12.4": + version: 6.12.6 + resolution: "ajv@npm:6.12.6" + dependencies: + fast-deep-equal: "npm:^3.1.1" + fast-json-stable-stringify: "npm:^2.0.0" + json-schema-traverse: "npm:^0.4.1" + uri-js: "npm:^4.2.2" + checksum: 10c0/41e23642cbe545889245b9d2a45854ebba51cda6c778ebced9649420d9205f2efb39cb43dbc41e358409223b1ea43303ae4839db682c848b891e4811da1a5a71 + languageName: node + linkType: hard + +"ansi-escapes@npm:^4.2.1": + version: 4.3.2 + resolution: "ansi-escapes@npm:4.3.2" + dependencies: + type-fest: "npm:^0.21.3" + checksum: 10c0/da917be01871525a3dfcf925ae2977bc59e8c513d4423368645634bf5d4ceba5401574eb705c1e92b79f7292af5a656f78c5725a4b0e1cec97c4b413705c1d50 + languageName: node + linkType: hard + +"ansi-regex@npm:^5.0.1": + version: 5.0.1 + resolution: "ansi-regex@npm:5.0.1" + checksum: 10c0/9a64bb8627b434ba9327b60c027742e5d17ac69277960d041898596271d992d4d52ba7267a63ca10232e29f6107fc8a835f6ce8d719b88c5f8493f8254813737 + languageName: node + linkType: hard + +"ansi-regex@npm:^6.0.1": + version: 6.0.1 + resolution: "ansi-regex@npm:6.0.1" + checksum: 10c0/cbe16dbd2c6b2735d1df7976a7070dd277326434f0212f43abf6d87674095d247968209babdaad31bb00882fa68807256ba9be340eec2f1004de14ca75f52a08 + languageName: node + linkType: hard + +"ansi-styles@npm:^3.2.1": + version: 3.2.1 + resolution: "ansi-styles@npm:3.2.1" + dependencies: + color-convert: "npm:^1.9.0" + checksum: 10c0/ece5a8ef069fcc5298f67e3f4771a663129abd174ea2dfa87923a2be2abf6cd367ef72ac87942da00ce85bd1d651d4cd8595aebdb1b385889b89b205860e977b + languageName: node + linkType: hard + +"ansi-styles@npm:^4.0.0, ansi-styles@npm:^4.1.0": + version: 4.3.0 + resolution: "ansi-styles@npm:4.3.0" + dependencies: + color-convert: "npm:^2.0.1" + checksum: 10c0/895a23929da416f2bd3de7e9cb4eabd340949328ab85ddd6e484a637d8f6820d485f53933446f5291c3b760cbc488beb8e88573dd0f9c7daf83dccc8fe81b041 + languageName: node + linkType: hard + +"ansi-styles@npm:^5.0.0": + version: 5.2.0 + resolution: "ansi-styles@npm:5.2.0" + checksum: 10c0/9c4ca80eb3c2fb7b33841c210d2f20807f40865d27008d7c3f707b7f95cab7d67462a565e2388ac3285b71cb3d9bb2173de8da37c57692a362885ec34d6e27df + languageName: node + linkType: hard + +"ansi-styles@npm:^6.1.0": + version: 6.2.1 + resolution: "ansi-styles@npm:6.2.1" + checksum: 10c0/5d1ec38c123984bcedd996eac680d548f31828bd679a66db2bdf11844634dde55fec3efa9c6bb1d89056a5e79c1ac540c4c784d592ea1d25028a92227d2f2d5c + languageName: node + linkType: hard + +"anymatch@npm:^3.0.3": + version: 3.1.3 + resolution: "anymatch@npm:3.1.3" + dependencies: + normalize-path: "npm:^3.0.0" + picomatch: "npm:^2.0.4" + checksum: 10c0/57b06ae984bc32a0d22592c87384cd88fe4511b1dd7581497831c56d41939c8a001b28e7b853e1450f2bf61992dfcaa8ae2d0d161a0a90c4fb631ef07098fbac + languageName: node + linkType: hard + +"aproba@npm:^1.0.3 || ^2.0.0": + version: 2.0.0 + resolution: "aproba@npm:2.0.0" + checksum: 10c0/d06e26384a8f6245d8c8896e138c0388824e259a329e0c9f196b4fa533c82502a6fd449586e3604950a0c42921832a458bb3aa0aa9f0ba449cfd4f50fd0d09b5 + languageName: node + linkType: hard + +"are-we-there-yet@npm:^2.0.0": + version: 2.0.0 + resolution: "are-we-there-yet@npm:2.0.0" + dependencies: + delegates: "npm:^1.0.0" + readable-stream: "npm:^3.6.0" + checksum: 10c0/375f753c10329153c8d66dc95e8f8b6c7cc2aa66e05cb0960bd69092b10dae22900cacc7d653ad11d26b3ecbdbfe1e8bfb6ccf0265ba8077a7d979970f16b99c + languageName: node + linkType: hard + +"argparse@npm:^1.0.7": + version: 1.0.10 + resolution: "argparse@npm:1.0.10" + dependencies: + sprintf-js: "npm:~1.0.2" + checksum: 10c0/b2972c5c23c63df66bca144dbc65d180efa74f25f8fd9b7d9a0a6c88ae839db32df3d54770dcb6460cf840d232b60695d1a6b1053f599d84e73f7437087712de + languageName: node + linkType: hard + +"argparse@npm:^2.0.1": + version: 2.0.1 + resolution: "argparse@npm:2.0.1" + checksum: 10c0/c5640c2d89045371c7cedd6a70212a04e360fd34d6edeae32f6952c63949e3525ea77dbec0289d8213a99bbaeab5abfa860b5c12cf88a2e6cf8106e90dd27a7e + languageName: node + linkType: hard + +"array-flatten@npm:1.1.1": + version: 1.1.1 + resolution: "array-flatten@npm:1.1.1" + checksum: 10c0/806966c8abb2f858b08f5324d9d18d7737480610f3bd5d3498aaae6eb5efdc501a884ba019c9b4a8f02ff67002058749d05548fd42fa8643f02c9c7f22198b91 + languageName: node + linkType: hard + +"array-union@npm:^2.1.0": + version: 2.1.0 + resolution: "array-union@npm:2.1.0" + checksum: 10c0/429897e68110374f39b771ec47a7161fc6a8fc33e196857c0a396dc75df0b5f65e4d046674db764330b6bb66b39ef48dd7c53b6a2ee75cfb0681e0c1a7033962 + languageName: node + linkType: hard + +"asap@npm:^2.0.0": + version: 2.0.6 + resolution: "asap@npm:2.0.6" + checksum: 10c0/c6d5e39fe1f15e4b87677460bd66b66050cd14c772269cee6688824c1410a08ab20254bb6784f9afb75af9144a9f9a7692d49547f4d19d715aeb7c0318f3136d + languageName: node + linkType: hard + +"async@npm:^3.2.3": + version: 3.2.5 + resolution: "async@npm:3.2.5" + checksum: 10c0/1408287b26c6db67d45cb346e34892cee555b8b59e6c68e6f8c3e495cad5ca13b4f218180e871f3c2ca30df4ab52693b66f2f6ff43644760cab0b2198bda79c1 + languageName: node + linkType: hard + +"asynckit@npm:^0.4.0": + version: 0.4.0 + resolution: "asynckit@npm:0.4.0" + checksum: 10c0/d73e2ddf20c4eb9337e1b3df1a0f6159481050a5de457c55b14ea2e5cb6d90bb69e004c9af54737a5ee0917fcf2c9e25de67777bbe58261847846066ba75bc9d + languageName: node + linkType: hard + +"axios@npm:^1.2.1": + version: 1.7.4 + resolution: "axios@npm:1.7.4" + dependencies: + follow-redirects: "npm:^1.15.6" + form-data: "npm:^4.0.0" + proxy-from-env: "npm:^1.1.0" + checksum: 10c0/5ea1a93140ca1d49db25ef8e1bd8cfc59da6f9220159a944168860ad15a2743ea21c5df2967795acb15cbe81362f5b157fdebbea39d53117ca27658bab9f7f17 + languageName: node + linkType: hard + +"babel-jest@npm:^29.7.0": + version: 29.7.0 + resolution: "babel-jest@npm:29.7.0" + dependencies: + "@jest/transform": "npm:^29.7.0" + "@types/babel__core": "npm:^7.1.14" + babel-plugin-istanbul: "npm:^6.1.1" + babel-preset-jest: "npm:^29.6.3" + chalk: "npm:^4.0.0" + graceful-fs: "npm:^4.2.9" + slash: "npm:^3.0.0" + peerDependencies: + "@babel/core": ^7.8.0 + checksum: 10c0/2eda9c1391e51936ca573dd1aedfee07b14c59b33dbe16ef347873ddd777bcf6e2fc739681e9e9661ab54ef84a3109a03725be2ac32cd2124c07ea4401cbe8c1 + languageName: node + linkType: hard + +"babel-plugin-istanbul@npm:^6.1.1": + version: 6.1.1 + resolution: "babel-plugin-istanbul@npm:6.1.1" + dependencies: + "@babel/helper-plugin-utils": "npm:^7.0.0" + "@istanbuljs/load-nyc-config": "npm:^1.0.0" + "@istanbuljs/schema": "npm:^0.1.2" + istanbul-lib-instrument: "npm:^5.0.4" + test-exclude: "npm:^6.0.0" + checksum: 10c0/1075657feb705e00fd9463b329921856d3775d9867c5054b449317d39153f8fbcebd3e02ebf00432824e647faff3683a9ca0a941325ef1afe9b3c4dd51b24beb + languageName: node + linkType: hard + +"babel-plugin-jest-hoist@npm:^29.6.3": + version: 29.6.3 + resolution: "babel-plugin-jest-hoist@npm:29.6.3" + dependencies: + "@babel/template": "npm:^7.3.3" + "@babel/types": "npm:^7.3.3" + "@types/babel__core": "npm:^7.1.14" + "@types/babel__traverse": "npm:^7.0.6" + checksum: 10c0/7e6451caaf7dce33d010b8aafb970e62f1b0c0b57f4978c37b0d457bbcf0874d75a395a102daf0bae0bd14eafb9f6e9a165ee5e899c0a4f1f3bb2e07b304ed2e + languageName: node + linkType: hard + +"babel-preset-current-node-syntax@npm:^1.0.0": + version: 1.0.1 + resolution: "babel-preset-current-node-syntax@npm:1.0.1" + dependencies: + "@babel/plugin-syntax-async-generators": "npm:^7.8.4" + "@babel/plugin-syntax-bigint": "npm:^7.8.3" + "@babel/plugin-syntax-class-properties": "npm:^7.8.3" + "@babel/plugin-syntax-import-meta": "npm:^7.8.3" + "@babel/plugin-syntax-json-strings": "npm:^7.8.3" + "@babel/plugin-syntax-logical-assignment-operators": "npm:^7.8.3" + "@babel/plugin-syntax-nullish-coalescing-operator": "npm:^7.8.3" + "@babel/plugin-syntax-numeric-separator": "npm:^7.8.3" + "@babel/plugin-syntax-object-rest-spread": "npm:^7.8.3" + "@babel/plugin-syntax-optional-catch-binding": "npm:^7.8.3" + "@babel/plugin-syntax-optional-chaining": "npm:^7.8.3" + "@babel/plugin-syntax-top-level-await": "npm:^7.8.3" + peerDependencies: + "@babel/core": ^7.0.0 + checksum: 10c0/5ba39a3a0e6c37d25e56a4fb843be632dac98d54706d8a0933f9bcb1a07987a96d55c2b5a6c11788a74063fb2534fe68c1f1dbb6c93626850c785e0938495627 + languageName: node + linkType: hard + +"babel-preset-jest@npm:^29.6.3": + version: 29.6.3 + resolution: "babel-preset-jest@npm:29.6.3" + dependencies: + babel-plugin-jest-hoist: "npm:^29.6.3" + babel-preset-current-node-syntax: "npm:^1.0.0" + peerDependencies: + "@babel/core": ^7.0.0 + checksum: 10c0/ec5fd0276b5630b05f0c14bb97cc3815c6b31600c683ebb51372e54dcb776cff790bdeeabd5b8d01ede375a040337ccbf6a3ccd68d3a34219125945e167ad943 + languageName: node + linkType: hard + +"balanced-match@npm:^1.0.0": + version: 1.0.2 + resolution: "balanced-match@npm:1.0.2" + checksum: 10c0/9308baf0a7e4838a82bbfd11e01b1cb0f0cf2893bc1676c27c2a8c0e70cbae1c59120c3268517a8ae7fb6376b4639ef81ca22582611dbee4ed28df945134aaee + languageName: node + linkType: hard + +"base64-js@npm:^1.3.1": + version: 1.5.1 + resolution: "base64-js@npm:1.5.1" + checksum: 10c0/f23823513b63173a001030fae4f2dabe283b99a9d324ade3ad3d148e218134676f1ee8568c877cd79ec1c53158dcf2d2ba527a97c606618928ba99dd930102bf + languageName: node + linkType: hard + +"bcrypt@npm:^5.1.1": + version: 5.1.1 + resolution: "bcrypt@npm:5.1.1" + dependencies: + "@mapbox/node-pre-gyp": "npm:^1.0.11" + node-addon-api: "npm:^5.0.0" + checksum: 10c0/743231158c866bddc46f25eb8e9617fe38bc1a6f5f3052aba35e361d349b7f8fb80e96b45c48a4c23c45c29967ccd11c81cf31166454fc0ab019801c336cab40 + languageName: node + linkType: hard + +"better-sqlite3@npm:^9.6.0": + version: 9.6.0 + resolution: "better-sqlite3@npm:9.6.0" + dependencies: + bindings: "npm:^1.5.0" + node-gyp: "npm:latest" + prebuild-install: "npm:^7.1.1" + checksum: 10c0/8db9b38f414e26a56d4c40fc16e94a253118491dae0e2c054338a9e470f1a883c7eb4cb330f2f5737db30f704d4f2e697c59071ca04e03364ee9fe04375aa9c8 + languageName: node + linkType: hard + +"bindings@npm:^1.5.0": + version: 1.5.0 + resolution: "bindings@npm:1.5.0" + dependencies: + file-uri-to-path: "npm:1.0.0" + checksum: 10c0/3dab2491b4bb24124252a91e656803eac24292473e56554e35bbfe3cc1875332cfa77600c3bac7564049dc95075bf6fcc63a4609920ff2d64d0fe405fcf0d4ba + languageName: node + linkType: hard + +"bl@npm:^4.0.3": + version: 4.1.0 + resolution: "bl@npm:4.1.0" + dependencies: + buffer: "npm:^5.5.0" + inherits: "npm:^2.0.4" + readable-stream: "npm:^3.4.0" + checksum: 10c0/02847e1d2cb089c9dc6958add42e3cdeaf07d13f575973963335ac0fdece563a50ac770ac4c8fa06492d2dd276f6cc3b7f08c7cd9c7a7ad0f8d388b2a28def5f + languageName: node + linkType: hard + +"body-parser@npm:1.20.3, body-parser@npm:^1.20.3": + version: 1.20.3 + resolution: "body-parser@npm:1.20.3" + dependencies: + bytes: "npm:3.1.2" + content-type: "npm:~1.0.5" + debug: "npm:2.6.9" + depd: "npm:2.0.0" + destroy: "npm:1.2.0" + http-errors: "npm:2.0.0" + iconv-lite: "npm:0.4.24" + on-finished: "npm:2.4.1" + qs: "npm:6.13.0" + raw-body: "npm:2.5.2" + type-is: "npm:~1.6.18" + unpipe: "npm:1.0.0" + checksum: 10c0/0a9a93b7518f222885498dcecaad528cf010dd109b071bf471c93def4bfe30958b83e03496eb9c1ad4896db543d999bb62be1a3087294162a88cfa1b42c16310 + languageName: node + linkType: hard + +"brace-expansion@npm:^1.1.7": + version: 1.1.11 + resolution: "brace-expansion@npm:1.1.11" + dependencies: + balanced-match: "npm:^1.0.0" + concat-map: "npm:0.0.1" + checksum: 10c0/695a56cd058096a7cb71fb09d9d6a7070113c7be516699ed361317aca2ec169f618e28b8af352e02ab4233fb54eb0168460a40dc320bab0034b36ab59aaad668 + languageName: node + linkType: hard + +"brace-expansion@npm:^2.0.1": + version: 2.0.1 + resolution: "brace-expansion@npm:2.0.1" + dependencies: + balanced-match: "npm:^1.0.0" + checksum: 10c0/b358f2fe060e2d7a87aa015979ecea07f3c37d4018f8d6deb5bd4c229ad3a0384fe6029bb76cd8be63c81e516ee52d1a0673edbe2023d53a5191732ae3c3e49f + languageName: node + linkType: hard + +"braces@npm:^3.0.3": + version: 3.0.3 + resolution: "braces@npm:3.0.3" + dependencies: + fill-range: "npm:^7.1.1" + checksum: 10c0/7c6dfd30c338d2997ba77500539227b9d1f85e388a5f43220865201e407e076783d0881f2d297b9f80951b4c957fcf0b51c1d2d24227631643c3f7c284b0aa04 + languageName: node + linkType: hard + +"browserslist@npm:^4.22.2": + version: 4.23.1 + resolution: "browserslist@npm:4.23.1" + dependencies: + caniuse-lite: "npm:^1.0.30001629" + electron-to-chromium: "npm:^1.4.796" + node-releases: "npm:^2.0.14" + update-browserslist-db: "npm:^1.0.16" + bin: + browserslist: cli.js + checksum: 10c0/eb47c7ab9d60db25ce2faca70efeb278faa7282a2f62b7f2fa2f92e5f5251cf65144244566c86559419ff4f6d78f59ea50e39911321ad91f3b27788901f1f5e9 + languageName: node + linkType: hard + +"bser@npm:2.1.1": + version: 2.1.1 + resolution: "bser@npm:2.1.1" + dependencies: + node-int64: "npm:^0.4.0" + checksum: 10c0/24d8dfb7b6d457d73f32744e678a60cc553e4ec0e9e1a01cf614b44d85c3c87e188d3cc78ef0442ce5032ee6818de20a0162ba1074725c0d08908f62ea979227 + languageName: node + linkType: hard + +"buffer-equal-constant-time@npm:1.0.1": + version: 1.0.1 + resolution: "buffer-equal-constant-time@npm:1.0.1" + checksum: 10c0/fb2294e64d23c573d0dd1f1e7a466c3e978fe94a4e0f8183937912ca374619773bef8e2aceb854129d2efecbbc515bbd0cc78d2734a3e3031edb0888531bbc8e + languageName: node + linkType: hard + +"buffer-from@npm:^1.0.0": + version: 1.1.2 + resolution: "buffer-from@npm:1.1.2" + checksum: 10c0/124fff9d66d691a86d3b062eff4663fe437a9d9ee4b47b1b9e97f5a5d14f6d5399345db80f796827be7c95e70a8e765dd404b7c3ff3b3324f98e9b0c8826cc34 + languageName: node + linkType: hard + +"buffer@npm:^5.5.0": + version: 5.7.1 + resolution: "buffer@npm:5.7.1" + dependencies: + base64-js: "npm:^1.3.1" + ieee754: "npm:^1.1.13" + checksum: 10c0/27cac81cff434ed2876058d72e7c4789d11ff1120ef32c9de48f59eab58179b66710c488987d295ae89a228f835fc66d088652dffeb8e3ba8659f80eb091d55e + languageName: node + linkType: hard + +"bytes@npm:3.1.2": + version: 3.1.2 + resolution: "bytes@npm:3.1.2" + checksum: 10c0/76d1c43cbd602794ad8ad2ae94095cddeb1de78c5dddaa7005c51af10b0176c69971a6d88e805a90c2b6550d76636e43c40d8427a808b8645ede885de4a0358e + languageName: node + linkType: hard + +"cacache@npm:^18.0.0": + version: 18.0.3 + resolution: "cacache@npm:18.0.3" + dependencies: + "@npmcli/fs": "npm:^3.1.0" + fs-minipass: "npm:^3.0.0" + glob: "npm:^10.2.2" + lru-cache: "npm:^10.0.1" + minipass: "npm:^7.0.3" + minipass-collect: "npm:^2.0.1" + minipass-flush: "npm:^1.0.5" + minipass-pipeline: "npm:^1.2.4" + p-map: "npm:^4.0.0" + ssri: "npm:^10.0.0" + tar: "npm:^6.1.11" + unique-filename: "npm:^3.0.0" + checksum: 10c0/dfda92840bb371fb66b88c087c61a74544363b37a265023223a99965b16a16bbb87661fe4948718d79df6e0cc04e85e62784fbcf1832b2a5e54ff4c46fbb45b7 + languageName: node + linkType: hard + +"call-bind@npm:^1.0.7": + version: 1.0.7 + resolution: "call-bind@npm:1.0.7" + dependencies: + es-define-property: "npm:^1.0.0" + es-errors: "npm:^1.3.0" + function-bind: "npm:^1.1.2" + get-intrinsic: "npm:^1.2.4" + set-function-length: "npm:^1.2.1" + checksum: 10c0/a3ded2e423b8e2a265983dba81c27e125b48eefb2655e7dfab6be597088da3d47c47976c24bc51b8fd9af1061f8f87b4ab78a314f3c77784b2ae2ba535ad8b8d + languageName: node + linkType: hard + +"callsites@npm:^3.0.0": + version: 3.1.0 + resolution: "callsites@npm:3.1.0" + checksum: 10c0/fff92277400eb06c3079f9e74f3af120db9f8ea03bad0e84d9aede54bbe2d44a56cccb5f6cf12211f93f52306df87077ecec5b712794c5a9b5dac6d615a3f301 + languageName: node + linkType: hard + +"camelcase@npm:^5.3.1": + version: 5.3.1 + resolution: "camelcase@npm:5.3.1" + checksum: 10c0/92ff9b443bfe8abb15f2b1513ca182d16126359ad4f955ebc83dc4ddcc4ef3fdd2c078bc223f2673dc223488e75c99b16cc4d056624374b799e6a1555cf61b23 + languageName: node + linkType: hard + +"camelcase@npm:^6.2.0": + version: 6.3.0 + resolution: "camelcase@npm:6.3.0" + checksum: 10c0/0d701658219bd3116d12da3eab31acddb3f9440790c0792e0d398f0a520a6a4058018e546862b6fba89d7ae990efaeb97da71e1913e9ebf5a8b5621a3d55c710 + languageName: node + linkType: hard + +"caniuse-lite@npm:^1.0.30001629": + version: 1.0.30001636 + resolution: "caniuse-lite@npm:1.0.30001636" + checksum: 10c0/e5f965b4da7bae1531fd9f93477d015729ff9e3fa12670ead39a9e6cdc4c43e62c272d47857c5cc332e7b02d697cb3f2f965a1030870ac7476da60c2fc81ee94 + languageName: node + linkType: hard + +"chalk@npm:^2.4.2": + version: 2.4.2 + resolution: "chalk@npm:2.4.2" + dependencies: + ansi-styles: "npm:^3.2.1" + escape-string-regexp: "npm:^1.0.5" + supports-color: "npm:^5.3.0" + checksum: 10c0/e6543f02ec877732e3a2d1c3c3323ddb4d39fbab687c23f526e25bd4c6a9bf3b83a696e8c769d078e04e5754921648f7821b2a2acfd16c550435fd630026e073 + languageName: node + linkType: hard + +"chalk@npm:^4.0.0, chalk@npm:^4.1.2": + version: 4.1.2 + resolution: "chalk@npm:4.1.2" + dependencies: + ansi-styles: "npm:^4.1.0" + supports-color: "npm:^7.1.0" + checksum: 10c0/4a3fef5cc34975c898ffe77141450f679721df9dde00f6c304353fa9c8b571929123b26a0e4617bde5018977eb655b31970c297b91b63ee83bb82aeb04666880 + languageName: node + linkType: hard + +"char-regex@npm:^1.0.2": + version: 1.0.2 + resolution: "char-regex@npm:1.0.2" + checksum: 10c0/57a09a86371331e0be35d9083ba429e86c4f4648ecbe27455dbfb343037c16ee6fdc7f6b61f433a57cc5ded5561d71c56a150e018f40c2ffb7bc93a26dae341e + languageName: node + linkType: hard + +"chownr@npm:^1.1.1": + version: 1.1.4 + resolution: "chownr@npm:1.1.4" + checksum: 10c0/ed57952a84cc0c802af900cf7136de643d3aba2eecb59d29344bc2f3f9bf703a301b9d84cdc71f82c3ffc9ccde831b0d92f5b45f91727d6c9da62f23aef9d9db + languageName: node + linkType: hard + +"chownr@npm:^2.0.0": + version: 2.0.0 + resolution: "chownr@npm:2.0.0" + checksum: 10c0/594754e1303672171cc04e50f6c398ae16128eb134a88f801bf5354fd96f205320f23536a045d9abd8b51024a149696e51231565891d4efdab8846021ecf88e6 + languageName: node + linkType: hard + +"ci-info@npm:^3.2.0": + version: 3.9.0 + resolution: "ci-info@npm:3.9.0" + checksum: 10c0/6f0109e36e111684291d46123d491bc4e7b7a1934c3a20dea28cba89f1d4a03acd892f5f6a81ed3855c38647e285a150e3c9ba062e38943bef57fee6c1554c3a + languageName: node + linkType: hard + +"cjs-module-lexer@npm:^1.0.0": + version: 1.3.1 + resolution: "cjs-module-lexer@npm:1.3.1" + checksum: 10c0/cd98fbf3c7f4272fb0ebf71d08d0c54bc75ce0e30b9d186114e15b4ba791f3d310af65a339eea2a0318599af2818cdd8886d353b43dfab94468f72987397ad16 + languageName: node + linkType: hard + +"clean-stack@npm:^2.0.0": + version: 2.2.0 + resolution: "clean-stack@npm:2.2.0" + checksum: 10c0/1f90262d5f6230a17e27d0c190b09d47ebe7efdd76a03b5a1127863f7b3c9aec4c3e6c8bb3a7bbf81d553d56a1fd35728f5a8ef4c63f867ac8d690109742a8c1 + languageName: node + linkType: hard + +"cliui@npm:^8.0.1": + version: 8.0.1 + resolution: "cliui@npm:8.0.1" + dependencies: + string-width: "npm:^4.2.0" + strip-ansi: "npm:^6.0.1" + wrap-ansi: "npm:^7.0.0" + checksum: 10c0/4bda0f09c340cbb6dfdc1ed508b3ca080f12992c18d68c6be4d9cf51756033d5266e61ec57529e610dacbf4da1c634423b0c1b11037709cc6b09045cbd815df5 + languageName: node + linkType: hard + +"co@npm:^4.6.0": + version: 4.6.0 + resolution: "co@npm:4.6.0" + checksum: 10c0/c0e85ea0ca8bf0a50cbdca82efc5af0301240ca88ebe3644a6ffb8ffe911f34d40f8fbcf8f1d52c5ddd66706abd4d3bfcd64259f1e8e2371d4f47573b0dc8c28 + languageName: node + linkType: hard + +"collect-v8-coverage@npm:^1.0.0": + version: 1.0.2 + resolution: "collect-v8-coverage@npm:1.0.2" + checksum: 10c0/ed7008e2e8b6852c5483b444a3ae6e976e088d4335a85aa0a9db2861c5f1d31bd2d7ff97a60469b3388deeba661a619753afbe201279fb159b4b9548ab8269a1 + languageName: node + linkType: hard + +"color-convert@npm:^1.9.0, color-convert@npm:^1.9.3": + version: 1.9.3 + resolution: "color-convert@npm:1.9.3" + dependencies: + color-name: "npm:1.1.3" + checksum: 10c0/5ad3c534949a8c68fca8fbc6f09068f435f0ad290ab8b2f76841b9e6af7e0bb57b98cb05b0e19fe33f5d91e5a8611ad457e5f69e0a484caad1f7487fd0e8253c + languageName: node + linkType: hard + +"color-convert@npm:^2.0.1": + version: 2.0.1 + resolution: "color-convert@npm:2.0.1" + dependencies: + color-name: "npm:~1.1.4" + checksum: 10c0/37e1150172f2e311fe1b2df62c6293a342ee7380da7b9cfdba67ea539909afbd74da27033208d01d6d5cfc65ee7868a22e18d7e7648e004425441c0f8a15a7d7 + languageName: node + linkType: hard + +"color-name@npm:1.1.3": + version: 1.1.3 + resolution: "color-name@npm:1.1.3" + checksum: 10c0/566a3d42cca25b9b3cd5528cd7754b8e89c0eb646b7f214e8e2eaddb69994ac5f0557d9c175eb5d8f0ad73531140d9c47525085ee752a91a2ab15ab459caf6d6 + languageName: node + linkType: hard + +"color-name@npm:^1.0.0, color-name@npm:~1.1.4": + version: 1.1.4 + resolution: "color-name@npm:1.1.4" + checksum: 10c0/a1a3f914156960902f46f7f56bc62effc6c94e84b2cae157a526b1c1f74b677a47ec602bf68a61abfa2b42d15b7c5651c6dbe72a43af720bc588dff885b10f95 + languageName: node + linkType: hard + +"color-string@npm:^1.6.0": + version: 1.9.1 + resolution: "color-string@npm:1.9.1" + dependencies: + color-name: "npm:^1.0.0" + simple-swizzle: "npm:^0.2.2" + checksum: 10c0/b0bfd74c03b1f837f543898b512f5ea353f71630ccdd0d66f83028d1f0924a7d4272deb278b9aef376cacf1289b522ac3fb175e99895283645a2dc3a33af2404 + languageName: node + linkType: hard + +"color-support@npm:^1.1.2": + version: 1.1.3 + resolution: "color-support@npm:1.1.3" + bin: + color-support: bin.js + checksum: 10c0/8ffeaa270a784dc382f62d9be0a98581db43e11eee301af14734a6d089bd456478b1a8b3e7db7ca7dc5b18a75f828f775c44074020b51c05fc00e6d0992b1cc6 + languageName: node + linkType: hard + +"color@npm:^3.1.3": + version: 3.2.1 + resolution: "color@npm:3.2.1" + dependencies: + color-convert: "npm:^1.9.3" + color-string: "npm:^1.6.0" + checksum: 10c0/39345d55825884c32a88b95127d417a2c24681d8b57069413596d9fcbb721459ef9d9ec24ce3e65527b5373ce171b73e38dbcd9c830a52a6487e7f37bf00e83c + languageName: node + linkType: hard + +"colorspace@npm:1.1.x": + version: 1.1.4 + resolution: "colorspace@npm:1.1.4" + dependencies: + color: "npm:^3.1.3" + text-hex: "npm:1.0.x" + checksum: 10c0/af5f91ff7f8e146b96e439ac20ed79b197210193bde721b47380a75b21751d90fa56390c773bb67c0aedd34ff85091883a437ab56861c779bd507d639ba7e123 + languageName: node + linkType: hard + +"combined-stream@npm:^1.0.8": + version: 1.0.8 + resolution: "combined-stream@npm:1.0.8" + dependencies: + delayed-stream: "npm:~1.0.0" + checksum: 10c0/0dbb829577e1b1e839fa82b40c07ffaf7de8a09b935cadd355a73652ae70a88b4320db322f6634a4ad93424292fa80973ac6480986247f1734a1137debf271d5 + languageName: node + linkType: hard + +"commander@npm:^2.20.3": + version: 2.20.3 + resolution: "commander@npm:2.20.3" + checksum: 10c0/74c781a5248c2402a0a3e966a0a2bba3c054aad144f5c023364be83265e796b20565aa9feff624132ff629aa64e16999fa40a743c10c12f7c61e96a794b99288 + languageName: node + linkType: hard + +"component-emitter@npm:^1.3.0": + version: 1.3.1 + resolution: "component-emitter@npm:1.3.1" + checksum: 10c0/e4900b1b790b5e76b8d71b328da41482118c0f3523a516a41be598dc2785a07fd721098d9bf6e22d89b19f4fa4e1025160dc00317ea111633a3e4f75c2b86032 + languageName: node + linkType: hard + +"concat-map@npm:0.0.1": + version: 0.0.1 + resolution: "concat-map@npm:0.0.1" + checksum: 10c0/c996b1cfdf95b6c90fee4dae37e332c8b6eb7d106430c17d538034c0ad9a1630cb194d2ab37293b1bdd4d779494beee7786d586a50bd9376fd6f7bcc2bd4c98f + languageName: node + linkType: hard + +"console-control-strings@npm:^1.0.0, console-control-strings@npm:^1.1.0": + version: 1.1.0 + resolution: "console-control-strings@npm:1.1.0" + checksum: 10c0/7ab51d30b52d461412cd467721bb82afe695da78fff8f29fe6f6b9cbaac9a2328e27a22a966014df9532100f6dd85370460be8130b9c677891ba36d96a343f50 + languageName: node + linkType: hard + +"content-disposition@npm:0.5.4": + version: 0.5.4 + resolution: "content-disposition@npm:0.5.4" + dependencies: + safe-buffer: "npm:5.2.1" + checksum: 10c0/bac0316ebfeacb8f381b38285dc691c9939bf0a78b0b7c2d5758acadad242d04783cee5337ba7d12a565a19075af1b3c11c728e1e4946de73c6ff7ce45f3f1bb + languageName: node + linkType: hard + +"content-type@npm:~1.0.4, content-type@npm:~1.0.5": + version: 1.0.5 + resolution: "content-type@npm:1.0.5" + checksum: 10c0/b76ebed15c000aee4678c3707e0860cb6abd4e680a598c0a26e17f0bfae723ec9cc2802f0ff1bc6e4d80603719010431d2231018373d4dde10f9ccff9dadf5af + languageName: node + linkType: hard + +"convert-source-map@npm:^2.0.0": + version: 2.0.0 + resolution: "convert-source-map@npm:2.0.0" + checksum: 10c0/8f2f7a27a1a011cc6cc88cc4da2d7d0cfa5ee0369508baae3d98c260bb3ac520691464e5bbe4ae7cdf09860c1d69ecc6f70c63c6e7c7f7e3f18ec08484dc7d9b + languageName: node + linkType: hard + +"cookie-signature@npm:1.0.6": + version: 1.0.6 + resolution: "cookie-signature@npm:1.0.6" + checksum: 10c0/b36fd0d4e3fef8456915fcf7742e58fbfcc12a17a018e0eb9501c9d5ef6893b596466f03b0564b81af29ff2538fd0aa4b9d54fe5ccbfb4c90ea50ad29fe2d221 + languageName: node + linkType: hard + +"cookie@npm:0.6.0": + version: 0.6.0 + resolution: "cookie@npm:0.6.0" + checksum: 10c0/f2318b31af7a31b4ddb4a678d024514df5e705f9be5909a192d7f116cfb6d45cbacf96a473fa733faa95050e7cff26e7832bb3ef94751592f1387b71c8956686 + languageName: node + linkType: hard + +"cookiejar@npm:^2.1.4": + version: 2.1.4 + resolution: "cookiejar@npm:2.1.4" + checksum: 10c0/2dae55611c6e1678f34d93984cbd4bda58f4fe3e5247cc4993f4a305cd19c913bbaf325086ed952e892108115073a747596453d3dc1c34947f47f731818b8ad1 + languageName: node + linkType: hard + +"cors@npm:^2.8.5": + version: 2.8.5 + resolution: "cors@npm:2.8.5" + dependencies: + object-assign: "npm:^4" + vary: "npm:^1" + checksum: 10c0/373702b7999409922da80de4a61938aabba6929aea5b6fd9096fefb9e8342f626c0ebd7507b0e8b0b311380744cc985f27edebc0a26e0ddb784b54e1085de761 + languageName: node + linkType: hard + +"create-jest@npm:^29.7.0": + version: 29.7.0 + resolution: "create-jest@npm:29.7.0" + dependencies: + "@jest/types": "npm:^29.6.3" + chalk: "npm:^4.0.0" + exit: "npm:^0.1.2" + graceful-fs: "npm:^4.2.9" + jest-config: "npm:^29.7.0" + jest-util: "npm:^29.7.0" + prompts: "npm:^2.0.1" + bin: + create-jest: bin/create-jest.js + checksum: 10c0/e7e54c280692470d3398f62a6238fd396327e01c6a0757002833f06d00afc62dd7bfe04ff2b9cd145264460e6b4d1eb8386f2925b7e567f97939843b7b0e812f + languageName: node + linkType: hard + +"cross-spawn@npm:^7.0.0, cross-spawn@npm:^7.0.2, cross-spawn@npm:^7.0.3": + version: 7.0.6 + resolution: "cross-spawn@npm:7.0.6" + dependencies: + path-key: "npm:^3.1.0" + shebang-command: "npm:^2.0.0" + which: "npm:^2.0.1" + checksum: 10c0/053ea8b2135caff68a9e81470e845613e374e7309a47731e81639de3eaeb90c3d01af0e0b44d2ab9d50b43467223b88567dfeb3262db942dc063b9976718ffc1 + languageName: node + linkType: hard + +"date-fns@npm:^2.30.0": + version: 2.30.0 + resolution: "date-fns@npm:2.30.0" + dependencies: + "@babel/runtime": "npm:^7.21.0" + checksum: 10c0/e4b521fbf22bc8c3db332bbfb7b094fd3e7627de0259a9d17c7551e2d2702608a7307a449206065916538e384f37b181565447ce2637ae09828427aed9cb5581 + languageName: node + linkType: hard + +"dateformat@npm:^4.6.3": + version: 4.6.3 + resolution: "dateformat@npm:4.6.3" + checksum: 10c0/e2023b905e8cfe2eb8444fb558562b524807a51cdfe712570f360f873271600b5c94aebffaf11efb285e2c072264a7cf243eadb68f3eba0f8cc85fb86cd25df6 + languageName: node + linkType: hard + +"dayjs@npm:^1.11.3": + version: 1.11.11 + resolution: "dayjs@npm:1.11.11" + checksum: 10c0/0131d10516b9945f05a57e13f4af49a6814de5573a494824e103131a3bbe4cc470b1aefe8e17e51f9a478a22cd116084be1ee5725cedb66ec4c3f9091202dc4b + languageName: node + linkType: hard + +"debug@npm:2.6.9": + version: 2.6.9 + resolution: "debug@npm:2.6.9" + dependencies: + ms: "npm:2.0.0" + checksum: 10c0/121908fb839f7801180b69a7e218a40b5a0b718813b886b7d6bdb82001b931c938e2941d1e4450f33a1b1df1da653f5f7a0440c197f29fbf8a6e9d45ff6ef589 + languageName: node + linkType: hard + +"debug@npm:4, debug@npm:^4.1.0, debug@npm:^4.1.1, debug@npm:^4.3.1, debug@npm:^4.3.2, debug@npm:^4.3.4": + version: 4.3.5 + resolution: "debug@npm:4.3.5" + dependencies: + ms: "npm:2.1.2" + peerDependenciesMeta: + supports-color: + optional: true + checksum: 10c0/082c375a2bdc4f4469c99f325ff458adad62a3fc2c482d59923c260cb08152f34e2659f72b3767db8bb2f21ca81a60a42d1019605a412132d7b9f59363a005cc + languageName: node + linkType: hard + +"decompress-response@npm:^6.0.0": + version: 6.0.0 + resolution: "decompress-response@npm:6.0.0" + dependencies: + mimic-response: "npm:^3.1.0" + checksum: 10c0/bd89d23141b96d80577e70c54fb226b2f40e74a6817652b80a116d7befb8758261ad073a8895648a29cc0a5947021ab66705cb542fa9c143c82022b27c5b175e + languageName: node + linkType: hard + +"dedent@npm:^1.0.0": + version: 1.5.3 + resolution: "dedent@npm:1.5.3" + peerDependencies: + babel-plugin-macros: ^3.1.0 + peerDependenciesMeta: + babel-plugin-macros: + optional: true + checksum: 10c0/d94bde6e6f780be4da4fd760288fcf755ec368872f4ac5218197200d86430aeb8d90a003a840bff1c20221188e3f23adced0119cb811c6873c70d0ac66d12832 + languageName: node + linkType: hard + +"deep-extend@npm:^0.6.0": + version: 0.6.0 + resolution: "deep-extend@npm:0.6.0" + checksum: 10c0/1c6b0abcdb901e13a44c7d699116d3d4279fdb261983122a3783e7273844d5f2537dc2e1c454a23fcf645917f93fbf8d07101c1d03c015a87faa662755212566 + languageName: node + linkType: hard + +"deep-is@npm:^0.1.3": + version: 0.1.4 + resolution: "deep-is@npm:0.1.4" + checksum: 10c0/7f0ee496e0dff14a573dc6127f14c95061b448b87b995fc96c017ce0a1e66af1675e73f1d6064407975bc4ea6ab679497a29fff7b5b9c4e99cb10797c1ad0b4c + languageName: node + linkType: hard + +"deepmerge@npm:^4.2.2": + version: 4.3.1 + resolution: "deepmerge@npm:4.3.1" + checksum: 10c0/e53481aaf1aa2c4082b5342be6b6d8ad9dfe387bc92ce197a66dea08bd4265904a087e75e464f14d1347cf2ac8afe1e4c16b266e0561cc5df29382d3c5f80044 + languageName: node + linkType: hard + +"define-data-property@npm:^1.1.4": + version: 1.1.4 + resolution: "define-data-property@npm:1.1.4" + dependencies: + es-define-property: "npm:^1.0.0" + es-errors: "npm:^1.3.0" + gopd: "npm:^1.0.1" + checksum: 10c0/dea0606d1483eb9db8d930d4eac62ca0fa16738b0b3e07046cddfacf7d8c868bbe13fa0cb263eb91c7d0d527960dc3f2f2471a69ed7816210307f6744fe62e37 + languageName: node + linkType: hard + +"delayed-stream@npm:~1.0.0": + version: 1.0.0 + resolution: "delayed-stream@npm:1.0.0" + checksum: 10c0/d758899da03392e6712f042bec80aa293bbe9e9ff1b2634baae6a360113e708b91326594c8a486d475c69d6259afb7efacdc3537bfcda1c6c648e390ce601b19 + languageName: node + linkType: hard + +"delegates@npm:^1.0.0": + version: 1.0.0 + resolution: "delegates@npm:1.0.0" + checksum: 10c0/ba05874b91148e1db4bf254750c042bf2215febd23a6d3cda2e64896aef79745fbd4b9996488bd3cafb39ce19dbce0fd6e3b6665275638befffe1c9b312b91b5 + languageName: node + linkType: hard + +"depd@npm:2.0.0": + version: 2.0.0 + resolution: "depd@npm:2.0.0" + checksum: 10c0/58bd06ec20e19529b06f7ad07ddab60e504d9e0faca4bd23079fac2d279c3594334d736508dc350e06e510aba5e22e4594483b3a6562ce7c17dd797f4cc4ad2c + languageName: node + linkType: hard + +"destroy@npm:1.2.0": + version: 1.2.0 + resolution: "destroy@npm:1.2.0" + checksum: 10c0/bd7633942f57418f5a3b80d5cb53898127bcf53e24cdf5d5f4396be471417671f0fee48a4ebe9a1e9defbde2a31280011af58a57e090ff822f589b443ed4e643 + languageName: node + linkType: hard + +"detect-libc@npm:^2.0.0": + version: 2.0.3 + resolution: "detect-libc@npm:2.0.3" + checksum: 10c0/88095bda8f90220c95f162bf92cad70bd0e424913e655c20578600e35b91edc261af27531cf160a331e185c0ced93944bc7e09939143225f56312d7fd800fdb7 + languageName: node + linkType: hard + +"detect-newline@npm:^3.0.0": + version: 3.1.0 + resolution: "detect-newline@npm:3.1.0" + checksum: 10c0/c38cfc8eeb9fda09febb44bcd85e467c970d4e3bf526095394e5a4f18bc26dd0cf6b22c69c1fa9969261521c593836db335c2795218f6d781a512aea2fb8209d + languageName: node + linkType: hard + +"dezalgo@npm:^1.0.4": + version: 1.0.4 + resolution: "dezalgo@npm:1.0.4" + dependencies: + asap: "npm:^2.0.0" + wrappy: "npm:1" + checksum: 10c0/8a870ed42eade9a397e6141fe5c025148a59ed52f1f28b1db5de216b4d57f0af7a257070c3af7ce3d5508c1ce9dd5009028a76f4b2cc9370dc56551d2355fad8 + languageName: node + linkType: hard + +"diff-sequences@npm:^29.6.3": + version: 29.6.3 + resolution: "diff-sequences@npm:29.6.3" + checksum: 10c0/32e27ac7dbffdf2fb0eb5a84efd98a9ad084fbabd5ac9abb8757c6770d5320d2acd172830b28c4add29bb873d59420601dfc805ac4064330ce59b1adfd0593b2 + languageName: node + linkType: hard + +"dir-glob@npm:^3.0.1": + version: 3.0.1 + resolution: "dir-glob@npm:3.0.1" + dependencies: + path-type: "npm:^4.0.0" + checksum: 10c0/dcac00920a4d503e38bb64001acb19df4efc14536ada475725e12f52c16777afdee4db827f55f13a908ee7efc0cb282e2e3dbaeeb98c0993dd93d1802d3bf00c + languageName: node + linkType: hard + +"doctrine@npm:^3.0.0": + version: 3.0.0 + resolution: "doctrine@npm:3.0.0" + dependencies: + esutils: "npm:^2.0.2" + checksum: 10c0/c96bdccabe9d62ab6fea9399fdff04a66e6563c1d6fb3a3a063e8d53c3bb136ba63e84250bbf63d00086a769ad53aef92d2bd483f03f837fc97b71cbee6b2520 + languageName: node + linkType: hard + +"dotenv@npm:^10.0.0": + version: 10.0.0 + resolution: "dotenv@npm:10.0.0" + checksum: 10c0/2d8d4ba64bfaff7931402aa5e8cbb8eba0acbc99fe9ae442300199af021079eafa7171ce90e150821a5cb3d74f0057721fbe7ec201a6044b68c8a7615f8c123f + languageName: node + linkType: hard + +"dotenv@npm:^16.0.0": + version: 16.4.5 + resolution: "dotenv@npm:16.4.5" + checksum: 10c0/48d92870076832af0418b13acd6e5a5a3e83bb00df690d9812e94b24aff62b88ade955ac99a05501305b8dc8f1b0ee7638b18493deb6fe93d680e5220936292f + languageName: node + linkType: hard + +"eastasianwidth@npm:^0.2.0": + version: 0.2.0 + resolution: "eastasianwidth@npm:0.2.0" + checksum: 10c0/26f364ebcdb6395f95124fda411f63137a4bfb5d3a06453f7f23dfe52502905bd84e0488172e0f9ec295fdc45f05c23d5d91baf16bd26f0fe9acd777a188dc39 + languageName: node + linkType: hard + +"ecdsa-sig-formatter@npm:1.0.11": + version: 1.0.11 + resolution: "ecdsa-sig-formatter@npm:1.0.11" + dependencies: + safe-buffer: "npm:^5.0.1" + checksum: 10c0/ebfbf19d4b8be938f4dd4a83b8788385da353d63307ede301a9252f9f7f88672e76f2191618fd8edfc2f24679236064176fab0b78131b161ee73daa37125408c + languageName: node + linkType: hard + +"ee-first@npm:1.1.1": + version: 1.1.1 + resolution: "ee-first@npm:1.1.1" + checksum: 10c0/b5bb125ee93161bc16bfe6e56c6b04de5ad2aa44234d8f644813cc95d861a6910903132b05093706de2b706599367c4130eb6d170f6b46895686b95f87d017b7 + languageName: node + linkType: hard + +"electron-to-chromium@npm:^1.4.796": + version: 1.4.805 + resolution: "electron-to-chromium@npm:1.4.805" + checksum: 10c0/90594849ebe1152c1c302183be7bf51642e24626e6d0332f8c56c5ad18d9fb821135e0ed9d0fcf3ec69422d774e48e6c226362be0d8c8efe6b0849225a28d53e + languageName: node + linkType: hard + +"emittery@npm:^0.13.1": + version: 0.13.1 + resolution: "emittery@npm:0.13.1" + checksum: 10c0/1573d0ae29ab34661b6c63251ff8f5facd24ccf6a823f19417ae8ba8c88ea450325788c67f16c99edec8de4b52ce93a10fe441ece389fd156e88ee7dab9bfa35 + languageName: node + linkType: hard + +"emoji-regex@npm:^8.0.0": + version: 8.0.0 + resolution: "emoji-regex@npm:8.0.0" + checksum: 10c0/b6053ad39951c4cf338f9092d7bfba448cdfd46fe6a2a034700b149ac9ffbc137e361cbd3c442297f86bed2e5f7576c1b54cc0a6bf8ef5106cc62f496af35010 + languageName: node + linkType: hard + +"emoji-regex@npm:^9.2.2": + version: 9.2.2 + resolution: "emoji-regex@npm:9.2.2" + checksum: 10c0/af014e759a72064cf66e6e694a7fc6b0ed3d8db680427b021a89727689671cefe9d04151b2cad51dbaf85d5ba790d061cd167f1cf32eb7b281f6368b3c181639 + languageName: node + linkType: hard + +"enabled@npm:2.0.x": + version: 2.0.0 + resolution: "enabled@npm:2.0.0" + checksum: 10c0/3b2c2af9bc7f8b9e291610f2dde4a75cf6ee52a68f4dd585482fbdf9a55d65388940e024e56d40bb03e05ef6671f5f53021fa8b72a20e954d7066ec28166713f + languageName: node + linkType: hard + +"encodeurl@npm:~1.0.2": + version: 1.0.2 + resolution: "encodeurl@npm:1.0.2" + checksum: 10c0/f6c2387379a9e7c1156c1c3d4f9cb7bb11cf16dd4c1682e1f6746512564b053df5781029b6061296832b59fb22f459dbe250386d217c2f6e203601abb2ee0bec + languageName: node + linkType: hard + +"encodeurl@npm:~2.0.0": + version: 2.0.0 + resolution: "encodeurl@npm:2.0.0" + checksum: 10c0/5d317306acb13e6590e28e27924c754163946a2480de11865c991a3a7eed4315cd3fba378b543ca145829569eefe9b899f3d84bb09870f675ae60bc924b01ceb + languageName: node + linkType: hard + +"encoding@npm:^0.1.13": + version: 0.1.13 + resolution: "encoding@npm:0.1.13" + dependencies: + iconv-lite: "npm:^0.6.2" + checksum: 10c0/36d938712ff00fe1f4bac88b43bcffb5930c1efa57bbcdca9d67e1d9d6c57cfb1200fb01efe0f3109b2ce99b231f90779532814a81370a1bd3274a0f58585039 + languageName: node + linkType: hard + +"end-of-stream@npm:^1.1.0, end-of-stream@npm:^1.4.1": + version: 1.4.4 + resolution: "end-of-stream@npm:1.4.4" + dependencies: + once: "npm:^1.4.0" + checksum: 10c0/870b423afb2d54bb8d243c63e07c170409d41e20b47eeef0727547aea5740bd6717aca45597a9f2745525667a6b804c1e7bede41f856818faee5806dd9ff3975 + languageName: node + linkType: hard + +"env-paths@npm:^2.2.0": + version: 2.2.1 + resolution: "env-paths@npm:2.2.1" + checksum: 10c0/285325677bf00e30845e330eec32894f5105529db97496ee3f598478e50f008c5352a41a30e5e72ec9de8a542b5a570b85699cd63bd2bc646dbcb9f311d83bc4 + languageName: node + linkType: hard + +"err-code@npm:^2.0.2": + version: 2.0.3 + resolution: "err-code@npm:2.0.3" + checksum: 10c0/b642f7b4dd4a376e954947550a3065a9ece6733ab8e51ad80db727aaae0817c2e99b02a97a3d6cecc648a97848305e728289cf312d09af395403a90c9d4d8a66 + languageName: node + linkType: hard + +"error-ex@npm:^1.3.1": + version: 1.3.2 + resolution: "error-ex@npm:1.3.2" + dependencies: + is-arrayish: "npm:^0.2.1" + checksum: 10c0/ba827f89369b4c93382cfca5a264d059dfefdaa56ecc5e338ffa58a6471f5ed93b71a20add1d52290a4873d92381174382658c885ac1a2305f7baca363ce9cce + languageName: node + linkType: hard + +"es-define-property@npm:^1.0.0": + version: 1.0.0 + resolution: "es-define-property@npm:1.0.0" + dependencies: + get-intrinsic: "npm:^1.2.4" + checksum: 10c0/6bf3191feb7ea2ebda48b577f69bdfac7a2b3c9bcf97307f55fd6ef1bbca0b49f0c219a935aca506c993d8c5d8bddd937766cb760cd5e5a1071351f2df9f9aa4 + languageName: node + linkType: hard + +"es-errors@npm:^1.3.0": + version: 1.3.0 + resolution: "es-errors@npm:1.3.0" + checksum: 10c0/0a61325670072f98d8ae3b914edab3559b6caa980f08054a3b872052640d91da01d38df55df797fcc916389d77fc92b8d5906cf028f4db46d7e3003abecbca85 + languageName: node + linkType: hard + +"escalade@npm:^3.1.1, escalade@npm:^3.1.2": + version: 3.1.2 + resolution: "escalade@npm:3.1.2" + checksum: 10c0/6b4adafecd0682f3aa1cd1106b8fff30e492c7015b178bc81b2d2f75106dabea6c6d6e8508fc491bd58e597c74abb0e8e2368f943ecb9393d4162e3c2f3cf287 + languageName: node + linkType: hard + +"escape-html@npm:~1.0.3": + version: 1.0.3 + resolution: "escape-html@npm:1.0.3" + checksum: 10c0/524c739d776b36c3d29fa08a22e03e8824e3b2fd57500e5e44ecf3cc4707c34c60f9ca0781c0e33d191f2991161504c295e98f68c78fe7baa6e57081ec6ac0a3 + languageName: node + linkType: hard + +"escape-string-regexp@npm:^1.0.5": + version: 1.0.5 + resolution: "escape-string-regexp@npm:1.0.5" + checksum: 10c0/a968ad453dd0c2724e14a4f20e177aaf32bb384ab41b674a8454afe9a41c5e6fe8903323e0a1052f56289d04bd600f81278edf140b0fcc02f5cac98d0f5b5371 + languageName: node + linkType: hard + +"escape-string-regexp@npm:^2.0.0": + version: 2.0.0 + resolution: "escape-string-regexp@npm:2.0.0" + checksum: 10c0/2530479fe8db57eace5e8646c9c2a9c80fa279614986d16dcc6bcaceb63ae77f05a851ba6c43756d816c61d7f4534baf56e3c705e3e0d884818a46808811c507 + languageName: node + linkType: hard + +"escape-string-regexp@npm:^4.0.0": + version: 4.0.0 + resolution: "escape-string-regexp@npm:4.0.0" + checksum: 10c0/9497d4dd307d845bd7f75180d8188bb17ea8c151c1edbf6b6717c100e104d629dc2dfb687686181b0f4b7d732c7dfdc4d5e7a8ff72de1b0ca283a75bbb3a9cd9 + languageName: node + linkType: hard + +"eslint-plugin-prettier@npm:^4.2.1": + version: 4.2.1 + resolution: "eslint-plugin-prettier@npm:4.2.1" + dependencies: + prettier-linter-helpers: "npm:^1.0.0" + peerDependencies: + eslint: ">=7.28.0" + prettier: ">=2.0.0" + peerDependenciesMeta: + eslint-config-prettier: + optional: true + checksum: 10c0/c5e7316baeab9d96ac39c279f16686e837277e5c67a8006c6588bcff317edffdc1532fb580441eb598bc6770f6444006756b68a6575dff1cd85ebe227252d0b7 + languageName: node + linkType: hard + +"eslint-scope@npm:^5.1.1": + version: 5.1.1 + resolution: "eslint-scope@npm:5.1.1" + dependencies: + esrecurse: "npm:^4.3.0" + estraverse: "npm:^4.1.1" + checksum: 10c0/d30ef9dc1c1cbdece34db1539a4933fe3f9b14e1ffb27ecc85987902ee663ad7c9473bbd49a9a03195a373741e62e2f807c4938992e019b511993d163450e70a + languageName: node + linkType: hard + +"eslint-scope@npm:^7.2.2": + version: 7.2.2 + resolution: "eslint-scope@npm:7.2.2" + dependencies: + esrecurse: "npm:^4.3.0" + estraverse: "npm:^5.2.0" + checksum: 10c0/613c267aea34b5a6d6c00514e8545ef1f1433108097e857225fed40d397dd6b1809dffd11c2fde23b37ca53d7bf935fe04d2a18e6fc932b31837b6ad67e1c116 + languageName: node + linkType: hard + +"eslint-visitor-keys@npm:^3.3.0, eslint-visitor-keys@npm:^3.4.1, eslint-visitor-keys@npm:^3.4.3": + version: 3.4.3 + resolution: "eslint-visitor-keys@npm:3.4.3" + checksum: 10c0/92708e882c0a5ffd88c23c0b404ac1628cf20104a108c745f240a13c332a11aac54f49a22d5762efbffc18ecbc9a580d1b7ad034bf5f3cc3307e5cbff2ec9820 + languageName: node + linkType: hard + +"eslint@npm:^8.33.0": + version: 8.57.0 + resolution: "eslint@npm:8.57.0" + dependencies: + "@eslint-community/eslint-utils": "npm:^4.2.0" + "@eslint-community/regexpp": "npm:^4.6.1" + "@eslint/eslintrc": "npm:^2.1.4" + "@eslint/js": "npm:8.57.0" + "@humanwhocodes/config-array": "npm:^0.11.14" + "@humanwhocodes/module-importer": "npm:^1.0.1" + "@nodelib/fs.walk": "npm:^1.2.8" + "@ungap/structured-clone": "npm:^1.2.0" + ajv: "npm:^6.12.4" + chalk: "npm:^4.0.0" + cross-spawn: "npm:^7.0.2" + debug: "npm:^4.3.2" + doctrine: "npm:^3.0.0" + escape-string-regexp: "npm:^4.0.0" + eslint-scope: "npm:^7.2.2" + eslint-visitor-keys: "npm:^3.4.3" + espree: "npm:^9.6.1" + esquery: "npm:^1.4.2" + esutils: "npm:^2.0.2" + fast-deep-equal: "npm:^3.1.3" + file-entry-cache: "npm:^6.0.1" + find-up: "npm:^5.0.0" + glob-parent: "npm:^6.0.2" + globals: "npm:^13.19.0" + graphemer: "npm:^1.4.0" + ignore: "npm:^5.2.0" + imurmurhash: "npm:^0.1.4" + is-glob: "npm:^4.0.0" + is-path-inside: "npm:^3.0.3" + js-yaml: "npm:^4.1.0" + json-stable-stringify-without-jsonify: "npm:^1.0.1" + levn: "npm:^0.4.1" + lodash.merge: "npm:^4.6.2" + minimatch: "npm:^3.1.2" + natural-compare: "npm:^1.4.0" + optionator: "npm:^0.9.3" + strip-ansi: "npm:^6.0.1" + text-table: "npm:^0.2.0" + bin: + eslint: bin/eslint.js + checksum: 10c0/00bb96fd2471039a312435a6776fe1fd557c056755eaa2b96093ef3a8508c92c8775d5f754768be6b1dddd09fdd3379ddb231eeb9b6c579ee17ea7d68000a529 + languageName: node + linkType: hard + +"espree@npm:^9.6.0, espree@npm:^9.6.1": + version: 9.6.1 + resolution: "espree@npm:9.6.1" + dependencies: + acorn: "npm:^8.9.0" + acorn-jsx: "npm:^5.3.2" + eslint-visitor-keys: "npm:^3.4.1" + checksum: 10c0/1a2e9b4699b715347f62330bcc76aee224390c28bb02b31a3752e9d07549c473f5f986720483c6469cf3cfb3c9d05df612ffc69eb1ee94b54b739e67de9bb460 + languageName: node + linkType: hard + +"esprima@npm:^4.0.0": + version: 4.0.1 + resolution: "esprima@npm:4.0.1" + bin: + esparse: ./bin/esparse.js + esvalidate: ./bin/esvalidate.js + checksum: 10c0/ad4bab9ead0808cf56501750fd9d3fb276f6b105f987707d059005d57e182d18a7c9ec7f3a01794ebddcca676773e42ca48a32d67a250c9d35e009ca613caba3 + languageName: node + linkType: hard + +"esquery@npm:^1.4.2": + version: 1.5.0 + resolution: "esquery@npm:1.5.0" + dependencies: + estraverse: "npm:^5.1.0" + checksum: 10c0/a084bd049d954cc88ac69df30534043fb2aee5555b56246493f42f27d1e168f00d9e5d4192e46f10290d312dc30dc7d58994d61a609c579c1219d636996f9213 + languageName: node + linkType: hard + +"esrecurse@npm:^4.3.0": + version: 4.3.0 + resolution: "esrecurse@npm:4.3.0" + dependencies: + estraverse: "npm:^5.2.0" + checksum: 10c0/81a37116d1408ded88ada45b9fb16dbd26fba3aadc369ce50fcaf82a0bac12772ebd7b24cd7b91fc66786bf2c1ac7b5f196bc990a473efff972f5cb338877cf5 + languageName: node + linkType: hard + +"estraverse@npm:^4.1.1": + version: 4.3.0 + resolution: "estraverse@npm:4.3.0" + checksum: 10c0/9cb46463ef8a8a4905d3708a652d60122a0c20bb58dec7e0e12ab0e7235123d74214fc0141d743c381813e1b992767e2708194f6f6e0f9fd00c1b4e0887b8b6d + languageName: node + linkType: hard + +"estraverse@npm:^5.1.0, estraverse@npm:^5.2.0": + version: 5.3.0 + resolution: "estraverse@npm:5.3.0" + checksum: 10c0/1ff9447b96263dec95d6d67431c5e0771eb9776427421260a3e2f0fdd5d6bd4f8e37a7338f5ad2880c9f143450c9b1e4fc2069060724570a49cf9cf0312bd107 + languageName: node + linkType: hard + +"esutils@npm:^2.0.2": + version: 2.0.3 + resolution: "esutils@npm:2.0.3" + checksum: 10c0/9a2fe69a41bfdade834ba7c42de4723c97ec776e40656919c62cbd13607c45e127a003f05f724a1ea55e5029a4cf2de444b13009f2af71271e42d93a637137c7 + languageName: node + linkType: hard + +"etag@npm:~1.8.1": + version: 1.8.1 + resolution: "etag@npm:1.8.1" + checksum: 10c0/12be11ef62fb9817314d790089a0a49fae4e1b50594135dcb8076312b7d7e470884b5100d249b28c18581b7fd52f8b485689ffae22a11ed9ec17377a33a08f84 + languageName: node + linkType: hard + +"execa@npm:^5.0.0": + version: 5.1.1 + resolution: "execa@npm:5.1.1" + dependencies: + cross-spawn: "npm:^7.0.3" + get-stream: "npm:^6.0.0" + human-signals: "npm:^2.1.0" + is-stream: "npm:^2.0.0" + merge-stream: "npm:^2.0.0" + npm-run-path: "npm:^4.0.1" + onetime: "npm:^5.1.2" + signal-exit: "npm:^3.0.3" + strip-final-newline: "npm:^2.0.0" + checksum: 10c0/c8e615235e8de4c5addf2fa4c3da3e3aa59ce975a3e83533b4f6a71750fb816a2e79610dc5f1799b6e28976c9ae86747a36a606655bf8cb414a74d8d507b304f + languageName: node + linkType: hard + +"exit@npm:^0.1.2": + version: 0.1.2 + resolution: "exit@npm:0.1.2" + checksum: 10c0/71d2ad9b36bc25bb8b104b17e830b40a08989be7f7d100b13269aaae7c3784c3e6e1e88a797e9e87523993a25ba27c8958959a554535370672cfb4d824af8989 + languageName: node + linkType: hard + +"expand-template@npm:^2.0.3": + version: 2.0.3 + resolution: "expand-template@npm:2.0.3" + checksum: 10c0/1c9e7afe9acadf9d373301d27f6a47b34e89b3391b1ef38b7471d381812537ef2457e620ae7f819d2642ce9c43b189b3583813ec395e2938319abe356a9b2f51 + languageName: node + linkType: hard + +"expect@npm:^29.0.0, expect@npm:^29.7.0": + version: 29.7.0 + resolution: "expect@npm:29.7.0" + dependencies: + "@jest/expect-utils": "npm:^29.7.0" + jest-get-type: "npm:^29.6.3" + jest-matcher-utils: "npm:^29.7.0" + jest-message-util: "npm:^29.7.0" + jest-util: "npm:^29.7.0" + checksum: 10c0/2eddeace66e68b8d8ee5f7be57f3014b19770caaf6815c7a08d131821da527fb8c8cb7b3dcd7c883d2d3d8d184206a4268984618032d1e4b16dc8d6596475d41 + languageName: node + linkType: hard + +"exponential-backoff@npm:^3.1.1": + version: 3.1.1 + resolution: "exponential-backoff@npm:3.1.1" + checksum: 10c0/160456d2d647e6019640bd07111634d8c353038d9fa40176afb7cd49b0548bdae83b56d05e907c2cce2300b81cae35d800ef92fefb9d0208e190fa3b7d6bb579 + languageName: node + linkType: hard + +"express-actuator@npm:1.8.4": + version: 1.8.4 + resolution: "express-actuator@npm:1.8.4" + dependencies: + dayjs: "npm:^1.11.3" + properties-reader: "npm:^2.2.0" + checksum: 10c0/216264ba977b34d59de95721924354d7b4090b62801f225eac7f861fd8e40251e215826d7b462e57b86d02143eabdff2384b50d17d207e54a61b5aaaa726ad83 + languageName: node + linkType: hard + +"express-rate-limit@npm:^6.7.0": + version: 6.11.2 + resolution: "express-rate-limit@npm:6.11.2" + peerDependencies: + express: ^4 || ^5 + checksum: 10c0/1f92107fca92423b6311ca26cb79a3d5d79ac5f5eb81791c382c2323e331da0e993249225615003e2db91b617d69ba0d428ef60d212bfabb7a83244645f10e0a + languageName: node + linkType: hard + +"express-response-size@npm:^0.0.3": + version: 0.0.3 + resolution: "express-response-size@npm:0.0.3" + dependencies: + on-headers: "npm:1.0.1" + checksum: 10c0/9203d192c7d7b48b4ea8710aa90d78522020d469963f9e83355a140eb1b13e40c03caa752b8f9d0b6bb401fe89958c5a043f28278ff514c90af8dc7d51409bc7 + languageName: node + linkType: hard + +"express-winston@npm:^4.2.0": + version: 4.2.0 + resolution: "express-winston@npm:4.2.0" + dependencies: + chalk: "npm:^2.4.2" + lodash: "npm:^4.17.21" + peerDependencies: + winston: ">=3.x <4" + checksum: 10c0/8f80993e7d7696b22a12c68ffb72ea0cd3ad980d8394073fd972162cdca116b8f506974dd504987e350cddfdbf55402871762dcb9c69f2f7feaef2df6d93ef09 + languageName: node + linkType: hard + +"express@npm:4.20.0": + version: 4.20.0 + resolution: "express@npm:4.20.0" + dependencies: + accepts: "npm:~1.3.8" + array-flatten: "npm:1.1.1" + body-parser: "npm:1.20.3" + content-disposition: "npm:0.5.4" + content-type: "npm:~1.0.4" + cookie: "npm:0.6.0" + cookie-signature: "npm:1.0.6" + debug: "npm:2.6.9" + depd: "npm:2.0.0" + encodeurl: "npm:~2.0.0" + escape-html: "npm:~1.0.3" + etag: "npm:~1.8.1" + finalhandler: "npm:1.2.0" + fresh: "npm:0.5.2" + http-errors: "npm:2.0.0" + merge-descriptors: "npm:1.0.3" + methods: "npm:~1.1.2" + on-finished: "npm:2.4.1" + parseurl: "npm:~1.3.3" + path-to-regexp: "npm:0.1.10" + proxy-addr: "npm:~2.0.7" + qs: "npm:6.11.0" + range-parser: "npm:~1.2.1" + safe-buffer: "npm:5.2.1" + send: "npm:0.19.0" + serve-static: "npm:1.16.0" + setprototypeof: "npm:1.2.0" + statuses: "npm:2.0.1" + type-is: "npm:~1.6.18" + utils-merge: "npm:1.0.1" + vary: "npm:~1.1.2" + checksum: 10c0/626e440e9feffa3f82ebce5e7dc0ad7a74fa96079994f30048cce450f4855a258abbcabf021f691aeb72154867f0d28440a8498c62888805faf667a829fb65aa + languageName: node + linkType: hard + +"fast-deep-equal@npm:^3.1.1, fast-deep-equal@npm:^3.1.3": + version: 3.1.3 + resolution: "fast-deep-equal@npm:3.1.3" + checksum: 10c0/40dedc862eb8992c54579c66d914635afbec43350afbbe991235fdcb4e3a8d5af1b23ae7e79bef7d4882d0ecee06c3197488026998fb19f72dc95acff1d1b1d0 + languageName: node + linkType: hard + +"fast-diff@npm:^1.1.2": + version: 1.3.0 + resolution: "fast-diff@npm:1.3.0" + checksum: 10c0/5c19af237edb5d5effda008c891a18a585f74bf12953be57923f17a3a4d0979565fc64dbc73b9e20926b9d895f5b690c618cbb969af0cf022e3222471220ad29 + languageName: node + linkType: hard + +"fast-glob@npm:^3.2.9": + version: 3.3.2 + resolution: "fast-glob@npm:3.3.2" + dependencies: + "@nodelib/fs.stat": "npm:^2.0.2" + "@nodelib/fs.walk": "npm:^1.2.3" + glob-parent: "npm:^5.1.2" + merge2: "npm:^1.3.0" + micromatch: "npm:^4.0.4" + checksum: 10c0/42baad7b9cd40b63e42039132bde27ca2cb3a4950d0a0f9abe4639ea1aa9d3e3b40f98b1fe31cbc0cc17b664c9ea7447d911a152fa34ec5b72977b125a6fc845 + languageName: node + linkType: hard + +"fast-json-stable-stringify@npm:^2.0.0, fast-json-stable-stringify@npm:^2.1.0": + version: 2.1.0 + resolution: "fast-json-stable-stringify@npm:2.1.0" + checksum: 10c0/7f081eb0b8a64e0057b3bb03f974b3ef00135fbf36c1c710895cd9300f13c94ba809bb3a81cf4e1b03f6e5285610a61abbd7602d0652de423144dfee5a389c9b + languageName: node + linkType: hard + +"fast-levenshtein@npm:^2.0.6": + version: 2.0.6 + resolution: "fast-levenshtein@npm:2.0.6" + checksum: 10c0/111972b37338bcb88f7d9e2c5907862c280ebf4234433b95bc611e518d192ccb2d38119c4ac86e26b668d75f7f3894f4ff5c4982899afced7ca78633b08287c4 + languageName: node + linkType: hard + +"fast-safe-stringify@npm:^2.1.1": + version: 2.1.1 + resolution: "fast-safe-stringify@npm:2.1.1" + checksum: 10c0/d90ec1c963394919828872f21edaa3ad6f1dddd288d2bd4e977027afff09f5db40f94e39536d4646f7e01761d704d72d51dce5af1b93717f3489ef808f5f4e4d + languageName: node + linkType: hard + +"fastq@npm:^1.6.0": + version: 1.17.1 + resolution: "fastq@npm:1.17.1" + dependencies: + reusify: "npm:^1.0.4" + checksum: 10c0/1095f16cea45fb3beff558bb3afa74ca7a9250f5a670b65db7ed585f92b4b48381445cd328b3d87323da81e43232b5d5978a8201bde84e0cd514310f1ea6da34 + languageName: node + linkType: hard + +"fb-watchman@npm:^2.0.0": + version: 2.0.2 + resolution: "fb-watchman@npm:2.0.2" + dependencies: + bser: "npm:2.1.1" + checksum: 10c0/feae89ac148adb8f6ae8ccd87632e62b13563e6fb114cacb5265c51f585b17e2e268084519fb2edd133872f1d47a18e6bfd7e5e08625c0d41b93149694187581 + languageName: node + linkType: hard + +"fecha@npm:^4.2.0": + version: 4.2.3 + resolution: "fecha@npm:4.2.3" + checksum: 10c0/0e895965959cf6a22bb7b00f0bf546f2783836310f510ddf63f463e1518d4c96dec61ab33fdfd8e79a71b4856a7c865478ce2ee8498d560fe125947703c9b1cf + languageName: node + linkType: hard + +"file-entry-cache@npm:^6.0.1": + version: 6.0.1 + resolution: "file-entry-cache@npm:6.0.1" + dependencies: + flat-cache: "npm:^3.0.4" + checksum: 10c0/58473e8a82794d01b38e5e435f6feaf648e3f36fdb3a56e98f417f4efae71ad1c0d4ebd8a9a7c50c3ad085820a93fc7494ad721e0e4ebc1da3573f4e1c3c7cdd + languageName: node + linkType: hard + +"file-uri-to-path@npm:1.0.0": + version: 1.0.0 + resolution: "file-uri-to-path@npm:1.0.0" + checksum: 10c0/3b545e3a341d322d368e880e1c204ef55f1d45cdea65f7efc6c6ce9e0c4d22d802d5629320eb779d006fe59624ac17b0e848d83cc5af7cd101f206cb704f5519 + languageName: node + linkType: hard + +"fill-range@npm:^7.1.1": + version: 7.1.1 + resolution: "fill-range@npm:7.1.1" + dependencies: + to-regex-range: "npm:^5.0.1" + checksum: 10c0/b75b691bbe065472f38824f694c2f7449d7f5004aa950426a2c28f0306c60db9b880c0b0e4ed819997ffb882d1da02cfcfc819bddc94d71627f5269682edf018 + languageName: node + linkType: hard + +"finalhandler@npm:1.2.0": + version: 1.2.0 + resolution: "finalhandler@npm:1.2.0" + dependencies: + debug: "npm:2.6.9" + encodeurl: "npm:~1.0.2" + escape-html: "npm:~1.0.3" + on-finished: "npm:2.4.1" + parseurl: "npm:~1.3.3" + statuses: "npm:2.0.1" + unpipe: "npm:~1.0.0" + checksum: 10c0/64b7e5ff2ad1fcb14931cd012651631b721ce657da24aedb5650ddde9378bf8e95daa451da43398123f5de161a81e79ff5affe4f9f2a6d2df4a813d6d3e254b7 + languageName: node + linkType: hard + +"find-up@npm:^4.0.0, find-up@npm:^4.1.0": + version: 4.1.0 + resolution: "find-up@npm:4.1.0" + dependencies: + locate-path: "npm:^5.0.0" + path-exists: "npm:^4.0.0" + checksum: 10c0/0406ee89ebeefa2d507feb07ec366bebd8a6167ae74aa4e34fb4c4abd06cf782a3ce26ae4194d70706f72182841733f00551c209fe575cb00bd92104056e78c1 + languageName: node + linkType: hard + +"find-up@npm:^5.0.0": + version: 5.0.0 + resolution: "find-up@npm:5.0.0" + dependencies: + locate-path: "npm:^6.0.0" + path-exists: "npm:^4.0.0" + checksum: 10c0/062c5a83a9c02f53cdd6d175a37ecf8f87ea5bbff1fdfb828f04bfa021441bc7583e8ebc0872a4c1baab96221fb8a8a275a19809fb93fbc40bd69ec35634069a + languageName: node + linkType: hard + +"flat-cache@npm:^3.0.4": + version: 3.2.0 + resolution: "flat-cache@npm:3.2.0" + dependencies: + flatted: "npm:^3.2.9" + keyv: "npm:^4.5.3" + rimraf: "npm:^3.0.2" + checksum: 10c0/b76f611bd5f5d68f7ae632e3ae503e678d205cf97a17c6ab5b12f6ca61188b5f1f7464503efae6dc18683ed8f0b41460beb48ac4b9ac63fe6201296a91ba2f75 + languageName: node + linkType: hard + +"flatted@npm:^3.2.9": + version: 3.3.1 + resolution: "flatted@npm:3.3.1" + checksum: 10c0/324166b125ee07d4ca9bcf3a5f98d915d5db4f39d711fba640a3178b959919aae1f7cfd8aabcfef5826ed8aa8a2aa14cc85b2d7d18ff638ddf4ae3df39573eaf + languageName: node + linkType: hard + +"fn.name@npm:1.x.x": + version: 1.1.0 + resolution: "fn.name@npm:1.1.0" + checksum: 10c0/8ad62aa2d4f0b2a76d09dba36cfec61c540c13a0fd72e5d94164e430f987a7ce6a743112bbeb14877c810ef500d1f73d7f56e76d029d2e3413f20d79e3460a9a + languageName: node + linkType: hard + +"follow-redirects@npm:^1.15.6": + version: 1.15.6 + resolution: "follow-redirects@npm:1.15.6" + peerDependenciesMeta: + debug: + optional: true + checksum: 10c0/9ff767f0d7be6aa6870c82ac79cf0368cd73e01bbc00e9eb1c2a16fbb198ec105e3c9b6628bb98e9f3ac66fe29a957b9645bcb9a490bb7aa0d35f908b6b85071 + languageName: node + linkType: hard + +"foreground-child@npm:^3.1.0": + version: 3.2.1 + resolution: "foreground-child@npm:3.2.1" + dependencies: + cross-spawn: "npm:^7.0.0" + signal-exit: "npm:^4.0.1" + checksum: 10c0/9a53a33dbd87090e9576bef65fb4a71de60f6863a8062a7b11bc1cbe3cc86d428677d7c0b9ef61cdac11007ac580006f78bd5638618d564cfd5e6fd713d6878f + languageName: node + linkType: hard + +"form-data@npm:^4.0.0": + version: 4.0.0 + resolution: "form-data@npm:4.0.0" + dependencies: + asynckit: "npm:^0.4.0" + combined-stream: "npm:^1.0.8" + mime-types: "npm:^2.1.12" + checksum: 10c0/cb6f3ac49180be03ff07ba3ff125f9eba2ff0b277fb33c7fc47569fc5e616882c5b1c69b9904c4c4187e97dd0419dd03b134174756f296dec62041e6527e2c6e + languageName: node + linkType: hard + +"formidable@npm:^2.1.2": + version: 2.1.2 + resolution: "formidable@npm:2.1.2" + dependencies: + dezalgo: "npm:^1.0.4" + hexoid: "npm:^1.0.0" + once: "npm:^1.4.0" + qs: "npm:^6.11.0" + checksum: 10c0/efba03d11127098daa6ef54c3c0fad25693973eb902fa88ccaaa203baebe8c74d12ba0fe1e113eccf79b9172510fa337e4e107330b124fb3a8c74697b4aa2ce3 + languageName: node + linkType: hard + +"forwarded@npm:0.2.0": + version: 0.2.0 + resolution: "forwarded@npm:0.2.0" + checksum: 10c0/9b67c3fac86acdbc9ae47ba1ddd5f2f81526fa4c8226863ede5600a3f7c7416ef451f6f1e240a3cc32d0fd79fcfe6beb08fd0da454f360032bde70bf80afbb33 + languageName: node + linkType: hard + +"fresh@npm:0.5.2": + version: 0.5.2 + resolution: "fresh@npm:0.5.2" + checksum: 10c0/c6d27f3ed86cc5b601404822f31c900dd165ba63fff8152a3ef714e2012e7535027063bc67ded4cb5b3a49fa596495d46cacd9f47d6328459cf570f08b7d9e5a + languageName: node + linkType: hard + +"fs-constants@npm:^1.0.0": + version: 1.0.0 + resolution: "fs-constants@npm:1.0.0" + checksum: 10c0/a0cde99085f0872f4d244e83e03a46aa387b74f5a5af750896c6b05e9077fac00e9932fdf5aef84f2f16634cd473c63037d7a512576da7d5c2b9163d1909f3a8 + languageName: node + linkType: hard + +"fs-minipass@npm:^2.0.0": + version: 2.1.0 + resolution: "fs-minipass@npm:2.1.0" + dependencies: + minipass: "npm:^3.0.0" + checksum: 10c0/703d16522b8282d7299337539c3ed6edddd1afe82435e4f5b76e34a79cd74e488a8a0e26a636afc2440e1a23b03878e2122e3a2cfe375a5cf63c37d92b86a004 + languageName: node + linkType: hard + +"fs-minipass@npm:^3.0.0": + version: 3.0.3 + resolution: "fs-minipass@npm:3.0.3" + dependencies: + minipass: "npm:^7.0.3" + checksum: 10c0/63e80da2ff9b621e2cb1596abcb9207f1cf82b968b116ccd7b959e3323144cce7fb141462200971c38bbf2ecca51695069db45265705bed09a7cd93ae5b89f94 + languageName: node + linkType: hard + +"fs.realpath@npm:^1.0.0": + version: 1.0.0 + resolution: "fs.realpath@npm:1.0.0" + checksum: 10c0/444cf1291d997165dfd4c0d58b69f0e4782bfd9149fd72faa4fe299e68e0e93d6db941660b37dd29153bf7186672ececa3b50b7e7249477b03fdf850f287c948 + languageName: node + linkType: hard + +"fsevents@npm:^2.3.2": + version: 2.3.3 + resolution: "fsevents@npm:2.3.3" + dependencies: + node-gyp: "npm:latest" + checksum: 10c0/a1f0c44595123ed717febbc478aa952e47adfc28e2092be66b8ab1635147254ca6cfe1df792a8997f22716d4cbafc73309899ff7bfac2ac3ad8cf2e4ecc3ec60 + conditions: os=darwin + languageName: node + linkType: hard + +"fsevents@patch:fsevents@npm%3A^2.3.2#optional!builtin<compat/fsevents>": + version: 2.3.3 + resolution: "fsevents@patch:fsevents@npm%3A2.3.3#optional!builtin<compat/fsevents>::version=2.3.3&hash=df0bf1" + dependencies: + node-gyp: "npm:latest" + conditions: os=darwin + languageName: node + linkType: hard + +"function-bind@npm:^1.1.2": + version: 1.1.2 + resolution: "function-bind@npm:1.1.2" + checksum: 10c0/d8680ee1e5fcd4c197e4ac33b2b4dce03c71f4d91717292785703db200f5c21f977c568d28061226f9b5900cbcd2c84463646134fd5337e7925e0942bc3f46d5 + languageName: node + linkType: hard + +"gauge@npm:^3.0.0": + version: 3.0.2 + resolution: "gauge@npm:3.0.2" + dependencies: + aproba: "npm:^1.0.3 || ^2.0.0" + color-support: "npm:^1.1.2" + console-control-strings: "npm:^1.0.0" + has-unicode: "npm:^2.0.1" + object-assign: "npm:^4.1.1" + signal-exit: "npm:^3.0.0" + string-width: "npm:^4.2.3" + strip-ansi: "npm:^6.0.1" + wide-align: "npm:^1.1.2" + checksum: 10c0/75230ccaf216471e31025c7d5fcea1629596ca20792de50c596eb18ffb14d8404f927cd55535aab2eeecd18d1e11bd6f23ec3c2e9878d2dda1dc74bccc34b913 + languageName: node + linkType: hard + +"gensync@npm:^1.0.0-beta.2": + version: 1.0.0-beta.2 + resolution: "gensync@npm:1.0.0-beta.2" + checksum: 10c0/782aba6cba65b1bb5af3b095d96249d20edbe8df32dbf4696fd49be2583faf676173bf4809386588828e4dd76a3354fcbeb577bab1c833ccd9fc4577f26103f8 + languageName: node + linkType: hard + +"get-caller-file@npm:^2.0.5": + version: 2.0.5 + resolution: "get-caller-file@npm:2.0.5" + checksum: 10c0/c6c7b60271931fa752aeb92f2b47e355eac1af3a2673f47c9589e8f8a41adc74d45551c1bc57b5e66a80609f10ffb72b6f575e4370d61cc3f7f3aaff01757cde + languageName: node + linkType: hard + +"get-intrinsic@npm:^1.1.3, get-intrinsic@npm:^1.2.4": + version: 1.2.4 + resolution: "get-intrinsic@npm:1.2.4" + dependencies: + es-errors: "npm:^1.3.0" + function-bind: "npm:^1.1.2" + has-proto: "npm:^1.0.1" + has-symbols: "npm:^1.0.3" + hasown: "npm:^2.0.0" + checksum: 10c0/0a9b82c16696ed6da5e39b1267104475c47e3a9bdbe8b509dfe1710946e38a87be70d759f4bb3cda042d76a41ef47fe769660f3b7c0d1f68750299344ffb15b7 + languageName: node + linkType: hard + +"get-package-type@npm:^0.1.0": + version: 0.1.0 + resolution: "get-package-type@npm:0.1.0" + checksum: 10c0/e34cdf447fdf1902a1f6d5af737eaadf606d2ee3518287abde8910e04159368c268568174b2e71102b87b26c2020486f126bfca9c4fb1ceb986ff99b52ecd1be + languageName: node + linkType: hard + +"get-stream@npm:^6.0.0": + version: 6.0.1 + resolution: "get-stream@npm:6.0.1" + checksum: 10c0/49825d57d3fd6964228e6200a58169464b8e8970489b3acdc24906c782fb7f01f9f56f8e6653c4a50713771d6658f7cfe051e5eb8c12e334138c9c918b296341 + languageName: node + linkType: hard + +"github-from-package@npm:0.0.0": + version: 0.0.0 + resolution: "github-from-package@npm:0.0.0" + checksum: 10c0/737ee3f52d0a27e26332cde85b533c21fcdc0b09fb716c3f8e522cfaa9c600d4a631dec9fcde179ec9d47cca89017b7848ed4d6ae6b6b78f936c06825b1fcc12 + languageName: node + linkType: hard + +"glob-parent@npm:^5.1.2": + version: 5.1.2 + resolution: "glob-parent@npm:5.1.2" + dependencies: + is-glob: "npm:^4.0.1" + checksum: 10c0/cab87638e2112bee3f839ef5f6e0765057163d39c66be8ec1602f3823da4692297ad4e972de876ea17c44d652978638d2fd583c6713d0eb6591706825020c9ee + languageName: node + linkType: hard + +"glob-parent@npm:^6.0.2": + version: 6.0.2 + resolution: "glob-parent@npm:6.0.2" + dependencies: + is-glob: "npm:^4.0.3" + checksum: 10c0/317034d88654730230b3f43bb7ad4f7c90257a426e872ea0bf157473ac61c99bf5d205fad8f0185f989be8d2fa6d3c7dce1645d99d545b6ea9089c39f838e7f8 + languageName: node + linkType: hard + +"glob@npm:^10.2.2, glob@npm:^10.3.10": + version: 10.4.1 + resolution: "glob@npm:10.4.1" + dependencies: + foreground-child: "npm:^3.1.0" + jackspeak: "npm:^3.1.2" + minimatch: "npm:^9.0.4" + minipass: "npm:^7.1.2" + path-scurry: "npm:^1.11.1" + bin: + glob: dist/esm/bin.mjs + checksum: 10c0/77f2900ed98b9cc2a0e1901ee5e476d664dae3cd0f1b662b8bfd4ccf00d0edc31a11595807706a274ca10e1e251411bbf2e8e976c82bed0d879a9b89343ed379 + languageName: node + linkType: hard + +"glob@npm:^7.1.3, glob@npm:^7.1.4": + version: 7.2.3 + resolution: "glob@npm:7.2.3" + dependencies: + fs.realpath: "npm:^1.0.0" + inflight: "npm:^1.0.4" + inherits: "npm:2" + minimatch: "npm:^3.1.1" + once: "npm:^1.3.0" + path-is-absolute: "npm:^1.0.0" + checksum: 10c0/65676153e2b0c9095100fe7f25a778bf45608eeb32c6048cf307f579649bcc30353277b3b898a3792602c65764e5baa4f643714dfbdfd64ea271d210c7a425fe + languageName: node + linkType: hard + +"globals@npm:^11.1.0": + version: 11.12.0 + resolution: "globals@npm:11.12.0" + checksum: 10c0/758f9f258e7b19226bd8d4af5d3b0dcf7038780fb23d82e6f98932c44e239f884847f1766e8fa9cc5635ccb3204f7fa7314d4408dd4002a5e8ea827b4018f0a1 + languageName: node + linkType: hard + +"globals@npm:^13.19.0": + version: 13.24.0 + resolution: "globals@npm:13.24.0" + dependencies: + type-fest: "npm:^0.20.2" + checksum: 10c0/d3c11aeea898eb83d5ec7a99508600fbe8f83d2cf00cbb77f873dbf2bcb39428eff1b538e4915c993d8a3b3473fa71eeebfe22c9bb3a3003d1e26b1f2c8a42cd + languageName: node + linkType: hard + +"globby@npm:^11.1.0": + version: 11.1.0 + resolution: "globby@npm:11.1.0" + dependencies: + array-union: "npm:^2.1.0" + dir-glob: "npm:^3.0.1" + fast-glob: "npm:^3.2.9" + ignore: "npm:^5.2.0" + merge2: "npm:^1.4.1" + slash: "npm:^3.0.0" + checksum: 10c0/b39511b4afe4bd8a7aead3a27c4ade2b9968649abab0a6c28b1a90141b96ca68ca5db1302f7c7bd29eab66bf51e13916b8e0a3d0ac08f75e1e84a39b35691189 + languageName: node + linkType: hard + +"google-protobuf@npm:^3.12.0-rc.1": + version: 3.21.2 + resolution: "google-protobuf@npm:3.21.2" + checksum: 10c0/df20b41aad9eba4d842d69c717a4d73ac6d321084c12f524ad5eb79a47ad185323bd1b477c19565a15fd08b6eef29e475c8ac281dbc6fe547b81d8b6b99974f5 + languageName: node + linkType: hard + +"gopd@npm:^1.0.1": + version: 1.0.1 + resolution: "gopd@npm:1.0.1" + dependencies: + get-intrinsic: "npm:^1.1.3" + checksum: 10c0/505c05487f7944c552cee72087bf1567debb470d4355b1335f2c262d218ebbff805cd3715448fe29b4b380bae6912561d0467233e4165830efd28da241418c63 + languageName: node + linkType: hard + +"graceful-fs@npm:^4.2.6, graceful-fs@npm:^4.2.9": + version: 4.2.11 + resolution: "graceful-fs@npm:4.2.11" + checksum: 10c0/386d011a553e02bc594ac2ca0bd6d9e4c22d7fa8cfbfc448a6d148c59ea881b092db9dbe3547ae4b88e55f1b01f7c4a2ecc53b310c042793e63aa44cf6c257f2 + languageName: node + linkType: hard + +"graphemer@npm:^1.4.0": + version: 1.4.0 + resolution: "graphemer@npm:1.4.0" + checksum: 10c0/e951259d8cd2e0d196c72ec711add7115d42eb9a8146c8eeda5b8d3ac91e5dd816b9cd68920726d9fd4490368e7ed86e9c423f40db87e2d8dfafa00fa17c3a31 + languageName: node + linkType: hard + +"has-flag@npm:^3.0.0": + version: 3.0.0 + resolution: "has-flag@npm:3.0.0" + checksum: 10c0/1c6c83b14b8b1b3c25b0727b8ba3e3b647f99e9e6e13eb7322107261de07a4c1be56fc0d45678fc376e09772a3a1642ccdaf8fc69bdf123b6c086598397ce473 + languageName: node + linkType: hard + +"has-flag@npm:^4.0.0": + version: 4.0.0 + resolution: "has-flag@npm:4.0.0" + checksum: 10c0/2e789c61b7888d66993e14e8331449e525ef42aac53c627cc53d1c3334e768bcb6abdc4f5f0de1478a25beec6f0bd62c7549058b7ac53e924040d4f301f02fd1 + languageName: node + linkType: hard + +"has-property-descriptors@npm:^1.0.2": + version: 1.0.2 + resolution: "has-property-descriptors@npm:1.0.2" + dependencies: + es-define-property: "npm:^1.0.0" + checksum: 10c0/253c1f59e80bb476cf0dde8ff5284505d90c3bdb762983c3514d36414290475fe3fd6f574929d84de2a8eec00d35cf07cb6776205ff32efd7c50719125f00236 + languageName: node + linkType: hard + +"has-proto@npm:^1.0.1": + version: 1.0.3 + resolution: "has-proto@npm:1.0.3" + checksum: 10c0/35a6989f81e9f8022c2f4027f8b48a552de714938765d019dbea6bb547bd49ce5010a3c7c32ec6ddac6e48fc546166a3583b128f5a7add8b058a6d8b4afec205 + languageName: node + linkType: hard + +"has-symbols@npm:^1.0.3": + version: 1.0.3 + resolution: "has-symbols@npm:1.0.3" + checksum: 10c0/e6922b4345a3f37069cdfe8600febbca791c94988c01af3394d86ca3360b4b93928bbf395859158f88099cb10b19d98e3bbab7c9ff2c1bd09cf665ee90afa2c3 + languageName: node + linkType: hard + +"has-unicode@npm:^2.0.1": + version: 2.0.1 + resolution: "has-unicode@npm:2.0.1" + checksum: 10c0/ebdb2f4895c26bb08a8a100b62d362e49b2190bcfd84b76bc4be1a3bd4d254ec52d0dd9f2fbcc093fc5eb878b20c52146f9dfd33e2686ed28982187be593b47c + languageName: node + linkType: hard + +"hasown@npm:^2.0.0": + version: 2.0.2 + resolution: "hasown@npm:2.0.2" + dependencies: + function-bind: "npm:^1.1.2" + checksum: 10c0/3769d434703b8ac66b209a4cca0737519925bbdb61dd887f93a16372b14694c63ff4e797686d87c90f08168e81082248b9b028bad60d4da9e0d1148766f56eb9 + languageName: node + linkType: hard + +"hexoid@npm:^1.0.0": + version: 1.0.0 + resolution: "hexoid@npm:1.0.0" + checksum: 10c0/9c45e8ba676b9eb88455631ebceec4c829a8374a583410dc735472ab9808bf11339fcd074633c3fa30e420901b894d8a92ffd5e2e21eddd41149546e05a91f69 + languageName: node + linkType: hard + +"html-escaper@npm:^2.0.0": + version: 2.0.2 + resolution: "html-escaper@npm:2.0.2" + checksum: 10c0/208e8a12de1a6569edbb14544f4567e6ce8ecc30b9394fcaa4e7bb1e60c12a7c9a1ed27e31290817157e8626f3a4f29e76c8747030822eb84a6abb15c255f0a0 + languageName: node + linkType: hard + +"http-cache-semantics@npm:^4.1.1": + version: 4.1.1 + resolution: "http-cache-semantics@npm:4.1.1" + checksum: 10c0/ce1319b8a382eb3cbb4a37c19f6bfe14e5bb5be3d09079e885e8c513ab2d3cd9214902f8a31c9dc4e37022633ceabfc2d697405deeaf1b8f3552bb4ed996fdfc + languageName: node + linkType: hard + +"http-errors@npm:2.0.0": + version: 2.0.0 + resolution: "http-errors@npm:2.0.0" + dependencies: + depd: "npm:2.0.0" + inherits: "npm:2.0.4" + setprototypeof: "npm:1.2.0" + statuses: "npm:2.0.1" + toidentifier: "npm:1.0.1" + checksum: 10c0/fc6f2715fe188d091274b5ffc8b3657bd85c63e969daa68ccb77afb05b071a4b62841acb7a21e417b5539014dff2ebf9550f0b14a9ff126f2734a7c1387f8e19 + languageName: node + linkType: hard + +"http-proxy-agent@npm:^7.0.0": + version: 7.0.2 + resolution: "http-proxy-agent@npm:7.0.2" + dependencies: + agent-base: "npm:^7.1.0" + debug: "npm:^4.3.4" + checksum: 10c0/4207b06a4580fb85dd6dff521f0abf6db517489e70863dca1a0291daa7f2d3d2d6015a57bd702af068ea5cf9f1f6ff72314f5f5b4228d299c0904135d2aef921 + languageName: node + linkType: hard + +"https-proxy-agent@npm:^5.0.0": + version: 5.0.1 + resolution: "https-proxy-agent@npm:5.0.1" + dependencies: + agent-base: "npm:6" + debug: "npm:4" + checksum: 10c0/6dd639f03434003577c62b27cafdb864784ef19b2de430d8ae2a1d45e31c4fd60719e5637b44db1a88a046934307da7089e03d6089ec3ddacc1189d8de8897d1 + languageName: node + linkType: hard + +"https-proxy-agent@npm:^7.0.1": + version: 7.0.4 + resolution: "https-proxy-agent@npm:7.0.4" + dependencies: + agent-base: "npm:^7.0.2" + debug: "npm:4" + checksum: 10c0/bc4f7c38da32a5fc622450b6cb49a24ff596f9bd48dcedb52d2da3fa1c1a80e100fb506bd59b326c012f21c863c69b275c23de1a01d0b84db396822fdf25e52b + languageName: node + linkType: hard + +"human-signals@npm:^2.1.0": + version: 2.1.0 + resolution: "human-signals@npm:2.1.0" + checksum: 10c0/695edb3edfcfe9c8b52a76926cd31b36978782062c0ed9b1192b36bebc75c4c87c82e178dfcb0ed0fc27ca59d434198aac0bd0be18f5781ded775604db22304a + languageName: node + linkType: hard + +"iconv-lite@npm:0.4.24": + version: 0.4.24 + resolution: "iconv-lite@npm:0.4.24" + dependencies: + safer-buffer: "npm:>= 2.1.2 < 3" + checksum: 10c0/c6886a24cc00f2a059767440ec1bc00d334a89f250db8e0f7feb4961c8727118457e27c495ba94d082e51d3baca378726cd110aaf7ded8b9bbfd6a44760cf1d4 + languageName: node + linkType: hard + +"iconv-lite@npm:^0.6.2": + version: 0.6.3 + resolution: "iconv-lite@npm:0.6.3" + dependencies: + safer-buffer: "npm:>= 2.1.2 < 3.0.0" + checksum: 10c0/98102bc66b33fcf5ac044099d1257ba0b7ad5e3ccd3221f34dd508ab4070edff183276221684e1e0555b145fce0850c9f7d2b60a9fcac50fbb4ea0d6e845a3b1 + languageName: node + linkType: hard + +"ieee754@npm:^1.1.13": + version: 1.2.1 + resolution: "ieee754@npm:1.2.1" + checksum: 10c0/b0782ef5e0935b9f12883a2e2aa37baa75da6e66ce6515c168697b42160807d9330de9a32ec1ed73149aea02e0d822e572bca6f1e22bdcbd2149e13b050b17bb + languageName: node + linkType: hard + +"ignore@npm:^5.2.0": + version: 5.3.1 + resolution: "ignore@npm:5.3.1" + checksum: 10c0/703f7f45ffb2a27fb2c5a8db0c32e7dee66b33a225d28e8db4e1be6474795f606686a6e3bcc50e1aa12f2042db4c9d4a7d60af3250511de74620fbed052ea4cd + languageName: node + linkType: hard + +"import-fresh@npm:^3.2.1": + version: 3.3.0 + resolution: "import-fresh@npm:3.3.0" + dependencies: + parent-module: "npm:^1.0.0" + resolve-from: "npm:^4.0.0" + checksum: 10c0/7f882953aa6b740d1f0e384d0547158bc86efbf2eea0f1483b8900a6f65c5a5123c2cf09b0d542cc419d0b98a759ecaeb394237e97ea427f2da221dc3cd80cc3 + languageName: node + linkType: hard + +"import-local@npm:^3.0.2": + version: 3.1.0 + resolution: "import-local@npm:3.1.0" + dependencies: + pkg-dir: "npm:^4.2.0" + resolve-cwd: "npm:^3.0.0" + bin: + import-local-fixture: fixtures/cli.js + checksum: 10c0/c67ecea72f775fe8684ca3d057e54bdb2ae28c14bf261d2607c269c18ea0da7b730924c06262eca9aed4b8ab31e31d65bc60b50e7296c85908a56e2f7d41ecd2 + languageName: node + linkType: hard + +"imurmurhash@npm:^0.1.4": + version: 0.1.4 + resolution: "imurmurhash@npm:0.1.4" + checksum: 10c0/8b51313850dd33605c6c9d3fd9638b714f4c4c40250cff658209f30d40da60f78992fb2df5dabee4acf589a6a82bbc79ad5486550754bd9ec4e3fc0d4a57d6a6 + languageName: node + linkType: hard + +"indent-string@npm:^4.0.0": + version: 4.0.0 + resolution: "indent-string@npm:4.0.0" + checksum: 10c0/1e1904ddb0cb3d6cce7cd09e27a90184908b7a5d5c21b92e232c93579d314f0b83c246ffb035493d0504b1e9147ba2c9b21df0030f48673fba0496ecd698161f + languageName: node + linkType: hard + +"inflight@npm:^1.0.4": + version: 1.0.6 + resolution: "inflight@npm:1.0.6" + dependencies: + once: "npm:^1.3.0" + wrappy: "npm:1" + checksum: 10c0/7faca22584600a9dc5b9fca2cd5feb7135ac8c935449837b315676b4c90aa4f391ec4f42240178244b5a34e8bede1948627fda392ca3191522fc46b34e985ab2 + languageName: node + linkType: hard + +"inherits@npm:2, inherits@npm:2.0.4, inherits@npm:^2.0.3, inherits@npm:^2.0.4": + version: 2.0.4 + resolution: "inherits@npm:2.0.4" + checksum: 10c0/4e531f648b29039fb7426fb94075e6545faa1eb9fe83c29f0b6d9e7263aceb4289d2d4557db0d428188eeb449cc7c5e77b0a0b2c4e248ff2a65933a0dee49ef2 + languageName: node + linkType: hard + +"ini@npm:~1.3.0": + version: 1.3.8 + resolution: "ini@npm:1.3.8" + checksum: 10c0/ec93838d2328b619532e4f1ff05df7909760b6f66d9c9e2ded11e5c1897d6f2f9980c54dd638f88654b00919ce31e827040631eab0a3969e4d1abefa0719516a + languageName: node + linkType: hard + +"ip-address@npm:^9.0.5": + version: 9.0.5 + resolution: "ip-address@npm:9.0.5" + dependencies: + jsbn: "npm:1.1.0" + sprintf-js: "npm:^1.1.3" + checksum: 10c0/331cd07fafcb3b24100613e4b53e1a2b4feab11e671e655d46dc09ee233da5011284d09ca40c4ecbdfe1d0004f462958675c224a804259f2f78d2465a87824bc + languageName: node + linkType: hard + +"ipaddr.js@npm:1.9.1": + version: 1.9.1 + resolution: "ipaddr.js@npm:1.9.1" + checksum: 10c0/0486e775047971d3fdb5fb4f063829bac45af299ae0b82dcf3afa2145338e08290563a2a70f34b732d795ecc8311902e541a8530eeb30d75860a78ff4e94ce2a + languageName: node + linkType: hard + +"is-arrayish@npm:^0.2.1": + version: 0.2.1 + resolution: "is-arrayish@npm:0.2.1" + checksum: 10c0/e7fb686a739068bb70f860b39b67afc62acc62e36bb61c5f965768abce1873b379c563e61dd2adad96ebb7edf6651111b385e490cf508378959b0ed4cac4e729 + languageName: node + linkType: hard + +"is-arrayish@npm:^0.3.1": + version: 0.3.2 + resolution: "is-arrayish@npm:0.3.2" + checksum: 10c0/f59b43dc1d129edb6f0e282595e56477f98c40278a2acdc8b0a5c57097c9eff8fe55470493df5775478cf32a4dc8eaf6d3a749f07ceee5bc263a78b2434f6a54 + languageName: node + linkType: hard + +"is-core-module@npm:^2.13.0": + version: 2.13.1 + resolution: "is-core-module@npm:2.13.1" + dependencies: + hasown: "npm:^2.0.0" + checksum: 10c0/2cba9903aaa52718f11c4896dabc189bab980870aae86a62dc0d5cedb546896770ee946fb14c84b7adf0735f5eaea4277243f1b95f5cefa90054f92fbcac2518 + languageName: node + linkType: hard + +"is-extglob@npm:^2.1.1": + version: 2.1.1 + resolution: "is-extglob@npm:2.1.1" + checksum: 10c0/5487da35691fbc339700bbb2730430b07777a3c21b9ebaecb3072512dfd7b4ba78ac2381a87e8d78d20ea08affb3f1971b4af629173a6bf435ff8a4c47747912 + languageName: node + linkType: hard + +"is-fullwidth-code-point@npm:^3.0.0": + version: 3.0.0 + resolution: "is-fullwidth-code-point@npm:3.0.0" + checksum: 10c0/bb11d825e049f38e04c06373a8d72782eee0205bda9d908cc550ccb3c59b99d750ff9537982e01733c1c94a58e35400661f57042158ff5e8f3e90cf936daf0fc + languageName: node + linkType: hard + +"is-generator-fn@npm:^2.0.0": + version: 2.1.0 + resolution: "is-generator-fn@npm:2.1.0" + checksum: 10c0/2957cab387997a466cd0bf5c1b6047bd21ecb32bdcfd8996b15747aa01002c1c88731802f1b3d34ac99f4f6874b626418bd118658cf39380fe5fff32a3af9c4d + languageName: node + linkType: hard + +"is-glob@npm:^4.0.0, is-glob@npm:^4.0.1, is-glob@npm:^4.0.3": + version: 4.0.3 + resolution: "is-glob@npm:4.0.3" + dependencies: + is-extglob: "npm:^2.1.1" + checksum: 10c0/17fb4014e22be3bbecea9b2e3a76e9e34ff645466be702f1693e8f1ee1adac84710d0be0bd9f967d6354036fd51ab7c2741d954d6e91dae6bb69714de92c197a + languageName: node + linkType: hard + +"is-lambda@npm:^1.0.1": + version: 1.0.1 + resolution: "is-lambda@npm:1.0.1" + checksum: 10c0/85fee098ae62ba6f1e24cf22678805473c7afd0fb3978a3aa260e354cb7bcb3a5806cf0a98403188465efedec41ab4348e8e4e79305d409601323855b3839d4d + languageName: node + linkType: hard + +"is-number@npm:^7.0.0": + version: 7.0.0 + resolution: "is-number@npm:7.0.0" + checksum: 10c0/b4686d0d3053146095ccd45346461bc8e53b80aeb7671cc52a4de02dbbf7dc0d1d2a986e2fe4ae206984b4d34ef37e8b795ebc4f4295c978373e6575e295d811 + languageName: node + linkType: hard + +"is-path-inside@npm:^3.0.3": + version: 3.0.3 + resolution: "is-path-inside@npm:3.0.3" + checksum: 10c0/cf7d4ac35fb96bab6a1d2c3598fe5ebb29aafb52c0aaa482b5a3ed9d8ba3edc11631e3ec2637660c44b3ce0e61a08d54946e8af30dec0b60a7c27296c68ffd05 + languageName: node + linkType: hard + +"is-stream@npm:^2.0.0": + version: 2.0.1 + resolution: "is-stream@npm:2.0.1" + checksum: 10c0/7c284241313fc6efc329b8d7f08e16c0efeb6baab1b4cd0ba579eb78e5af1aa5da11e68559896a2067cd6c526bd29241dda4eb1225e627d5aa1a89a76d4635a5 + languageName: node + linkType: hard + +"isexe@npm:^2.0.0": + version: 2.0.0 + resolution: "isexe@npm:2.0.0" + checksum: 10c0/228cfa503fadc2c31596ab06ed6aa82c9976eec2bfd83397e7eaf06d0ccf42cd1dfd6743bf9aeb01aebd4156d009994c5f76ea898d2832c1fe342da923ca457d + languageName: node + linkType: hard + +"isexe@npm:^3.1.1": + version: 3.1.1 + resolution: "isexe@npm:3.1.1" + checksum: 10c0/9ec257654093443eb0a528a9c8cbba9c0ca7616ccb40abd6dde7202734d96bb86e4ac0d764f0f8cd965856aacbff2f4ce23e730dc19dfb41e3b0d865ca6fdcc7 + languageName: node + linkType: hard + +"istanbul-lib-coverage@npm:^3.0.0, istanbul-lib-coverage@npm:^3.2.0": + version: 3.2.2 + resolution: "istanbul-lib-coverage@npm:3.2.2" + checksum: 10c0/6c7ff2106769e5f592ded1fb418f9f73b4411fd5a084387a5410538332b6567cd1763ff6b6cadca9b9eb2c443cce2f7ea7d7f1b8d315f9ce58539793b1e0922b + languageName: node + linkType: hard + +"istanbul-lib-instrument@npm:^5.0.4": + version: 5.2.1 + resolution: "istanbul-lib-instrument@npm:5.2.1" + dependencies: + "@babel/core": "npm:^7.12.3" + "@babel/parser": "npm:^7.14.7" + "@istanbuljs/schema": "npm:^0.1.2" + istanbul-lib-coverage: "npm:^3.2.0" + semver: "npm:^6.3.0" + checksum: 10c0/8a1bdf3e377dcc0d33ec32fe2b6ecacdb1e4358fd0eb923d4326bb11c67622c0ceb99600a680f3dad5d29c66fc1991306081e339b4d43d0b8a2ab2e1d910a6ee + languageName: node + linkType: hard + +"istanbul-lib-instrument@npm:^6.0.0": + version: 6.0.2 + resolution: "istanbul-lib-instrument@npm:6.0.2" + dependencies: + "@babel/core": "npm:^7.23.9" + "@babel/parser": "npm:^7.23.9" + "@istanbuljs/schema": "npm:^0.1.3" + istanbul-lib-coverage: "npm:^3.2.0" + semver: "npm:^7.5.4" + checksum: 10c0/405c6ac037bf8c7ee7495980b0cd5544b2c53078c10534d0c9ceeb92a9ea7dcf8510f58ccfce31336458a8fa6ccef27b570bbb602abaa8c1650f5496a807477c + languageName: node + linkType: hard + +"istanbul-lib-report@npm:^3.0.0": + version: 3.0.1 + resolution: "istanbul-lib-report@npm:3.0.1" + dependencies: + istanbul-lib-coverage: "npm:^3.0.0" + make-dir: "npm:^4.0.0" + supports-color: "npm:^7.1.0" + checksum: 10c0/84323afb14392de8b6a5714bd7e9af845cfbd56cfe71ed276cda2f5f1201aea673c7111901227ee33e68e4364e288d73861eb2ed48f6679d1e69a43b6d9b3ba7 + languageName: node + linkType: hard + +"istanbul-lib-source-maps@npm:^4.0.0": + version: 4.0.1 + resolution: "istanbul-lib-source-maps@npm:4.0.1" + dependencies: + debug: "npm:^4.1.1" + istanbul-lib-coverage: "npm:^3.0.0" + source-map: "npm:^0.6.1" + checksum: 10c0/19e4cc405016f2c906dff271a76715b3e881fa9faeb3f09a86cb99b8512b3a5ed19cadfe0b54c17ca0e54c1142c9c6de9330d65506e35873994e06634eebeb66 + languageName: node + linkType: hard + +"istanbul-reports@npm:^3.1.3": + version: 3.1.7 + resolution: "istanbul-reports@npm:3.1.7" + dependencies: + html-escaper: "npm:^2.0.0" + istanbul-lib-report: "npm:^3.0.0" + checksum: 10c0/a379fadf9cf8dc5dfe25568115721d4a7eb82fbd50b005a6672aff9c6989b20cc9312d7865814e0859cd8df58cbf664482e1d3604be0afde1f7fc3ccc1394a51 + languageName: node + linkType: hard + +"jackspeak@npm:^3.1.2": + version: 3.4.0 + resolution: "jackspeak@npm:3.4.0" + dependencies: + "@isaacs/cliui": "npm:^8.0.2" + "@pkgjs/parseargs": "npm:^0.11.0" + dependenciesMeta: + "@pkgjs/parseargs": + optional: true + checksum: 10c0/7e42d1ea411b4d57d43ea8a6afbca9224382804359cb72626d0fc45bb8db1de5ad0248283c3db45fe73e77210750d4fcc7c2b4fe5d24fda94aaa24d658295c5f + languageName: node + linkType: hard + +"jest-changed-files@npm:^29.7.0": + version: 29.7.0 + resolution: "jest-changed-files@npm:29.7.0" + dependencies: + execa: "npm:^5.0.0" + jest-util: "npm:^29.7.0" + p-limit: "npm:^3.1.0" + checksum: 10c0/e071384d9e2f6bb462231ac53f29bff86f0e12394c1b49ccafbad225ce2ab7da226279a8a94f421949920bef9be7ef574fd86aee22e8adfa149be73554ab828b + languageName: node + linkType: hard + +"jest-circus@npm:^29.7.0": + version: 29.7.0 + resolution: "jest-circus@npm:29.7.0" + dependencies: + "@jest/environment": "npm:^29.7.0" + "@jest/expect": "npm:^29.7.0" + "@jest/test-result": "npm:^29.7.0" + "@jest/types": "npm:^29.6.3" + "@types/node": "npm:*" + chalk: "npm:^4.0.0" + co: "npm:^4.6.0" + dedent: "npm:^1.0.0" + is-generator-fn: "npm:^2.0.0" + jest-each: "npm:^29.7.0" + jest-matcher-utils: "npm:^29.7.0" + jest-message-util: "npm:^29.7.0" + jest-runtime: "npm:^29.7.0" + jest-snapshot: "npm:^29.7.0" + jest-util: "npm:^29.7.0" + p-limit: "npm:^3.1.0" + pretty-format: "npm:^29.7.0" + pure-rand: "npm:^6.0.0" + slash: "npm:^3.0.0" + stack-utils: "npm:^2.0.3" + checksum: 10c0/8d15344cf7a9f14e926f0deed64ed190c7a4fa1ed1acfcd81e4cc094d3cc5bf7902ebb7b874edc98ada4185688f90c91e1747e0dfd7ac12463b097968ae74b5e + languageName: node + linkType: hard + +"jest-cli@npm:^29.7.0": + version: 29.7.0 + resolution: "jest-cli@npm:29.7.0" + dependencies: + "@jest/core": "npm:^29.7.0" + "@jest/test-result": "npm:^29.7.0" + "@jest/types": "npm:^29.6.3" + chalk: "npm:^4.0.0" + create-jest: "npm:^29.7.0" + exit: "npm:^0.1.2" + import-local: "npm:^3.0.2" + jest-config: "npm:^29.7.0" + jest-util: "npm:^29.7.0" + jest-validate: "npm:^29.7.0" + yargs: "npm:^17.3.1" + peerDependencies: + node-notifier: ^8.0.1 || ^9.0.0 || ^10.0.0 + peerDependenciesMeta: + node-notifier: + optional: true + bin: + jest: bin/jest.js + checksum: 10c0/a658fd55050d4075d65c1066364595962ead7661711495cfa1dfeecf3d6d0a8ffec532f3dbd8afbb3e172dd5fd2fb2e813c5e10256e7cf2fea766314942fb43a + languageName: node + linkType: hard + +"jest-config@npm:^29.7.0": + version: 29.7.0 + resolution: "jest-config@npm:29.7.0" + dependencies: + "@babel/core": "npm:^7.11.6" + "@jest/test-sequencer": "npm:^29.7.0" + "@jest/types": "npm:^29.6.3" + babel-jest: "npm:^29.7.0" + chalk: "npm:^4.0.0" + ci-info: "npm:^3.2.0" + deepmerge: "npm:^4.2.2" + glob: "npm:^7.1.3" + graceful-fs: "npm:^4.2.9" + jest-circus: "npm:^29.7.0" + jest-environment-node: "npm:^29.7.0" + jest-get-type: "npm:^29.6.3" + jest-regex-util: "npm:^29.6.3" + jest-resolve: "npm:^29.7.0" + jest-runner: "npm:^29.7.0" + jest-util: "npm:^29.7.0" + jest-validate: "npm:^29.7.0" + micromatch: "npm:^4.0.4" + parse-json: "npm:^5.2.0" + pretty-format: "npm:^29.7.0" + slash: "npm:^3.0.0" + strip-json-comments: "npm:^3.1.1" + peerDependencies: + "@types/node": "*" + ts-node: ">=9.0.0" + peerDependenciesMeta: + "@types/node": + optional: true + ts-node: + optional: true + checksum: 10c0/bab23c2eda1fff06e0d104b00d6adfb1d1aabb7128441899c9bff2247bd26710b050a5364281ce8d52b46b499153bf7e3ee88b19831a8f3451f1477a0246a0f1 + languageName: node + linkType: hard + +"jest-diff@npm:^29.7.0": + version: 29.7.0 + resolution: "jest-diff@npm:29.7.0" + dependencies: + chalk: "npm:^4.0.0" + diff-sequences: "npm:^29.6.3" + jest-get-type: "npm:^29.6.3" + pretty-format: "npm:^29.7.0" + checksum: 10c0/89a4a7f182590f56f526443dde69acefb1f2f0c9e59253c61d319569856c4931eae66b8a3790c443f529267a0ddba5ba80431c585deed81827032b2b2a1fc999 + languageName: node + linkType: hard + +"jest-docblock@npm:^29.7.0": + version: 29.7.0 + resolution: "jest-docblock@npm:29.7.0" + dependencies: + detect-newline: "npm:^3.0.0" + checksum: 10c0/d932a8272345cf6b6142bb70a2bb63e0856cc0093f082821577ea5bdf4643916a98744dfc992189d2b1417c38a11fa42466f6111526bc1fb81366f56410f3be9 + languageName: node + linkType: hard + +"jest-each@npm:^29.7.0": + version: 29.7.0 + resolution: "jest-each@npm:29.7.0" + dependencies: + "@jest/types": "npm:^29.6.3" + chalk: "npm:^4.0.0" + jest-get-type: "npm:^29.6.3" + jest-util: "npm:^29.7.0" + pretty-format: "npm:^29.7.0" + checksum: 10c0/f7f9a90ebee80cc688e825feceb2613627826ac41ea76a366fa58e669c3b2403d364c7c0a74d862d469b103c843154f8456d3b1c02b487509a12afa8b59edbb4 + languageName: node + linkType: hard + +"jest-environment-node@npm:^29.7.0": + version: 29.7.0 + resolution: "jest-environment-node@npm:29.7.0" + dependencies: + "@jest/environment": "npm:^29.7.0" + "@jest/fake-timers": "npm:^29.7.0" + "@jest/types": "npm:^29.6.3" + "@types/node": "npm:*" + jest-mock: "npm:^29.7.0" + jest-util: "npm:^29.7.0" + checksum: 10c0/61f04fec077f8b1b5c1a633e3612fc0c9aa79a0ab7b05600683428f1e01a4d35346c474bde6f439f9fcc1a4aa9a2861ff852d079a43ab64b02105d1004b2592b + languageName: node + linkType: hard + +"jest-get-type@npm:^29.6.3": + version: 29.6.3 + resolution: "jest-get-type@npm:29.6.3" + checksum: 10c0/552e7a97a983d3c2d4e412a44eb7de0430ff773dd99f7500962c268d6dfbfa431d7d08f919c9d960530e5f7f78eb47f267ad9b318265e5092b3ff9ede0db7c2b + languageName: node + linkType: hard + +"jest-haste-map@npm:^29.7.0": + version: 29.7.0 + resolution: "jest-haste-map@npm:29.7.0" + dependencies: + "@jest/types": "npm:^29.6.3" + "@types/graceful-fs": "npm:^4.1.3" + "@types/node": "npm:*" + anymatch: "npm:^3.0.3" + fb-watchman: "npm:^2.0.0" + fsevents: "npm:^2.3.2" + graceful-fs: "npm:^4.2.9" + jest-regex-util: "npm:^29.6.3" + jest-util: "npm:^29.7.0" + jest-worker: "npm:^29.7.0" + micromatch: "npm:^4.0.4" + walker: "npm:^1.0.8" + dependenciesMeta: + fsevents: + optional: true + checksum: 10c0/2683a8f29793c75a4728787662972fedd9267704c8f7ef9d84f2beed9a977f1cf5e998c07b6f36ba5603f53cb010c911fe8cd0ac9886e073fe28ca66beefd30c + languageName: node + linkType: hard + +"jest-leak-detector@npm:^29.7.0": + version: 29.7.0 + resolution: "jest-leak-detector@npm:29.7.0" + dependencies: + jest-get-type: "npm:^29.6.3" + pretty-format: "npm:^29.7.0" + checksum: 10c0/71bb9f77fc489acb842a5c7be030f2b9acb18574dc9fb98b3100fc57d422b1abc55f08040884bd6e6dbf455047a62f7eaff12aa4058f7cbdc11558718ca6a395 + languageName: node + linkType: hard + +"jest-matcher-utils@npm:^29.7.0": + version: 29.7.0 + resolution: "jest-matcher-utils@npm:29.7.0" + dependencies: + chalk: "npm:^4.0.0" + jest-diff: "npm:^29.7.0" + jest-get-type: "npm:^29.6.3" + pretty-format: "npm:^29.7.0" + checksum: 10c0/0d0e70b28fa5c7d4dce701dc1f46ae0922102aadc24ed45d594dd9b7ae0a8a6ef8b216718d1ab79e451291217e05d4d49a82666e1a3cc2b428b75cd9c933244e + languageName: node + linkType: hard + +"jest-message-util@npm:^29.7.0": + version: 29.7.0 + resolution: "jest-message-util@npm:29.7.0" + dependencies: + "@babel/code-frame": "npm:^7.12.13" + "@jest/types": "npm:^29.6.3" + "@types/stack-utils": "npm:^2.0.0" + chalk: "npm:^4.0.0" + graceful-fs: "npm:^4.2.9" + micromatch: "npm:^4.0.4" + pretty-format: "npm:^29.7.0" + slash: "npm:^3.0.0" + stack-utils: "npm:^2.0.3" + checksum: 10c0/850ae35477f59f3e6f27efac5215f706296e2104af39232bb14e5403e067992afb5c015e87a9243ec4d9df38525ef1ca663af9f2f4766aa116f127247008bd22 + languageName: node + linkType: hard + +"jest-mock@npm:^29.7.0": + version: 29.7.0 + resolution: "jest-mock@npm:29.7.0" + dependencies: + "@jest/types": "npm:^29.6.3" + "@types/node": "npm:*" + jest-util: "npm:^29.7.0" + checksum: 10c0/7b9f8349ee87695a309fe15c46a74ab04c853369e5c40952d68061d9dc3159a0f0ed73e215f81b07ee97a9faaf10aebe5877a9d6255068a0977eae6a9ff1d5ac + languageName: node + linkType: hard + +"jest-pnp-resolver@npm:^1.2.2": + version: 1.2.3 + resolution: "jest-pnp-resolver@npm:1.2.3" + peerDependencies: + jest-resolve: "*" + peerDependenciesMeta: + jest-resolve: + optional: true + checksum: 10c0/86eec0c78449a2de733a6d3e316d49461af6a858070e113c97f75fb742a48c2396ea94150cbca44159ffd4a959f743a47a8b37a792ef6fdad2cf0a5cba973fac + languageName: node + linkType: hard + +"jest-regex-util@npm:^29.6.3": + version: 29.6.3 + resolution: "jest-regex-util@npm:29.6.3" + checksum: 10c0/4e33fb16c4f42111159cafe26397118dcfc4cf08bc178a67149fb05f45546a91928b820894572679d62559839d0992e21080a1527faad65daaae8743a5705a3b + languageName: node + linkType: hard + +"jest-resolve-dependencies@npm:^29.7.0": + version: 29.7.0 + resolution: "jest-resolve-dependencies@npm:29.7.0" + dependencies: + jest-regex-util: "npm:^29.6.3" + jest-snapshot: "npm:^29.7.0" + checksum: 10c0/b6e9ad8ae5b6049474118ea6441dfddd385b6d1fc471db0136f7c8fbcfe97137a9665e4f837a9f49f15a29a1deb95a14439b7aec812f3f99d08f228464930f0d + languageName: node + linkType: hard + +"jest-resolve@npm:^29.7.0": + version: 29.7.0 + resolution: "jest-resolve@npm:29.7.0" + dependencies: + chalk: "npm:^4.0.0" + graceful-fs: "npm:^4.2.9" + jest-haste-map: "npm:^29.7.0" + jest-pnp-resolver: "npm:^1.2.2" + jest-util: "npm:^29.7.0" + jest-validate: "npm:^29.7.0" + resolve: "npm:^1.20.0" + resolve.exports: "npm:^2.0.0" + slash: "npm:^3.0.0" + checksum: 10c0/59da5c9c5b50563e959a45e09e2eace783d7f9ac0b5dcc6375dea4c0db938d2ebda97124c8161310082760e8ebbeff9f6b177c15ca2f57fb424f637a5d2adb47 + languageName: node + linkType: hard + +"jest-runner@npm:^29.7.0": + version: 29.7.0 + resolution: "jest-runner@npm:29.7.0" + dependencies: + "@jest/console": "npm:^29.7.0" + "@jest/environment": "npm:^29.7.0" + "@jest/test-result": "npm:^29.7.0" + "@jest/transform": "npm:^29.7.0" + "@jest/types": "npm:^29.6.3" + "@types/node": "npm:*" + chalk: "npm:^4.0.0" + emittery: "npm:^0.13.1" + graceful-fs: "npm:^4.2.9" + jest-docblock: "npm:^29.7.0" + jest-environment-node: "npm:^29.7.0" + jest-haste-map: "npm:^29.7.0" + jest-leak-detector: "npm:^29.7.0" + jest-message-util: "npm:^29.7.0" + jest-resolve: "npm:^29.7.0" + jest-runtime: "npm:^29.7.0" + jest-util: "npm:^29.7.0" + jest-watcher: "npm:^29.7.0" + jest-worker: "npm:^29.7.0" + p-limit: "npm:^3.1.0" + source-map-support: "npm:0.5.13" + checksum: 10c0/2194b4531068d939f14c8d3274fe5938b77fa73126aedf9c09ec9dec57d13f22c72a3b5af01ac04f5c1cf2e28d0ac0b4a54212a61b05f10b5d6b47f2a1097bb4 + languageName: node + linkType: hard + +"jest-runtime@npm:^29.7.0": + version: 29.7.0 + resolution: "jest-runtime@npm:29.7.0" + dependencies: + "@jest/environment": "npm:^29.7.0" + "@jest/fake-timers": "npm:^29.7.0" + "@jest/globals": "npm:^29.7.0" + "@jest/source-map": "npm:^29.6.3" + "@jest/test-result": "npm:^29.7.0" + "@jest/transform": "npm:^29.7.0" + "@jest/types": "npm:^29.6.3" + "@types/node": "npm:*" + chalk: "npm:^4.0.0" + cjs-module-lexer: "npm:^1.0.0" + collect-v8-coverage: "npm:^1.0.0" + glob: "npm:^7.1.3" + graceful-fs: "npm:^4.2.9" + jest-haste-map: "npm:^29.7.0" + jest-message-util: "npm:^29.7.0" + jest-mock: "npm:^29.7.0" + jest-regex-util: "npm:^29.6.3" + jest-resolve: "npm:^29.7.0" + jest-snapshot: "npm:^29.7.0" + jest-util: "npm:^29.7.0" + slash: "npm:^3.0.0" + strip-bom: "npm:^4.0.0" + checksum: 10c0/7cd89a1deda0bda7d0941835434e44f9d6b7bd50b5c5d9b0fc9a6c990b2d4d2cab59685ab3cb2850ed4cc37059f6de903af5a50565d7f7f1192a77d3fd6dd2a6 + languageName: node + linkType: hard + +"jest-snapshot@npm:^29.7.0": + version: 29.7.0 + resolution: "jest-snapshot@npm:29.7.0" + dependencies: + "@babel/core": "npm:^7.11.6" + "@babel/generator": "npm:^7.7.2" + "@babel/plugin-syntax-jsx": "npm:^7.7.2" + "@babel/plugin-syntax-typescript": "npm:^7.7.2" + "@babel/types": "npm:^7.3.3" + "@jest/expect-utils": "npm:^29.7.0" + "@jest/transform": "npm:^29.7.0" + "@jest/types": "npm:^29.6.3" + babel-preset-current-node-syntax: "npm:^1.0.0" + chalk: "npm:^4.0.0" + expect: "npm:^29.7.0" + graceful-fs: "npm:^4.2.9" + jest-diff: "npm:^29.7.0" + jest-get-type: "npm:^29.6.3" + jest-matcher-utils: "npm:^29.7.0" + jest-message-util: "npm:^29.7.0" + jest-util: "npm:^29.7.0" + natural-compare: "npm:^1.4.0" + pretty-format: "npm:^29.7.0" + semver: "npm:^7.5.3" + checksum: 10c0/6e9003c94ec58172b4a62864a91c0146513207bedf4e0a06e1e2ac70a4484088a2683e3a0538d8ea913bcfd53dc54a9b98a98cdfa562e7fe1d1339aeae1da570 + languageName: node + linkType: hard + +"jest-util@npm:^29.7.0": + version: 29.7.0 + resolution: "jest-util@npm:29.7.0" + dependencies: + "@jest/types": "npm:^29.6.3" + "@types/node": "npm:*" + chalk: "npm:^4.0.0" + ci-info: "npm:^3.2.0" + graceful-fs: "npm:^4.2.9" + picomatch: "npm:^2.2.3" + checksum: 10c0/bc55a8f49fdbb8f51baf31d2a4f312fb66c9db1483b82f602c9c990e659cdd7ec529c8e916d5a89452ecbcfae4949b21b40a7a59d4ffc0cd813a973ab08c8150 + languageName: node + linkType: hard + +"jest-validate@npm:^29.7.0": + version: 29.7.0 + resolution: "jest-validate@npm:29.7.0" + dependencies: + "@jest/types": "npm:^29.6.3" + camelcase: "npm:^6.2.0" + chalk: "npm:^4.0.0" + jest-get-type: "npm:^29.6.3" + leven: "npm:^3.1.0" + pretty-format: "npm:^29.7.0" + checksum: 10c0/a20b930480c1ed68778c739f4739dce39423131bc070cd2505ddede762a5570a256212e9c2401b7ae9ba4d7b7c0803f03c5b8f1561c62348213aba18d9dbece2 + languageName: node + linkType: hard + +"jest-watcher@npm:^29.7.0": + version: 29.7.0 + resolution: "jest-watcher@npm:29.7.0" + dependencies: + "@jest/test-result": "npm:^29.7.0" + "@jest/types": "npm:^29.6.3" + "@types/node": "npm:*" + ansi-escapes: "npm:^4.2.1" + chalk: "npm:^4.0.0" + emittery: "npm:^0.13.1" + jest-util: "npm:^29.7.0" + string-length: "npm:^4.0.1" + checksum: 10c0/ec6c75030562fc8f8c727cb8f3b94e75d831fc718785abfc196e1f2a2ebc9a2e38744a15147170039628a853d77a3b695561ce850375ede3a4ee6037a2574567 + languageName: node + linkType: hard + +"jest-worker@npm:^29.7.0": + version: 29.7.0 + resolution: "jest-worker@npm:29.7.0" + dependencies: + "@types/node": "npm:*" + jest-util: "npm:^29.7.0" + merge-stream: "npm:^2.0.0" + supports-color: "npm:^8.0.0" + checksum: 10c0/5570a3a005b16f46c131968b8a5b56d291f9bbb85ff4217e31c80bd8a02e7de799e59a54b95ca28d5c302f248b54cbffde2d177c2f0f52ffcee7504c6eabf660 + languageName: node + linkType: hard + +"jest@npm:^29.3.1": + version: 29.7.0 + resolution: "jest@npm:29.7.0" + dependencies: + "@jest/core": "npm:^29.7.0" + "@jest/types": "npm:^29.6.3" + import-local: "npm:^3.0.2" + jest-cli: "npm:^29.7.0" + peerDependencies: + node-notifier: ^8.0.1 || ^9.0.0 || ^10.0.0 + peerDependenciesMeta: + node-notifier: + optional: true + bin: + jest: bin/jest.js + checksum: 10c0/f40eb8171cf147c617cc6ada49d062fbb03b4da666cb8d39cdbfb739a7d75eea4c3ca150fb072d0d273dce0c753db4d0467d54906ad0293f59c54f9db4a09d8b + languageName: node + linkType: hard + +"jose@npm:^4.15.5": + version: 4.15.9 + resolution: "jose@npm:4.15.9" + checksum: 10c0/4ed4ddf4a029db04bd167f2215f65d7245e4dc5f36d7ac3c0126aab38d66309a9e692f52df88975d99429e357e5fd8bab340ff20baab544d17684dd1d940a0f4 + languageName: node + linkType: hard + +"js-tokens@npm:^4.0.0": + version: 4.0.0 + resolution: "js-tokens@npm:4.0.0" + checksum: 10c0/e248708d377aa058eacf2037b07ded847790e6de892bbad3dac0abba2e759cb9f121b00099a65195616badcb6eca8d14d975cb3e89eb1cfda644756402c8aeed + languageName: node + linkType: hard + +"js-yaml@npm:^3.13.1": + version: 3.14.1 + resolution: "js-yaml@npm:3.14.1" + dependencies: + argparse: "npm:^1.0.7" + esprima: "npm:^4.0.0" + bin: + js-yaml: bin/js-yaml.js + checksum: 10c0/6746baaaeac312c4db8e75fa22331d9a04cccb7792d126ed8ce6a0bbcfef0cedaddd0c5098fade53db067c09fe00aa1c957674b4765610a8b06a5a189e46433b + languageName: node + linkType: hard + +"js-yaml@npm:^4.1.0": + version: 4.1.0 + resolution: "js-yaml@npm:4.1.0" + dependencies: + argparse: "npm:^2.0.1" + bin: + js-yaml: bin/js-yaml.js + checksum: 10c0/184a24b4eaacfce40ad9074c64fd42ac83cf74d8c8cd137718d456ced75051229e5061b8633c3366b8aada17945a7a356b337828c19da92b51ae62126575018f + languageName: node + linkType: hard + +"jsbn@npm:1.1.0": + version: 1.1.0 + resolution: "jsbn@npm:1.1.0" + checksum: 10c0/4f907fb78d7b712e11dea8c165fe0921f81a657d3443dde75359ed52eb2b5d33ce6773d97985a089f09a65edd80b11cb75c767b57ba47391fee4c969f7215c96 + languageName: node + linkType: hard + +"jsesc@npm:^2.5.1": + version: 2.5.2 + resolution: "jsesc@npm:2.5.2" + bin: + jsesc: bin/jsesc + checksum: 10c0/dbf59312e0ebf2b4405ef413ec2b25abb5f8f4d9bc5fb8d9f90381622ebca5f2af6a6aa9a8578f65903f9e33990a6dc798edd0ce5586894bf0e9e31803a1de88 + languageName: node + linkType: hard + +"json-buffer@npm:3.0.1": + version: 3.0.1 + resolution: "json-buffer@npm:3.0.1" + checksum: 10c0/0d1c91569d9588e7eef2b49b59851f297f3ab93c7b35c7c221e288099322be6b562767d11e4821da500f3219542b9afd2e54c5dc573107c1126ed1080f8e96d7 + languageName: node + linkType: hard + +"json-parse-even-better-errors@npm:^2.3.0": + version: 2.3.1 + resolution: "json-parse-even-better-errors@npm:2.3.1" + checksum: 10c0/140932564c8f0b88455432e0f33c4cb4086b8868e37524e07e723f4eaedb9425bdc2bafd71bd1d9765bd15fd1e2d126972bc83990f55c467168c228c24d665f3 + languageName: node + linkType: hard + +"json-schema-traverse@npm:^0.4.1": + version: 0.4.1 + resolution: "json-schema-traverse@npm:0.4.1" + checksum: 10c0/108fa90d4cc6f08243aedc6da16c408daf81793bf903e9fd5ab21983cda433d5d2da49e40711da016289465ec2e62e0324dcdfbc06275a607fe3233fde4942ce + languageName: node + linkType: hard + +"json-stable-stringify-without-jsonify@npm:^1.0.1": + version: 1.0.1 + resolution: "json-stable-stringify-without-jsonify@npm:1.0.1" + checksum: 10c0/cb168b61fd4de83e58d09aaa6425ef71001bae30d260e2c57e7d09a5fd82223e2f22a042dedaab8db23b7d9ae46854b08bb1f91675a8be11c5cffebef5fb66a5 + languageName: node + linkType: hard + +"json5@npm:^2.2.3": + version: 2.2.3 + resolution: "json5@npm:2.2.3" + bin: + json5: lib/cli.js + checksum: 10c0/5a04eed94810fa55c5ea138b2f7a5c12b97c3750bc63d11e511dcecbfef758003861522a070c2272764ee0f4e3e323862f386945aeb5b85b87ee43f084ba586c + languageName: node + linkType: hard + +"jwa@npm:^2.0.0": + version: 2.0.0 + resolution: "jwa@npm:2.0.0" + dependencies: + buffer-equal-constant-time: "npm:1.0.1" + ecdsa-sig-formatter: "npm:1.0.11" + safe-buffer: "npm:^5.0.1" + checksum: 10c0/6baab823b93c038ba1d2a9e531984dcadbc04e9eb98d171f4901b7a40d2be15961a359335de1671d78cb6d987f07cbe5d350d8143255977a889160c4d90fcc3c + languageName: node + linkType: hard + +"jws@npm:^4.0.0": + version: 4.0.0 + resolution: "jws@npm:4.0.0" + dependencies: + jwa: "npm:^2.0.0" + safe-buffer: "npm:^5.0.1" + checksum: 10c0/f1ca77ea5451e8dc5ee219cb7053b8a4f1254a79cb22417a2e1043c1eb8a569ae118c68f24d72a589e8a3dd1824697f47d6bd4fb4bebb93a3bdf53545e721661 + languageName: node + linkType: hard + +"keyv@npm:^4.5.3": + version: 4.5.4 + resolution: "keyv@npm:4.5.4" + dependencies: + json-buffer: "npm:3.0.1" + checksum: 10c0/aa52f3c5e18e16bb6324876bb8b59dd02acf782a4b789c7b2ae21107fab95fab3890ed448d4f8dba80ce05391eeac4bfabb4f02a20221342982f806fa2cf271e + languageName: node + linkType: hard + +"kleur@npm:^3.0.3": + version: 3.0.3 + resolution: "kleur@npm:3.0.3" + checksum: 10c0/cd3a0b8878e7d6d3799e54340efe3591ca787d9f95f109f28129bdd2915e37807bf8918bb295ab86afb8c82196beec5a1adcaf29042ce3f2bd932b038fe3aa4b + languageName: node + linkType: hard + +"kuler@npm:^2.0.0": + version: 2.0.0 + resolution: "kuler@npm:2.0.0" + checksum: 10c0/0a4e99d92ca373f8f74d1dc37931909c4d0d82aebc94cf2ba265771160fc12c8df34eaaac80805efbda367e2795cb1f1dd4c3d404b6b1cf38aec94035b503d2d + languageName: node + linkType: hard + +"leven@npm:^3.1.0": + version: 3.1.0 + resolution: "leven@npm:3.1.0" + checksum: 10c0/cd778ba3fbab0f4d0500b7e87d1f6e1f041507c56fdcd47e8256a3012c98aaee371d4c15e0a76e0386107af2d42e2b7466160a2d80688aaa03e66e49949f42df + languageName: node + linkType: hard + +"levn@npm:^0.4.1": + version: 0.4.1 + resolution: "levn@npm:0.4.1" + dependencies: + prelude-ls: "npm:^1.2.1" + type-check: "npm:~0.4.0" + checksum: 10c0/effb03cad7c89dfa5bd4f6989364bfc79994c2042ec5966cb9b95990e2edee5cd8969ddf42616a0373ac49fac1403437deaf6e9050fbbaa3546093a59b9ac94e + languageName: node + linkType: hard + +"lines-and-columns@npm:^1.1.6": + version: 1.2.4 + resolution: "lines-and-columns@npm:1.2.4" + checksum: 10c0/3da6ee62d4cd9f03f5dc90b4df2540fb85b352081bee77fe4bbcd12c9000ead7f35e0a38b8d09a9bb99b13223446dd8689ff3c4959807620726d788701a83d2d + languageName: node + linkType: hard + +"locate-path@npm:^5.0.0": + version: 5.0.0 + resolution: "locate-path@npm:5.0.0" + dependencies: + p-locate: "npm:^4.1.0" + checksum: 10c0/33a1c5247e87e022f9713e6213a744557a3e9ec32c5d0b5efb10aa3a38177615bf90221a5592674857039c1a0fd2063b82f285702d37b792d973e9e72ace6c59 + languageName: node + linkType: hard + +"locate-path@npm:^6.0.0": + version: 6.0.0 + resolution: "locate-path@npm:6.0.0" + dependencies: + p-locate: "npm:^5.0.0" + checksum: 10c0/d3972ab70dfe58ce620e64265f90162d247e87159b6126b01314dd67be43d50e96a50b517bce2d9452a79409c7614054c277b5232377de50416564a77ac7aad3 + languageName: node + linkType: hard + +"lodash.merge@npm:^4.6.2": + version: 4.6.2 + resolution: "lodash.merge@npm:4.6.2" + checksum: 10c0/402fa16a1edd7538de5b5903a90228aa48eb5533986ba7fa26606a49db2572bf414ff73a2c9f5d5fd36b31c46a5d5c7e1527749c07cbcf965ccff5fbdf32c506 + languageName: node + linkType: hard + +"lodash@npm:^4.17.21": + version: 4.17.21 + resolution: "lodash@npm:4.17.21" + checksum: 10c0/d8cbea072bb08655bb4c989da418994b073a608dffa608b09ac04b43a791b12aeae7cd7ad919aa4c925f33b48490b5cfe6c1f71d827956071dae2e7bb3a6b74c + languageName: node + linkType: hard + +"logform@npm:^2.6.0, logform@npm:^2.6.1": + version: 2.6.1 + resolution: "logform@npm:2.6.1" + dependencies: + "@colors/colors": "npm:1.6.0" + "@types/triple-beam": "npm:^1.3.2" + fecha: "npm:^4.2.0" + ms: "npm:^2.1.1" + safe-stable-stringify: "npm:^2.3.1" + triple-beam: "npm:^1.3.0" + checksum: 10c0/c20019336b1da8c08adea67dd7de2b0effdc6e35289c0156722924b571df94ba9f900ef55620c56bceb07cae7cc46057c9859accdee37a131251ba34d6789bce + languageName: node + linkType: hard + +"lru-cache@npm:^10.0.1, lru-cache@npm:^10.2.0": + version: 10.2.2 + resolution: "lru-cache@npm:10.2.2" + checksum: 10c0/402d31094335851220d0b00985084288136136992979d0e015f0f1697e15d1c86052d7d53ae86b614e5b058425606efffc6969a31a091085d7a2b80a8a1e26d6 + languageName: node + linkType: hard + +"lru-cache@npm:^5.1.1": + version: 5.1.1 + resolution: "lru-cache@npm:5.1.1" + dependencies: + yallist: "npm:^3.0.2" + checksum: 10c0/89b2ef2ef45f543011e38737b8a8622a2f8998cddf0e5437174ef8f1f70a8b9d14a918ab3e232cb3ba343b7abddffa667f0b59075b2b80e6b4d63c3de6127482 + languageName: node + linkType: hard + +"lru-cache@npm:^6.0.0": + version: 6.0.0 + resolution: "lru-cache@npm:6.0.0" + dependencies: + yallist: "npm:^4.0.0" + checksum: 10c0/cb53e582785c48187d7a188d3379c181b5ca2a9c78d2bce3e7dee36f32761d1c42983da3fe12b55cb74e1779fa94cdc2e5367c028a9b35317184ede0c07a30a9 + languageName: node + linkType: hard + +"make-dir@npm:^3.1.0": + version: 3.1.0 + resolution: "make-dir@npm:3.1.0" + dependencies: + semver: "npm:^6.0.0" + checksum: 10c0/56aaafefc49c2dfef02c5c95f9b196c4eb6988040cf2c712185c7fe5c99b4091591a7fc4d4eafaaefa70ff763a26f6ab8c3ff60b9e75ea19876f49b18667ecaa + languageName: node + linkType: hard + +"make-dir@npm:^4.0.0": + version: 4.0.0 + resolution: "make-dir@npm:4.0.0" + dependencies: + semver: "npm:^7.5.3" + checksum: 10c0/69b98a6c0b8e5c4fe9acb61608a9fbcfca1756d910f51e5dbe7a9e5cfb74fca9b8a0c8a0ffdf1294a740826c1ab4871d5bf3f62f72a3049e5eac6541ddffed68 + languageName: node + linkType: hard + +"make-fetch-happen@npm:^13.0.0": + version: 13.0.1 + resolution: "make-fetch-happen@npm:13.0.1" + dependencies: + "@npmcli/agent": "npm:^2.0.0" + cacache: "npm:^18.0.0" + http-cache-semantics: "npm:^4.1.1" + is-lambda: "npm:^1.0.1" + minipass: "npm:^7.0.2" + minipass-fetch: "npm:^3.0.0" + minipass-flush: "npm:^1.0.5" + minipass-pipeline: "npm:^1.2.4" + negotiator: "npm:^0.6.3" + proc-log: "npm:^4.2.0" + promise-retry: "npm:^2.0.1" + ssri: "npm:^10.0.0" + checksum: 10c0/df5f4dbb6d98153b751bccf4dc4cc500de85a96a9331db9805596c46aa9f99d9555983954e6c1266d9f981ae37a9e4647f42b9a4bb5466f867f4012e582c9e7e + languageName: node + linkType: hard + +"makeerror@npm:1.0.12": + version: 1.0.12 + resolution: "makeerror@npm:1.0.12" + dependencies: + tmpl: "npm:1.0.5" + checksum: 10c0/b0e6e599780ce6bab49cc413eba822f7d1f0dfebd1c103eaa3785c59e43e22c59018323cf9e1708f0ef5329e94a745d163fcbb6bff8e4c6742f9be9e86f3500c + languageName: node + linkType: hard + +"media-typer@npm:0.3.0": + version: 0.3.0 + resolution: "media-typer@npm:0.3.0" + checksum: 10c0/d160f31246907e79fed398470285f21bafb45a62869dc469b1c8877f3f064f5eabc4bcc122f9479b8b605bc5c76187d7871cf84c4ee3ecd3e487da1993279928 + languageName: node + linkType: hard + +"merge-descriptors@npm:1.0.3": + version: 1.0.3 + resolution: "merge-descriptors@npm:1.0.3" + checksum: 10c0/866b7094afd9293b5ea5dcd82d71f80e51514bed33b4c4e9f516795dc366612a4cbb4dc94356e943a8a6914889a914530badff27f397191b9b75cda20b6bae93 + languageName: node + linkType: hard + +"merge-stream@npm:^2.0.0": + version: 2.0.0 + resolution: "merge-stream@npm:2.0.0" + checksum: 10c0/867fdbb30a6d58b011449b8885601ec1690c3e41c759ecd5a9d609094f7aed0096c37823ff4a7190ef0b8f22cc86beb7049196ff68c016e3b3c671d0dac91ce5 + languageName: node + linkType: hard + +"merge2@npm:^1.3.0, merge2@npm:^1.4.1": + version: 1.4.1 + resolution: "merge2@npm:1.4.1" + checksum: 10c0/254a8a4605b58f450308fc474c82ac9a094848081bf4c06778200207820e5193726dc563a0d2c16468810516a5c97d9d3ea0ca6585d23c58ccfff2403e8dbbeb + languageName: node + linkType: hard + +"methods@npm:^1.1.2, methods@npm:~1.1.2": + version: 1.1.2 + resolution: "methods@npm:1.1.2" + checksum: 10c0/bdf7cc72ff0a33e3eede03708c08983c4d7a173f91348b4b1e4f47d4cdbf734433ad971e7d1e8c77247d9e5cd8adb81ea4c67b0a2db526b758b2233d7814b8b2 + languageName: node + linkType: hard + +"micromatch@npm:^4.0.4": + version: 4.0.8 + resolution: "micromatch@npm:4.0.8" + dependencies: + braces: "npm:^3.0.3" + picomatch: "npm:^2.3.1" + checksum: 10c0/166fa6eb926b9553f32ef81f5f531d27b4ce7da60e5baf8c021d043b27a388fb95e46a8038d5045877881e673f8134122b59624d5cecbd16eb50a42e7a6b5ca8 + languageName: node + linkType: hard + +"migrate@npm:^2.0.1": + version: 2.1.0 + resolution: "migrate@npm:2.1.0" + dependencies: + chalk: "npm:^4.1.2" + commander: "npm:^2.20.3" + dateformat: "npm:^4.6.3" + dotenv: "npm:^16.0.0" + inherits: "npm:^2.0.3" + minimatch: "npm:^9.0.1" + mkdirp: "npm:^3.0.1" + slug: "npm:^8.2.2" + bin: + migrate: bin/migrate + migrate-create: bin/migrate-create + migrate-down: bin/migrate-down + migrate-init: bin/migrate-init + migrate-list: bin/migrate-list + migrate-up: bin/migrate-up + checksum: 10c0/15a3ccd14e95f6c1eed87860860ec3195910e96fa23702867ad6ddbfa802a8c93ea94672192f1c64f868cf441449ac9aaaed629a89fb09d7e139c9502ae9b5f8 + languageName: node + linkType: hard + +"mime-db@npm:1.52.0": + version: 1.52.0 + resolution: "mime-db@npm:1.52.0" + checksum: 10c0/0557a01deebf45ac5f5777fe7740b2a5c309c6d62d40ceab4e23da9f821899ce7a900b7ac8157d4548ddbb7beffe9abc621250e6d182b0397ec7f10c7b91a5aa + languageName: node + linkType: hard + +"mime-types@npm:^2.1.12, mime-types@npm:~2.1.24, mime-types@npm:~2.1.34": + version: 2.1.35 + resolution: "mime-types@npm:2.1.35" + dependencies: + mime-db: "npm:1.52.0" + checksum: 10c0/82fb07ec56d8ff1fc999a84f2f217aa46cb6ed1033fefaabd5785b9a974ed225c90dc72fff460259e66b95b73648596dbcc50d51ed69cdf464af2d237d3149b2 + languageName: node + linkType: hard + +"mime@npm:1.6.0": + version: 1.6.0 + resolution: "mime@npm:1.6.0" + bin: + mime: cli.js + checksum: 10c0/b92cd0adc44888c7135a185bfd0dddc42c32606401c72896a842ae15da71eb88858f17669af41e498b463cd7eb998f7b48939a25b08374c7924a9c8a6f8a81b0 + languageName: node + linkType: hard + +"mime@npm:2.6.0": + version: 2.6.0 + resolution: "mime@npm:2.6.0" + bin: + mime: cli.js + checksum: 10c0/a7f2589900d9c16e3bdf7672d16a6274df903da958c1643c9c45771f0478f3846dcb1097f31eb9178452570271361e2149310931ec705c037210fc69639c8e6c + languageName: node + linkType: hard + +"mimic-fn@npm:^2.1.0": + version: 2.1.0 + resolution: "mimic-fn@npm:2.1.0" + checksum: 10c0/b26f5479d7ec6cc2bce275a08f146cf78f5e7b661b18114e2506dd91ec7ec47e7a25bf4360e5438094db0560bcc868079fb3b1fb3892b833c1ecbf63f80c95a4 + languageName: node + linkType: hard + +"mimic-response@npm:^3.1.0": + version: 3.1.0 + resolution: "mimic-response@npm:3.1.0" + checksum: 10c0/0d6f07ce6e03e9e4445bee655202153bdb8a98d67ee8dc965ac140900d7a2688343e6b4c9a72cfc9ef2f7944dfd76eef4ab2482eb7b293a68b84916bac735362 + languageName: node + linkType: hard + +"minimatch@npm:^3.0.4, minimatch@npm:^3.0.5, minimatch@npm:^3.1.1, minimatch@npm:^3.1.2": + version: 3.1.2 + resolution: "minimatch@npm:3.1.2" + dependencies: + brace-expansion: "npm:^1.1.7" + checksum: 10c0/0262810a8fc2e72cca45d6fd86bd349eee435eb95ac6aa45c9ea2180e7ee875ef44c32b55b5973ceabe95ea12682f6e3725cbb63d7a2d1da3ae1163c8b210311 + languageName: node + linkType: hard + +"minimatch@npm:^9.0.1, minimatch@npm:^9.0.4": + version: 9.0.4 + resolution: "minimatch@npm:9.0.4" + dependencies: + brace-expansion: "npm:^2.0.1" + checksum: 10c0/2c16f21f50e64922864e560ff97c587d15fd491f65d92a677a344e970fe62aafdbeafe648965fa96d33c061b4d0eabfe0213466203dd793367e7f28658cf6414 + languageName: node + linkType: hard + +"minimist@npm:^1.2.0, minimist@npm:^1.2.3": + version: 1.2.8 + resolution: "minimist@npm:1.2.8" + checksum: 10c0/19d3fcdca050087b84c2029841a093691a91259a47def2f18222f41e7645a0b7c44ef4b40e88a1e58a40c84d2ef0ee6047c55594d298146d0eb3f6b737c20ce6 + languageName: node + linkType: hard + +"minipass-collect@npm:^2.0.1": + version: 2.0.1 + resolution: "minipass-collect@npm:2.0.1" + dependencies: + minipass: "npm:^7.0.3" + checksum: 10c0/5167e73f62bb74cc5019594709c77e6a742051a647fe9499abf03c71dca75515b7959d67a764bdc4f8b361cf897fbf25e2d9869ee039203ed45240f48b9aa06e + languageName: node + linkType: hard + +"minipass-fetch@npm:^3.0.0": + version: 3.0.5 + resolution: "minipass-fetch@npm:3.0.5" + dependencies: + encoding: "npm:^0.1.13" + minipass: "npm:^7.0.3" + minipass-sized: "npm:^1.0.3" + minizlib: "npm:^2.1.2" + dependenciesMeta: + encoding: + optional: true + checksum: 10c0/9d702d57f556274286fdd97e406fc38a2f5c8d15e158b498d7393b1105974b21249289ec571fa2b51e038a4872bfc82710111cf75fae98c662f3d6f95e72152b + languageName: node + linkType: hard + +"minipass-flush@npm:^1.0.5": + version: 1.0.5 + resolution: "minipass-flush@npm:1.0.5" + dependencies: + minipass: "npm:^3.0.0" + checksum: 10c0/2a51b63feb799d2bb34669205eee7c0eaf9dce01883261a5b77410c9408aa447e478efd191b4de6fc1101e796ff5892f8443ef20d9544385819093dbb32d36bd + languageName: node + linkType: hard + +"minipass-pipeline@npm:^1.2.4": + version: 1.2.4 + resolution: "minipass-pipeline@npm:1.2.4" + dependencies: + minipass: "npm:^3.0.0" + checksum: 10c0/cbda57cea20b140b797505dc2cac71581a70b3247b84480c1fed5ca5ba46c25ecc25f68bfc9e6dcb1a6e9017dab5c7ada5eab73ad4f0a49d84e35093e0c643f2 + languageName: node + linkType: hard + +"minipass-sized@npm:^1.0.3": + version: 1.0.3 + resolution: "minipass-sized@npm:1.0.3" + dependencies: + minipass: "npm:^3.0.0" + checksum: 10c0/298f124753efdc745cfe0f2bdfdd81ba25b9f4e753ca4a2066eb17c821f25d48acea607dfc997633ee5bf7b6dfffb4eee4f2051eb168663f0b99fad2fa4829cb + languageName: node + linkType: hard + +"minipass@npm:^3.0.0": + version: 3.3.6 + resolution: "minipass@npm:3.3.6" + dependencies: + yallist: "npm:^4.0.0" + checksum: 10c0/a114746943afa1dbbca8249e706d1d38b85ed1298b530f5808ce51f8e9e941962e2a5ad2e00eae7dd21d8a4aae6586a66d4216d1a259385e9d0358f0c1eba16c + languageName: node + linkType: hard + +"minipass@npm:^5.0.0": + version: 5.0.0 + resolution: "minipass@npm:5.0.0" + checksum: 10c0/a91d8043f691796a8ac88df039da19933ef0f633e3d7f0d35dcd5373af49131cf2399bfc355f41515dc495e3990369c3858cd319e5c2722b4753c90bf3152462 + languageName: node + linkType: hard + +"minipass@npm:^5.0.0 || ^6.0.2 || ^7.0.0, minipass@npm:^7.0.2, minipass@npm:^7.0.3, minipass@npm:^7.1.2": + version: 7.1.2 + resolution: "minipass@npm:7.1.2" + checksum: 10c0/b0fd20bb9fb56e5fa9a8bfac539e8915ae07430a619e4b86ff71f5fc757ef3924b23b2c4230393af1eda647ed3d75739e4e0acb250a6b1eb277cf7f8fe449557 + languageName: node + linkType: hard + +"minizlib@npm:^2.1.1, minizlib@npm:^2.1.2": + version: 2.1.2 + resolution: "minizlib@npm:2.1.2" + dependencies: + minipass: "npm:^3.0.0" + yallist: "npm:^4.0.0" + checksum: 10c0/64fae024e1a7d0346a1102bb670085b17b7f95bf6cfdf5b128772ec8faf9ea211464ea4add406a3a6384a7d87a0cd1a96263692134323477b4fb43659a6cab78 + languageName: node + linkType: hard + +"mkdirp-classic@npm:^0.5.2, mkdirp-classic@npm:^0.5.3": + version: 0.5.3 + resolution: "mkdirp-classic@npm:0.5.3" + checksum: 10c0/95371d831d196960ddc3833cc6907e6b8f67ac5501a6582f47dfae5eb0f092e9f8ce88e0d83afcae95d6e2b61a01741ba03714eeafb6f7a6e9dcc158ac85b168 + languageName: node + linkType: hard + +"mkdirp@npm:^1.0.3, mkdirp@npm:^1.0.4": + version: 1.0.4 + resolution: "mkdirp@npm:1.0.4" + bin: + mkdirp: bin/cmd.js + checksum: 10c0/46ea0f3ffa8bc6a5bc0c7081ffc3907777f0ed6516888d40a518c5111f8366d97d2678911ad1a6882bf592fa9de6c784fea32e1687bb94e1f4944170af48a5cf + languageName: node + linkType: hard + +"mkdirp@npm:^3.0.1": + version: 3.0.1 + resolution: "mkdirp@npm:3.0.1" + bin: + mkdirp: dist/cjs/src/bin.js + checksum: 10c0/9f2b975e9246351f5e3a40dcfac99fcd0baa31fbfab615fe059fb11e51f10e4803c63de1f384c54d656e4db31d000e4767e9ef076a22e12a641357602e31d57d + languageName: node + linkType: hard + +"ms@npm:2.0.0": + version: 2.0.0 + resolution: "ms@npm:2.0.0" + checksum: 10c0/f8fda810b39fd7255bbdc451c46286e549794fcc700dc9cd1d25658bbc4dc2563a5de6fe7c60f798a16a60c6ceb53f033cb353f493f0cf63e5199b702943159d + languageName: node + linkType: hard + +"ms@npm:2.1.2": + version: 2.1.2 + resolution: "ms@npm:2.1.2" + checksum: 10c0/a437714e2f90dbf881b5191d35a6db792efbca5badf112f87b9e1c712aace4b4b9b742dd6537f3edf90fd6f684de897cec230abde57e87883766712ddda297cc + languageName: node + linkType: hard + +"ms@npm:2.1.3, ms@npm:^2.1.1": + version: 2.1.3 + resolution: "ms@npm:2.1.3" + checksum: 10c0/d924b57e7312b3b63ad21fc5b3dc0af5e78d61a1fc7cfb5457edaf26326bf62be5307cc87ffb6862ef1c2b33b0233cdb5d4f01c4c958cc0d660948b65a287a48 + languageName: node + linkType: hard + +"murmurhash@npm:^2.0.1": + version: 2.0.1 + resolution: "murmurhash@npm:2.0.1" + checksum: 10c0/f6c7cb12d6ebc9c1cfd232fe9406089e1ceb128d24245e852866ba28967271925d915140f77fef7c92ee29b13165f4537ce80a85c3d0550b1b5cdb9f8bcaa19f + languageName: node + linkType: hard + +"napi-build-utils@npm:^1.0.1": + version: 1.0.2 + resolution: "napi-build-utils@npm:1.0.2" + checksum: 10c0/37fd2cd0ff2ad20073ce78d83fd718a740d568b225924e753ae51cb69d68f330c80544d487e5e5bd18e28702ed2ca469c2424ad948becd1862c1b0209542b2e9 + languageName: node + linkType: hard + +"natural-compare-lite@npm:^1.4.0": + version: 1.4.0 + resolution: "natural-compare-lite@npm:1.4.0" + checksum: 10c0/f6cef26f5044515754802c0fc475d81426f3b90fe88c20fabe08771ce1f736ce46e0397c10acb569a4dd0acb84c7f1ee70676122f95d5bfdd747af3a6c6bbaa8 + languageName: node + linkType: hard + +"natural-compare@npm:^1.4.0": + version: 1.4.0 + resolution: "natural-compare@npm:1.4.0" + checksum: 10c0/f5f9a7974bfb28a91afafa254b197f0f22c684d4a1731763dda960d2c8e375b36c7d690e0d9dc8fba774c537af14a7e979129bca23d88d052fbeb9466955e447 + languageName: node + linkType: hard + +"negotiator@npm:0.6.3, negotiator@npm:^0.6.3": + version: 0.6.3 + resolution: "negotiator@npm:0.6.3" + checksum: 10c0/3ec9fd413e7bf071c937ae60d572bc67155262068ed522cf4b3be5edbe6ddf67d095ec03a3a14ebf8fc8e95f8e1d61be4869db0dbb0de696f6b837358bd43fc2 + languageName: node + linkType: hard + +"node-abi@npm:^3.3.0": + version: 3.65.0 + resolution: "node-abi@npm:3.65.0" + dependencies: + semver: "npm:^7.3.5" + checksum: 10c0/112672015d8f27d6be2f18d64569f28f5d6a15a94cc510da513c69c3e3ab5df6dac196ef13ff115a8fadb69b554974c47ef89b4f6350a2b02de2bca5c23db1e5 + languageName: node + linkType: hard + +"node-addon-api@npm:^5.0.0": + version: 5.1.0 + resolution: "node-addon-api@npm:5.1.0" + dependencies: + node-gyp: "npm:latest" + checksum: 10c0/0eb269786124ba6fad9df8007a149e03c199b3e5a3038125dfb3e747c2d5113d406a4e33f4de1ea600aa2339be1f137d55eba1a73ee34e5fff06c52a5c296d1d + languageName: node + linkType: hard + +"node-fetch@npm:^2.6.7": + version: 2.7.0 + resolution: "node-fetch@npm:2.7.0" + dependencies: + whatwg-url: "npm:^5.0.0" + peerDependencies: + encoding: ^0.1.0 + peerDependenciesMeta: + encoding: + optional: true + checksum: 10c0/b55786b6028208e6fbe594ccccc213cab67a72899c9234eb59dba51062a299ea853210fcf526998eaa2867b0963ad72338824450905679ff0fa304b8c5093ae8 + languageName: node + linkType: hard + +"node-gyp@npm:latest": + version: 10.1.0 + resolution: "node-gyp@npm:10.1.0" + dependencies: + env-paths: "npm:^2.2.0" + exponential-backoff: "npm:^3.1.1" + glob: "npm:^10.3.10" + graceful-fs: "npm:^4.2.6" + make-fetch-happen: "npm:^13.0.0" + nopt: "npm:^7.0.0" + proc-log: "npm:^3.0.0" + semver: "npm:^7.3.5" + tar: "npm:^6.1.2" + which: "npm:^4.0.0" + bin: + node-gyp: bin/node-gyp.js + checksum: 10c0/9cc821111ca244a01fb7f054db7523ab0a0cd837f665267eb962eb87695d71fb1e681f9e21464cc2fd7c05530dc4c81b810bca1a88f7d7186909b74477491a3c + languageName: node + linkType: hard + +"node-int64@npm:^0.4.0": + version: 0.4.0 + resolution: "node-int64@npm:0.4.0" + checksum: 10c0/a6a4d8369e2f2720e9c645255ffde909c0fbd41c92ea92a5607fc17055955daac99c1ff589d421eee12a0d24e99f7bfc2aabfeb1a4c14742f6c099a51863f31a + languageName: node + linkType: hard + +"node-releases@npm:^2.0.14": + version: 2.0.14 + resolution: "node-releases@npm:2.0.14" + checksum: 10c0/199fc93773ae70ec9969bc6d5ac5b2bbd6eb986ed1907d751f411fef3ede0e4bfdb45ceb43711f8078bea237b6036db8b1bf208f6ff2b70c7d615afd157f3ab9 + languageName: node + linkType: hard + +"nopt@npm:^5.0.0": + version: 5.0.0 + resolution: "nopt@npm:5.0.0" + dependencies: + abbrev: "npm:1" + bin: + nopt: bin/nopt.js + checksum: 10c0/fc5c4f07155cb455bf5fc3dd149fac421c1a40fd83c6bfe83aa82b52f02c17c5e88301321318adaa27611c8a6811423d51d29deaceab5fa158b585a61a551061 + languageName: node + linkType: hard + +"nopt@npm:^7.0.0": + version: 7.2.1 + resolution: "nopt@npm:7.2.1" + dependencies: + abbrev: "npm:^2.0.0" + bin: + nopt: bin/nopt.js + checksum: 10c0/a069c7c736767121242037a22a788863accfa932ab285a1eb569eb8cd534b09d17206f68c37f096ae785647435e0c5a5a0a67b42ec743e481a455e5ae6a6df81 + languageName: node + linkType: hard + +"nordigen-node@npm:^1.4.0": + version: 1.4.0 + resolution: "nordigen-node@npm:1.4.0" + dependencies: + axios: "npm:^1.2.1" + dotenv: "npm:^10.0.0" + checksum: 10c0/a04ec90480e4e65b2169d909ac9ea3044f764d59283162420d287a6b229808754dc78c758637724d63c87aa77f2237bc47543f521b0b4057ed3980d6db137e1a + languageName: node + linkType: hard + +"normalize-path@npm:^3.0.0": + version: 3.0.0 + resolution: "normalize-path@npm:3.0.0" + checksum: 10c0/e008c8142bcc335b5e38cf0d63cfd39d6cf2d97480af9abdbe9a439221fd4d749763bab492a8ee708ce7a194bb00c9da6d0a115018672310850489137b3da046 + languageName: node + linkType: hard + +"npm-run-path@npm:^4.0.1": + version: 4.0.1 + resolution: "npm-run-path@npm:4.0.1" + dependencies: + path-key: "npm:^3.0.0" + checksum: 10c0/6f9353a95288f8455cf64cbeb707b28826a7f29690244c1e4bb61ec573256e021b6ad6651b394eb1ccfd00d6ec50147253aba2c5fe58a57ceb111fad62c519ac + languageName: node + linkType: hard + +"npmlog@npm:^5.0.1": + version: 5.0.1 + resolution: "npmlog@npm:5.0.1" + dependencies: + are-we-there-yet: "npm:^2.0.0" + console-control-strings: "npm:^1.1.0" + gauge: "npm:^3.0.0" + set-blocking: "npm:^2.0.0" + checksum: 10c0/489ba519031013001135c463406f55491a17fc7da295c18a04937fe3a4d523fd65e88dd418a28b967ab743d913fdeba1e29838ce0ad8c75557057c481f7d49fa + languageName: node + linkType: hard + +"object-assign@npm:^4, object-assign@npm:^4.1.1": + version: 4.1.1 + resolution: "object-assign@npm:4.1.1" + checksum: 10c0/1f4df9945120325d041ccf7b86f31e8bcc14e73d29171e37a7903050e96b81323784ec59f93f102ec635bcf6fa8034ba3ea0a8c7e69fa202b87ae3b6cec5a414 + languageName: node + linkType: hard + +"object-hash@npm:^2.2.0": + version: 2.2.0 + resolution: "object-hash@npm:2.2.0" + checksum: 10c0/1527de843926c5442ed61f8bdddfc7dc181b6497f725b0e89fcf50a55d9c803088763ed447cac85a5aa65345f1e99c2469ba679a54349ef3c4c0aeaa396a3eb9 + languageName: node + linkType: hard + +"object-inspect@npm:^1.13.1": + version: 1.13.1 + resolution: "object-inspect@npm:1.13.1" + checksum: 10c0/fad603f408e345c82e946abdf4bfd774260a5ed3e5997a0b057c44153ac32c7271ff19e3a5ae39c858da683ba045ccac2f65245c12763ce4e8594f818f4a648d + languageName: node + linkType: hard + +"oidc-token-hash@npm:^5.0.3": + version: 5.0.3 + resolution: "oidc-token-hash@npm:5.0.3" + checksum: 10c0/d0dc0551406f09577874155cc83cf69c39e4b826293d50bb6c37936698aeca17d4bcee356ab910c859e53e83f2728a2acbd041020165191353b29de51fbca615 + languageName: node + linkType: hard + +"on-finished@npm:2.4.1": + version: 2.4.1 + resolution: "on-finished@npm:2.4.1" + dependencies: + ee-first: "npm:1.1.1" + checksum: 10c0/46fb11b9063782f2d9968863d9cbba33d77aa13c17f895f56129c274318b86500b22af3a160fe9995aa41317efcd22941b6eba747f718ced08d9a73afdb087b4 + languageName: node + linkType: hard + +"on-headers@npm:1.0.1": + version: 1.0.1 + resolution: "on-headers@npm:1.0.1" + checksum: 10c0/060229267db33d0f56b03f59f4a1edf50130bf51498599b1f4b1a1bcf364bd8a9e05c3a984f0d0d384e2cdcdacdf176379a17e67bf5041e84c8c5f4a1938ec82 + languageName: node + linkType: hard + +"once@npm:^1.3.0, once@npm:^1.3.1, once@npm:^1.4.0": + version: 1.4.0 + resolution: "once@npm:1.4.0" + dependencies: + wrappy: "npm:1" + checksum: 10c0/5d48aca287dfefabd756621c5dfce5c91a549a93e9fdb7b8246bc4c4790aa2ec17b34a260530474635147aeb631a2dcc8b32c613df0675f96041cbb8244517d0 + languageName: node + linkType: hard + +"one-time@npm:^1.0.0": + version: 1.0.0 + resolution: "one-time@npm:1.0.0" + dependencies: + fn.name: "npm:1.x.x" + checksum: 10c0/6e4887b331edbb954f4e915831cbec0a7b9956c36f4feb5f6de98c448ac02ff881fd8d9b55a6b1b55030af184c6b648f340a76eb211812f4ad8c9b4b8692fdaa + languageName: node + linkType: hard + +"onetime@npm:^5.1.2": + version: 5.1.2 + resolution: "onetime@npm:5.1.2" + dependencies: + mimic-fn: "npm:^2.1.0" + checksum: 10c0/ffcef6fbb2692c3c40749f31ea2e22677a876daea92959b8a80b521d95cca7a668c884d8b2045d1d8ee7d56796aa405c405462af112a1477594cc63531baeb8f + languageName: node + linkType: hard + +"openid-client@npm:^5.4.2": + version: 5.6.5 + resolution: "openid-client@npm:5.6.5" + dependencies: + jose: "npm:^4.15.5" + lru-cache: "npm:^6.0.0" + object-hash: "npm:^2.2.0" + oidc-token-hash: "npm:^5.0.3" + checksum: 10c0/4308dcd37a9ffb1efc2ede0bc556ae42ccc2569e71baa52a03ddfa44407bf403d4534286f6f571381c5eaa1845c609ed699a5eb0d350acfb8c3bacb72c2a6890 + languageName: node + linkType: hard + +"optionator@npm:^0.9.3": + version: 0.9.4 + resolution: "optionator@npm:0.9.4" + dependencies: + deep-is: "npm:^0.1.3" + fast-levenshtein: "npm:^2.0.6" + levn: "npm:^0.4.1" + prelude-ls: "npm:^1.2.1" + type-check: "npm:^0.4.0" + word-wrap: "npm:^1.2.5" + checksum: 10c0/4afb687a059ee65b61df74dfe87d8d6815cd6883cb8b3d5883a910df72d0f5d029821f37025e4bccf4048873dbdb09acc6d303d27b8f76b1a80dd5a7d5334675 + languageName: node + linkType: hard + +"p-limit@npm:^2.2.0": + version: 2.3.0 + resolution: "p-limit@npm:2.3.0" + dependencies: + p-try: "npm:^2.0.0" + checksum: 10c0/8da01ac53efe6a627080fafc127c873da40c18d87b3f5d5492d465bb85ec7207e153948df6b9cbaeb130be70152f874229b8242ee2be84c0794082510af97f12 + languageName: node + linkType: hard + +"p-limit@npm:^3.0.2, p-limit@npm:^3.1.0": + version: 3.1.0 + resolution: "p-limit@npm:3.1.0" + dependencies: + yocto-queue: "npm:^0.1.0" + checksum: 10c0/9db675949dbdc9c3763c89e748d0ef8bdad0afbb24d49ceaf4c46c02c77d30db4e0652ed36d0a0a7a95154335fab810d95c86153105bb73b3a90448e2bb14e1a + languageName: node + linkType: hard + +"p-locate@npm:^4.1.0": + version: 4.1.0 + resolution: "p-locate@npm:4.1.0" + dependencies: + p-limit: "npm:^2.2.0" + checksum: 10c0/1b476ad69ad7f6059744f343b26d51ce091508935c1dbb80c4e0a2f397ffce0ca3a1f9f5cd3c7ce19d7929a09719d5c65fe70d8ee289c3f267cd36f2881813e9 + languageName: node + linkType: hard + +"p-locate@npm:^5.0.0": + version: 5.0.0 + resolution: "p-locate@npm:5.0.0" + dependencies: + p-limit: "npm:^3.0.2" + checksum: 10c0/2290d627ab7903b8b70d11d384fee714b797f6040d9278932754a6860845c4d3190603a0772a663c8cb5a7b21d1b16acb3a6487ebcafa9773094edc3dfe6009a + languageName: node + linkType: hard + +"p-map@npm:^4.0.0": + version: 4.0.0 + resolution: "p-map@npm:4.0.0" + dependencies: + aggregate-error: "npm:^3.0.0" + checksum: 10c0/592c05bd6262c466ce269ff172bb8de7c6975afca9b50c975135b974e9bdaafbfe80e61aaaf5be6d1200ba08b30ead04b88cfa7e25ff1e3b93ab28c9f62a2c75 + languageName: node + linkType: hard + +"p-try@npm:^2.0.0": + version: 2.2.0 + resolution: "p-try@npm:2.2.0" + checksum: 10c0/c36c19907734c904b16994e6535b02c36c2224d433e01a2f1ab777237f4d86e6289fd5fd464850491e940379d4606ed850c03e0f9ab600b0ebddb511312e177f + languageName: node + linkType: hard + +"parent-module@npm:^1.0.0": + version: 1.0.1 + resolution: "parent-module@npm:1.0.1" + dependencies: + callsites: "npm:^3.0.0" + checksum: 10c0/c63d6e80000d4babd11978e0d3fee386ca7752a02b035fd2435960ffaa7219dc42146f07069fb65e6e8bf1caef89daf9af7535a39bddf354d78bf50d8294f556 + languageName: node + linkType: hard + +"parse-json@npm:^5.2.0": + version: 5.2.0 + resolution: "parse-json@npm:5.2.0" + dependencies: + "@babel/code-frame": "npm:^7.0.0" + error-ex: "npm:^1.3.1" + json-parse-even-better-errors: "npm:^2.3.0" + lines-and-columns: "npm:^1.1.6" + checksum: 10c0/77947f2253005be7a12d858aedbafa09c9ae39eb4863adf330f7b416ca4f4a08132e453e08de2db46459256fb66afaac5ee758b44fe6541b7cdaf9d252e59585 + languageName: node + linkType: hard + +"parseurl@npm:~1.3.3": + version: 1.3.3 + resolution: "parseurl@npm:1.3.3" + checksum: 10c0/90dd4760d6f6174adb9f20cf0965ae12e23879b5f5464f38e92fce8073354341e4b3b76fa3d878351efe7d01e617121955284cfd002ab087fba1a0726ec0b4f5 + languageName: node + linkType: hard + +"path-exists@npm:^4.0.0": + version: 4.0.0 + resolution: "path-exists@npm:4.0.0" + checksum: 10c0/8c0bd3f5238188197dc78dced15207a4716c51cc4e3624c44fc97acf69558f5ebb9a2afff486fe1b4ee148e0c133e96c5e11a9aa5c48a3006e3467da070e5e1b + languageName: node + linkType: hard + +"path-is-absolute@npm:^1.0.0": + version: 1.0.1 + resolution: "path-is-absolute@npm:1.0.1" + checksum: 10c0/127da03c82172a2a50099cddbf02510c1791fc2cc5f7713ddb613a56838db1e8168b121a920079d052e0936c23005562059756d653b7c544c53185efe53be078 + languageName: node + linkType: hard + +"path-key@npm:^3.0.0, path-key@npm:^3.1.0": + version: 3.1.1 + resolution: "path-key@npm:3.1.1" + checksum: 10c0/748c43efd5a569c039d7a00a03b58eecd1d75f3999f5a28303d75f521288df4823bc057d8784eb72358b2895a05f29a070bc9f1f17d28226cc4e62494cc58c4c + languageName: node + linkType: hard + +"path-parse@npm:^1.0.7": + version: 1.0.7 + resolution: "path-parse@npm:1.0.7" + checksum: 10c0/11ce261f9d294cc7a58d6a574b7f1b935842355ec66fba3c3fd79e0f036462eaf07d0aa95bb74ff432f9afef97ce1926c720988c6a7451d8a584930ae7de86e1 + languageName: node + linkType: hard + +"path-scurry@npm:^1.11.1": + version: 1.11.1 + resolution: "path-scurry@npm:1.11.1" + dependencies: + lru-cache: "npm:^10.2.0" + minipass: "npm:^5.0.0 || ^6.0.2 || ^7.0.0" + checksum: 10c0/32a13711a2a505616ae1cc1b5076801e453e7aae6ac40ab55b388bb91b9d0547a52f5aaceff710ea400205f18691120d4431e520afbe4266b836fadede15872d + languageName: node + linkType: hard + +"path-to-regexp@npm:0.1.10": + version: 0.1.10 + resolution: "path-to-regexp@npm:0.1.10" + checksum: 10c0/34196775b9113ca6df88e94c8d83ba82c0e1a2063dd33bfe2803a980da8d49b91db8104f49d5191b44ea780d46b8670ce2b7f4a5e349b0c48c6779b653f1afe4 + languageName: node + linkType: hard + +"path-type@npm:^4.0.0": + version: 4.0.0 + resolution: "path-type@npm:4.0.0" + checksum: 10c0/666f6973f332f27581371efaf303fd6c272cc43c2057b37aa99e3643158c7e4b2626549555d88626e99ea9e046f82f32e41bbde5f1508547e9a11b149b52387c + languageName: node + linkType: hard + +"picocolors@npm:^1.0.0, picocolors@npm:^1.0.1": + version: 1.0.1 + resolution: "picocolors@npm:1.0.1" + checksum: 10c0/c63cdad2bf812ef0d66c8db29583802355d4ca67b9285d846f390cc15c2f6ccb94e8cb7eb6a6e97fc5990a6d3ad4ae42d86c84d3146e667c739a4234ed50d400 + languageName: node + linkType: hard + +"picomatch@npm:^2.0.4, picomatch@npm:^2.2.3, picomatch@npm:^2.3.1": + version: 2.3.1 + resolution: "picomatch@npm:2.3.1" + checksum: 10c0/26c02b8d06f03206fc2ab8d16f19960f2ff9e81a658f831ecb656d8f17d9edc799e8364b1f4a7873e89d9702dff96204be0fa26fe4181f6843f040f819dac4be + languageName: node + linkType: hard + +"pirates@npm:^4.0.4": + version: 4.0.6 + resolution: "pirates@npm:4.0.6" + checksum: 10c0/00d5fa51f8dded94d7429700fb91a0c1ead00ae2c7fd27089f0c5b63e6eca36197fe46384631872690a66f390c5e27198e99006ab77ae472692ab9c2ca903f36 + languageName: node + linkType: hard + +"pkg-dir@npm:^4.2.0": + version: 4.2.0 + resolution: "pkg-dir@npm:4.2.0" + dependencies: + find-up: "npm:^4.0.0" + checksum: 10c0/c56bda7769e04907a88423feb320babaed0711af8c436ce3e56763ab1021ba107c7b0cafb11cde7529f669cfc22bffcaebffb573645cbd63842ea9fb17cd7728 + languageName: node + linkType: hard + +"prebuild-install@npm:^7.1.1": + version: 7.1.2 + resolution: "prebuild-install@npm:7.1.2" + dependencies: + detect-libc: "npm:^2.0.0" + expand-template: "npm:^2.0.3" + github-from-package: "npm:0.0.0" + minimist: "npm:^1.2.3" + mkdirp-classic: "npm:^0.5.3" + napi-build-utils: "npm:^1.0.1" + node-abi: "npm:^3.3.0" + pump: "npm:^3.0.0" + rc: "npm:^1.2.7" + simple-get: "npm:^4.0.0" + tar-fs: "npm:^2.0.0" + tunnel-agent: "npm:^0.6.0" + bin: + prebuild-install: bin.js + checksum: 10c0/e64868ba9ef2068fd7264f5b03e5298a901e02a450acdb1f56258d88c09dea601eefdb3d1dfdff8513fdd230a92961712be0676192626a3b4d01ba154d48bdd3 + languageName: node + linkType: hard + +"prelude-ls@npm:^1.2.1": + version: 1.2.1 + resolution: "prelude-ls@npm:1.2.1" + checksum: 10c0/b00d617431e7886c520a6f498a2e14c75ec58f6d93ba48c3b639cf241b54232d90daa05d83a9e9b9fef6baa63cb7e1e4602c2372fea5bc169668401eb127d0cd + languageName: node + linkType: hard + +"prettier-linter-helpers@npm:^1.0.0": + version: 1.0.0 + resolution: "prettier-linter-helpers@npm:1.0.0" + dependencies: + fast-diff: "npm:^1.1.2" + checksum: 10c0/81e0027d731b7b3697ccd2129470ed9913ecb111e4ec175a12f0fcfab0096516373bf0af2fef132af50cafb0a905b74ff57996d615f59512bb9ac7378fcc64ab + languageName: node + linkType: hard + +"prettier@npm:^2.8.3": + version: 2.8.8 + resolution: "prettier@npm:2.8.8" + bin: + prettier: bin-prettier.js + checksum: 10c0/463ea8f9a0946cd5b828d8cf27bd8b567345cf02f56562d5ecde198b91f47a76b7ac9eae0facd247ace70e927143af6135e8cf411986b8cb8478784a4d6d724a + languageName: node + linkType: hard + +"pretty-format@npm:^29.0.0, pretty-format@npm:^29.7.0": + version: 29.7.0 + resolution: "pretty-format@npm:29.7.0" + dependencies: + "@jest/schemas": "npm:^29.6.3" + ansi-styles: "npm:^5.0.0" + react-is: "npm:^18.0.0" + checksum: 10c0/edc5ff89f51916f036c62ed433506b55446ff739358de77207e63e88a28ca2894caac6e73dcb68166a606e51c8087d32d400473e6a9fdd2dbe743f46c9c0276f + languageName: node + linkType: hard + +"proc-log@npm:^3.0.0": + version: 3.0.0 + resolution: "proc-log@npm:3.0.0" + checksum: 10c0/f66430e4ff947dbb996058f6fd22de2c66612ae1a89b097744e17fb18a4e8e7a86db99eda52ccf15e53f00b63f4ec0b0911581ff2aac0355b625c8eac509b0dc + languageName: node + linkType: hard + +"proc-log@npm:^4.2.0": + version: 4.2.0 + resolution: "proc-log@npm:4.2.0" + checksum: 10c0/17db4757c2a5c44c1e545170e6c70a26f7de58feb985091fb1763f5081cab3d01b181fb2dd240c9f4a4255a1d9227d163d5771b7e69c9e49a561692db865efb9 + languageName: node + linkType: hard + +"promise-retry@npm:^2.0.1": + version: 2.0.1 + resolution: "promise-retry@npm:2.0.1" + dependencies: + err-code: "npm:^2.0.2" + retry: "npm:^0.12.0" + checksum: 10c0/9c7045a1a2928094b5b9b15336dcd2a7b1c052f674550df63cc3f36cd44028e5080448175b6f6ca32b642de81150f5e7b1a98b728f15cb069f2dd60ac2616b96 + languageName: node + linkType: hard + +"prompts@npm:^2.0.1": + version: 2.4.2 + resolution: "prompts@npm:2.4.2" + dependencies: + kleur: "npm:^3.0.3" + sisteransi: "npm:^1.0.5" + checksum: 10c0/16f1ac2977b19fe2cf53f8411cc98db7a3c8b115c479b2ca5c82b5527cd937aa405fa04f9a5960abeb9daef53191b53b4d13e35c1f5d50e8718c76917c5f1ea4 + languageName: node + linkType: hard + +"properties-reader@npm:^2.2.0": + version: 2.3.0 + resolution: "properties-reader@npm:2.3.0" + dependencies: + mkdirp: "npm:^1.0.4" + checksum: 10c0/f665057e3a9076c643ba1198afcc71703eda227a59913252f7ff9467ece8d29c0cf8bf14bf1abcaef71570840c32a4e257e6c39b7550451bbff1a777efcf5667 + languageName: node + linkType: hard + +"proxy-addr@npm:~2.0.7": + version: 2.0.7 + resolution: "proxy-addr@npm:2.0.7" + dependencies: + forwarded: "npm:0.2.0" + ipaddr.js: "npm:1.9.1" + checksum: 10c0/c3eed999781a35f7fd935f398b6d8920b6fb00bbc14287bc6de78128ccc1a02c89b95b56742bf7cf0362cc333c61d138532049c7dedc7a328ef13343eff81210 + languageName: node + linkType: hard + +"proxy-from-env@npm:^1.1.0": + version: 1.1.0 + resolution: "proxy-from-env@npm:1.1.0" + checksum: 10c0/fe7dd8b1bdbbbea18d1459107729c3e4a2243ca870d26d34c2c1bcd3e4425b7bcc5112362df2d93cc7fb9746f6142b5e272fd1cc5c86ddf8580175186f6ad42b + languageName: node + linkType: hard + +"pump@npm:^3.0.0": + version: 3.0.0 + resolution: "pump@npm:3.0.0" + dependencies: + end-of-stream: "npm:^1.1.0" + once: "npm:^1.3.1" + checksum: 10c0/bbdeda4f747cdf47db97428f3a135728669e56a0ae5f354a9ac5b74556556f5446a46f720a8f14ca2ece5be9b4d5d23c346db02b555f46739934cc6c093a5478 + languageName: node + linkType: hard + +"punycode@npm:^2.1.0": + version: 2.3.1 + resolution: "punycode@npm:2.3.1" + checksum: 10c0/14f76a8206bc3464f794fb2e3d3cc665ae416c01893ad7a02b23766eb07159144ee612ad67af5e84fa4479ccfe67678c4feb126b0485651b302babf66f04f9e9 + languageName: node + linkType: hard + +"pure-rand@npm:^6.0.0": + version: 6.1.0 + resolution: "pure-rand@npm:6.1.0" + checksum: 10c0/1abe217897bf74dcb3a0c9aba3555fe975023147b48db540aa2faf507aee91c03bf54f6aef0eb2bf59cc259a16d06b28eca37f0dc426d94f4692aeff02fb0e65 + languageName: node + linkType: hard + +"qs@npm:6.11.0": + version: 6.11.0 + resolution: "qs@npm:6.11.0" + dependencies: + side-channel: "npm:^1.0.4" + checksum: 10c0/4e4875e4d7c7c31c233d07a448e7e4650f456178b9dd3766b7cfa13158fdb24ecb8c4f059fa91e820dc6ab9f2d243721d071c9c0378892dcdad86e9e9a27c68f + languageName: node + linkType: hard + +"qs@npm:6.13.0": + version: 6.13.0 + resolution: "qs@npm:6.13.0" + dependencies: + side-channel: "npm:^1.0.6" + checksum: 10c0/62372cdeec24dc83a9fb240b7533c0fdcf0c5f7e0b83343edd7310f0ab4c8205a5e7c56406531f2e47e1b4878a3821d652be4192c841de5b032ca83619d8f860 + languageName: node + linkType: hard + +"qs@npm:^6.11.0": + version: 6.12.1 + resolution: "qs@npm:6.12.1" + dependencies: + side-channel: "npm:^1.0.6" + checksum: 10c0/439e6d7c6583e7c69f2cab2c39c55b97db7ce576e4c7c469082b938b7fc8746e8d547baacb69b4cd2b6666484776c3f4840ad7163a4c5326300b0afa0acdd84b + languageName: node + linkType: hard + +"queue-microtask@npm:^1.2.2": + version: 1.2.3 + resolution: "queue-microtask@npm:1.2.3" + checksum: 10c0/900a93d3cdae3acd7d16f642c29a642aea32c2026446151f0778c62ac089d4b8e6c986811076e1ae180a694cedf077d453a11b58ff0a865629a4f82ab558e102 + languageName: node + linkType: hard + +"range-parser@npm:~1.2.1": + version: 1.2.1 + resolution: "range-parser@npm:1.2.1" + checksum: 10c0/96c032ac2475c8027b7a4e9fe22dc0dfe0f6d90b85e496e0f016fbdb99d6d066de0112e680805075bd989905e2123b3b3d002765149294dce0c1f7f01fcc2ea0 + languageName: node + linkType: hard + +"raw-body@npm:2.5.2": + version: 2.5.2 + resolution: "raw-body@npm:2.5.2" + dependencies: + bytes: "npm:3.1.2" + http-errors: "npm:2.0.0" + iconv-lite: "npm:0.4.24" + unpipe: "npm:1.0.0" + checksum: 10c0/b201c4b66049369a60e766318caff5cb3cc5a900efd89bdac431463822d976ad0670912c931fdbdcf5543207daf6f6833bca57aa116e1661d2ea91e12ca692c4 + languageName: node + linkType: hard + +"rc@npm:^1.2.7": + version: 1.2.8 + resolution: "rc@npm:1.2.8" + dependencies: + deep-extend: "npm:^0.6.0" + ini: "npm:~1.3.0" + minimist: "npm:^1.2.0" + strip-json-comments: "npm:~2.0.1" + bin: + rc: ./cli.js + checksum: 10c0/24a07653150f0d9ac7168e52943cc3cb4b7a22c0e43c7dff3219977c2fdca5a2760a304a029c20811a0e79d351f57d46c9bde216193a0f73978496afc2b85b15 + languageName: node + linkType: hard + +"react-is@npm:^18.0.0": + version: 18.3.1 + resolution: "react-is@npm:18.3.1" + checksum: 10c0/f2f1e60010c683479e74c63f96b09fb41603527cd131a9959e2aee1e5a8b0caf270b365e5ca77d4a6b18aae659b60a86150bb3979073528877029b35aecd2072 + languageName: node + linkType: hard + +"readable-stream@npm:^3.1.1, readable-stream@npm:^3.4.0, readable-stream@npm:^3.6.0, readable-stream@npm:^3.6.2": + version: 3.6.2 + resolution: "readable-stream@npm:3.6.2" + dependencies: + inherits: "npm:^2.0.3" + string_decoder: "npm:^1.1.1" + util-deprecate: "npm:^1.0.1" + checksum: 10c0/e37be5c79c376fdd088a45fa31ea2e423e5d48854be7a22a58869b4e84d25047b193f6acb54f1012331e1bcd667ffb569c01b99d36b0bd59658fb33f513511b7 + languageName: node + linkType: hard + +"regenerator-runtime@npm:^0.14.0": + version: 0.14.1 + resolution: "regenerator-runtime@npm:0.14.1" + checksum: 10c0/1b16eb2c4bceb1665c89de70dcb64126a22bc8eb958feef3cd68fe11ac6d2a4899b5cd1b80b0774c7c03591dc57d16631a7f69d2daa2ec98100e2f29f7ec4cc4 + languageName: node + linkType: hard + +"require-directory@npm:^2.1.1": + version: 2.1.1 + resolution: "require-directory@npm:2.1.1" + checksum: 10c0/83aa76a7bc1531f68d92c75a2ca2f54f1b01463cb566cf3fbc787d0de8be30c9dbc211d1d46be3497dac5785fe296f2dd11d531945ac29730643357978966e99 + languageName: node + linkType: hard + +"resolve-cwd@npm:^3.0.0": + version: 3.0.0 + resolution: "resolve-cwd@npm:3.0.0" + dependencies: + resolve-from: "npm:^5.0.0" + checksum: 10c0/e608a3ebd15356264653c32d7ecbc8fd702f94c6703ea4ac2fb81d9c359180cba0ae2e6b71faa446631ed6145454d5a56b227efc33a2d40638ac13f8beb20ee4 + languageName: node + linkType: hard + +"resolve-from@npm:^4.0.0": + version: 4.0.0 + resolution: "resolve-from@npm:4.0.0" + checksum: 10c0/8408eec31a3112ef96e3746c37be7d64020cda07c03a920f5024e77290a218ea758b26ca9529fd7b1ad283947f34b2291c1c0f6aa0ed34acfdda9c6014c8d190 + languageName: node + linkType: hard + +"resolve-from@npm:^5.0.0": + version: 5.0.0 + resolution: "resolve-from@npm:5.0.0" + checksum: 10c0/b21cb7f1fb746de8107b9febab60095187781137fd803e6a59a76d421444b1531b641bba5857f5dc011974d8a5c635d61cec49e6bd3b7fc20e01f0fafc4efbf2 + languageName: node + linkType: hard + +"resolve.exports@npm:^2.0.0": + version: 2.0.2 + resolution: "resolve.exports@npm:2.0.2" + checksum: 10c0/cc4cffdc25447cf34730f388dca5021156ba9302a3bad3d7f168e790dc74b2827dff603f1bc6ad3d299bac269828dca96dd77e036dc9fba6a2a1807c47ab5c98 + languageName: node + linkType: hard + +"resolve@npm:^1.20.0": + version: 1.22.8 + resolution: "resolve@npm:1.22.8" + dependencies: + is-core-module: "npm:^2.13.0" + path-parse: "npm:^1.0.7" + supports-preserve-symlinks-flag: "npm:^1.0.0" + bin: + resolve: bin/resolve + checksum: 10c0/07e179f4375e1fd072cfb72ad66d78547f86e6196c4014b31cb0b8bb1db5f7ca871f922d08da0fbc05b94e9fd42206f819648fa3b5b873ebbc8e1dc68fec433a + languageName: node + linkType: hard + +"resolve@patch:resolve@npm%3A^1.20.0#optional!builtin<compat/resolve>": + version: 1.22.8 + resolution: "resolve@patch:resolve@npm%3A1.22.8#optional!builtin<compat/resolve>::version=1.22.8&hash=c3c19d" + dependencies: + is-core-module: "npm:^2.13.0" + path-parse: "npm:^1.0.7" + supports-preserve-symlinks-flag: "npm:^1.0.0" + bin: + resolve: bin/resolve + checksum: 10c0/0446f024439cd2e50c6c8fa8ba77eaa8370b4180f401a96abf3d1ebc770ac51c1955e12764cde449fde3fff480a61f84388e3505ecdbab778f4bef5f8212c729 + languageName: node + linkType: hard + +"retry@npm:^0.12.0": + version: 0.12.0 + resolution: "retry@npm:0.12.0" + checksum: 10c0/59933e8501727ba13ad73ef4a04d5280b3717fd650408460c987392efe9d7be2040778ed8ebe933c5cbd63da3dcc37919c141ef8af0a54a6e4fca5a2af177bfe + languageName: node + linkType: hard + +"reusify@npm:^1.0.4": + version: 1.0.4 + resolution: "reusify@npm:1.0.4" + checksum: 10c0/c19ef26e4e188f408922c46f7ff480d38e8dfc55d448310dfb518736b23ed2c4f547fb64a6ed5bdba92cd7e7ddc889d36ff78f794816d5e71498d645ef476107 + languageName: node + linkType: hard + +"rimraf@npm:^3.0.2": + version: 3.0.2 + resolution: "rimraf@npm:3.0.2" + dependencies: + glob: "npm:^7.1.3" + bin: + rimraf: bin.js + checksum: 10c0/9cb7757acb489bd83757ba1a274ab545eafd75598a9d817e0c3f8b164238dd90eba50d6b848bd4dcc5f3040912e882dc7ba71653e35af660d77b25c381d402e8 + languageName: node + linkType: hard + +"run-parallel@npm:^1.1.9": + version: 1.2.0 + resolution: "run-parallel@npm:1.2.0" + dependencies: + queue-microtask: "npm:^1.2.2" + checksum: 10c0/200b5ab25b5b8b7113f9901bfe3afc347e19bb7475b267d55ad0eb86a62a46d77510cb0f232507c9e5d497ebda569a08a9867d0d14f57a82ad5564d991588b39 + languageName: node + linkType: hard + +"safe-buffer@npm:5.2.1, safe-buffer@npm:^5.0.1, safe-buffer@npm:~5.2.0": + version: 5.2.1 + resolution: "safe-buffer@npm:5.2.1" + checksum: 10c0/6501914237c0a86e9675d4e51d89ca3c21ffd6a31642efeba25ad65720bce6921c9e7e974e5be91a786b25aa058b5303285d3c15dbabf983a919f5f630d349f3 + languageName: node + linkType: hard + +"safe-stable-stringify@npm:^2.3.1": + version: 2.4.3 + resolution: "safe-stable-stringify@npm:2.4.3" + checksum: 10c0/81dede06b8f2ae794efd868b1e281e3c9000e57b39801c6c162267eb9efda17bd7a9eafa7379e1f1cacd528d4ced7c80d7460ad26f62ada7c9e01dec61b2e768 + languageName: node + linkType: hard + +"safer-buffer@npm:>= 2.1.2 < 3, safer-buffer@npm:>= 2.1.2 < 3.0.0": + version: 2.1.2 + resolution: "safer-buffer@npm:2.1.2" + checksum: 10c0/7e3c8b2e88a1841c9671094bbaeebd94448111dd90a81a1f606f3f67708a6ec57763b3b47f06da09fc6054193e0e6709e77325415dc8422b04497a8070fa02d4 + languageName: node + linkType: hard + +"semver@npm:^6.0.0, semver@npm:^6.3.0, semver@npm:^6.3.1": + version: 6.3.1 + resolution: "semver@npm:6.3.1" + bin: + semver: bin/semver.js + checksum: 10c0/e3d79b609071caa78bcb6ce2ad81c7966a46a7431d9d58b8800cfa9cb6a63699b3899a0e4bcce36167a284578212d9ae6942b6929ba4aa5015c079a67751d42d + languageName: node + linkType: hard + +"semver@npm:^7.3.5, semver@npm:^7.3.7, semver@npm:^7.3.8, semver@npm:^7.5.3, semver@npm:^7.5.4": + version: 7.6.2 + resolution: "semver@npm:7.6.2" + bin: + semver: bin/semver.js + checksum: 10c0/97d3441e97ace8be4b1976433d1c32658f6afaff09f143e52c593bae7eef33de19e3e369c88bd985ce1042c6f441c80c6803078d1de2a9988080b66684cbb30c + languageName: node + linkType: hard + +"send@npm:0.18.0": + version: 0.18.0 + resolution: "send@npm:0.18.0" + dependencies: + debug: "npm:2.6.9" + depd: "npm:2.0.0" + destroy: "npm:1.2.0" + encodeurl: "npm:~1.0.2" + escape-html: "npm:~1.0.3" + etag: "npm:~1.8.1" + fresh: "npm:0.5.2" + http-errors: "npm:2.0.0" + mime: "npm:1.6.0" + ms: "npm:2.1.3" + on-finished: "npm:2.4.1" + range-parser: "npm:~1.2.1" + statuses: "npm:2.0.1" + checksum: 10c0/0eb134d6a51fc13bbcb976a1f4214ea1e33f242fae046efc311e80aff66c7a43603e26a79d9d06670283a13000e51be6e0a2cb80ff0942eaf9f1cd30b7ae736a + languageName: node + linkType: hard + +"send@npm:0.19.0": + version: 0.19.0 + resolution: "send@npm:0.19.0" + dependencies: + debug: "npm:2.6.9" + depd: "npm:2.0.0" + destroy: "npm:1.2.0" + encodeurl: "npm:~1.0.2" + escape-html: "npm:~1.0.3" + etag: "npm:~1.8.1" + fresh: "npm:0.5.2" + http-errors: "npm:2.0.0" + mime: "npm:1.6.0" + ms: "npm:2.1.3" + on-finished: "npm:2.4.1" + range-parser: "npm:~1.2.1" + statuses: "npm:2.0.1" + checksum: 10c0/ea3f8a67a8f0be3d6bf9080f0baed6d2c51d11d4f7b4470de96a5029c598a7011c497511ccc28968b70ef05508675cebff27da9151dd2ceadd60be4e6cf845e3 + languageName: node + linkType: hard + +"serve-static@npm:1.16.0": + version: 1.16.0 + resolution: "serve-static@npm:1.16.0" + dependencies: + encodeurl: "npm:~1.0.2" + escape-html: "npm:~1.0.3" + parseurl: "npm:~1.3.3" + send: "npm:0.18.0" + checksum: 10c0/d7a5beca08cc55f92998d8b87c111dd842d642404231c90c11f504f9650935da4599c13256747b0a988442a59851343271fe8e1946e03e92cd79c447b5f3ae01 + languageName: node + linkType: hard + +"set-blocking@npm:^2.0.0": + version: 2.0.0 + resolution: "set-blocking@npm:2.0.0" + checksum: 10c0/9f8c1b2d800800d0b589de1477c753492de5c1548d4ade52f57f1d1f5e04af5481554d75ce5e5c43d4004b80a3eb714398d6907027dc0534177b7539119f4454 + languageName: node + linkType: hard + +"set-function-length@npm:^1.2.1": + version: 1.2.2 + resolution: "set-function-length@npm:1.2.2" + dependencies: + define-data-property: "npm:^1.1.4" + es-errors: "npm:^1.3.0" + function-bind: "npm:^1.1.2" + get-intrinsic: "npm:^1.2.4" + gopd: "npm:^1.0.1" + has-property-descriptors: "npm:^1.0.2" + checksum: 10c0/82850e62f412a258b71e123d4ed3873fa9377c216809551192bb6769329340176f109c2eeae8c22a8d386c76739855f78e8716515c818bcaef384b51110f0f3c + languageName: node + linkType: hard + +"setprototypeof@npm:1.2.0": + version: 1.2.0 + resolution: "setprototypeof@npm:1.2.0" + checksum: 10c0/68733173026766fa0d9ecaeb07f0483f4c2dc70ca376b3b7c40b7cda909f94b0918f6c5ad5ce27a9160bdfb475efaa9d5e705a11d8eaae18f9835d20976028bc + languageName: node + linkType: hard + +"shebang-command@npm:^2.0.0": + version: 2.0.0 + resolution: "shebang-command@npm:2.0.0" + dependencies: + shebang-regex: "npm:^3.0.0" + checksum: 10c0/a41692e7d89a553ef21d324a5cceb5f686d1f3c040759c50aab69688634688c5c327f26f3ecf7001ebfd78c01f3c7c0a11a7c8bfd0a8bc9f6240d4f40b224e4e + languageName: node + linkType: hard + +"shebang-regex@npm:^3.0.0": + version: 3.0.0 + resolution: "shebang-regex@npm:3.0.0" + checksum: 10c0/1dbed0726dd0e1152a92696c76c7f06084eb32a90f0528d11acd764043aacf76994b2fb30aa1291a21bd019d6699164d048286309a278855ee7bec06cf6fb690 + languageName: node + linkType: hard + +"side-channel@npm:^1.0.4, side-channel@npm:^1.0.6": + version: 1.0.6 + resolution: "side-channel@npm:1.0.6" + dependencies: + call-bind: "npm:^1.0.7" + es-errors: "npm:^1.3.0" + get-intrinsic: "npm:^1.2.4" + object-inspect: "npm:^1.13.1" + checksum: 10c0/d2afd163dc733cc0a39aa6f7e39bf0c436293510dbccbff446733daeaf295857dbccf94297092ec8c53e2503acac30f0b78830876f0485991d62a90e9cad305f + languageName: node + linkType: hard + +"signal-exit@npm:^3.0.0, signal-exit@npm:^3.0.3, signal-exit@npm:^3.0.7": + version: 3.0.7 + resolution: "signal-exit@npm:3.0.7" + checksum: 10c0/25d272fa73e146048565e08f3309d5b942c1979a6f4a58a8c59d5fa299728e9c2fcd1a759ec870863b1fd38653670240cd420dad2ad9330c71f36608a6a1c912 + languageName: node + linkType: hard + +"signal-exit@npm:^4.0.1": + version: 4.1.0 + resolution: "signal-exit@npm:4.1.0" + checksum: 10c0/41602dce540e46d599edba9d9860193398d135f7ff72cab629db5171516cfae628d21e7bfccde1bbfdf11c48726bc2a6d1a8fb8701125852fbfda7cf19c6aa83 + languageName: node + linkType: hard + +"simple-concat@npm:^1.0.0": + version: 1.0.1 + resolution: "simple-concat@npm:1.0.1" + checksum: 10c0/62f7508e674414008910b5397c1811941d457dfa0db4fd5aa7fa0409eb02c3609608dfcd7508cace75b3a0bf67a2a77990711e32cd213d2c76f4fd12ee86d776 + languageName: node + linkType: hard + +"simple-get@npm:^4.0.0": + version: 4.0.1 + resolution: "simple-get@npm:4.0.1" + dependencies: + decompress-response: "npm:^6.0.0" + once: "npm:^1.3.1" + simple-concat: "npm:^1.0.0" + checksum: 10c0/b0649a581dbca741babb960423248899203165769747142033479a7dc5e77d7b0fced0253c731cd57cf21e31e4d77c9157c3069f4448d558ebc96cf9e1eebcf0 + languageName: node + linkType: hard + +"simple-swizzle@npm:^0.2.2": + version: 0.2.2 + resolution: "simple-swizzle@npm:0.2.2" + dependencies: + is-arrayish: "npm:^0.3.1" + checksum: 10c0/df5e4662a8c750bdba69af4e8263c5d96fe4cd0f9fe4bdfa3cbdeb45d2e869dff640beaaeb1ef0e99db4d8d2ec92f85508c269f50c972174851bc1ae5bd64308 + languageName: node + linkType: hard + +"sisteransi@npm:^1.0.5": + version: 1.0.5 + resolution: "sisteransi@npm:1.0.5" + checksum: 10c0/230ac975cca485b7f6fe2b96a711aa62a6a26ead3e6fb8ba17c5a00d61b8bed0d7adc21f5626b70d7c33c62ff4e63933017a6462942c719d1980bb0b1207ad46 + languageName: node + linkType: hard + +"slash@npm:^3.0.0": + version: 3.0.0 + resolution: "slash@npm:3.0.0" + checksum: 10c0/e18488c6a42bdfd4ac5be85b2ced3ccd0224773baae6ad42cfbb9ec74fc07f9fa8396bd35ee638084ead7a2a0818eb5e7151111544d4731ce843019dab4be47b + languageName: node + linkType: hard + +"slug@npm:^8.2.2": + version: 8.2.3 + resolution: "slug@npm:8.2.3" + bin: + slug: cli.js + checksum: 10c0/82499b57e5d2f8425e5fa366da86cf911b0d60c2d3143cadd89039fb6c5b8a3d340cd2fe26f4c85b62a6bdaa64327da5e7554ddbda176b58ee56ad932bfa3708 + languageName: node + linkType: hard + +"smart-buffer@npm:^4.2.0": + version: 4.2.0 + resolution: "smart-buffer@npm:4.2.0" + checksum: 10c0/a16775323e1404dd43fabafe7460be13a471e021637bc7889468eb45ce6a6b207261f454e4e530a19500cc962c4cc5348583520843b363f4193cee5c00e1e539 + languageName: node + linkType: hard + +"socks-proxy-agent@npm:^8.0.3": + version: 8.0.3 + resolution: "socks-proxy-agent@npm:8.0.3" + dependencies: + agent-base: "npm:^7.1.1" + debug: "npm:^4.3.4" + socks: "npm:^2.7.1" + checksum: 10c0/4950529affd8ccd6951575e21c1b7be8531b24d924aa4df3ee32df506af34b618c4e50d261f4cc603f1bfd8d426915b7d629966c8ce45b05fb5ad8c8b9a6459d + languageName: node + linkType: hard + +"socks@npm:^2.7.1": + version: 2.8.3 + resolution: "socks@npm:2.8.3" + dependencies: + ip-address: "npm:^9.0.5" + smart-buffer: "npm:^4.2.0" + checksum: 10c0/d54a52bf9325165770b674a67241143a3d8b4e4c8884560c4e0e078aace2a728dffc7f70150660f51b85797c4e1a3b82f9b7aa25e0a0ceae1a243365da5c51a7 + languageName: node + linkType: hard + +"source-map-support@npm:0.5.13": + version: 0.5.13 + resolution: "source-map-support@npm:0.5.13" + dependencies: + buffer-from: "npm:^1.0.0" + source-map: "npm:^0.6.0" + checksum: 10c0/137539f8c453fa0f496ea42049ab5da4569f96781f6ac8e5bfda26937be9494f4e8891f523c5f98f0e85f71b35d74127a00c46f83f6a4f54672b58d53202565e + languageName: node + linkType: hard + +"source-map@npm:^0.6.0, source-map@npm:^0.6.1": + version: 0.6.1 + resolution: "source-map@npm:0.6.1" + checksum: 10c0/ab55398007c5e5532957cb0beee2368529618ac0ab372d789806f5718123cc4367d57de3904b4e6a4170eb5a0b0f41373066d02ca0735a0c4d75c7d328d3e011 + languageName: node + linkType: hard + +"sprintf-js@npm:^1.1.3": + version: 1.1.3 + resolution: "sprintf-js@npm:1.1.3" + checksum: 10c0/09270dc4f30d479e666aee820eacd9e464215cdff53848b443964202bf4051490538e5dd1b42e1a65cf7296916ca17640aebf63dae9812749c7542ee5f288dec + languageName: node + linkType: hard + +"sprintf-js@npm:~1.0.2": + version: 1.0.3 + resolution: "sprintf-js@npm:1.0.3" + checksum: 10c0/ecadcfe4c771890140da5023d43e190b7566d9cf8b2d238600f31bec0fc653f328da4450eb04bd59a431771a8e9cc0e118f0aa3974b683a4981b4e07abc2a5bb + languageName: node + linkType: hard + +"ssri@npm:^10.0.0": + version: 10.0.6 + resolution: "ssri@npm:10.0.6" + dependencies: + minipass: "npm:^7.0.3" + checksum: 10c0/e5a1e23a4057a86a97971465418f22ea89bd439ac36ade88812dd920e4e61873e8abd6a9b72a03a67ef50faa00a2daf1ab745c5a15b46d03e0544a0296354227 + languageName: node + linkType: hard + +"stack-trace@npm:0.0.x": + version: 0.0.10 + resolution: "stack-trace@npm:0.0.10" + checksum: 10c0/9ff3dabfad4049b635a85456f927a075c9d0c210e3ea336412d18220b2a86cbb9b13ec46d6c37b70a302a4ea4d49e30e5d4944dd60ae784073f1cde778ac8f4b + languageName: node + linkType: hard + +"stack-utils@npm:^2.0.3": + version: 2.0.6 + resolution: "stack-utils@npm:2.0.6" + dependencies: + escape-string-regexp: "npm:^2.0.0" + checksum: 10c0/651c9f87667e077584bbe848acaecc6049bc71979f1e9a46c7b920cad4431c388df0f51b8ad7cfd6eed3db97a2878d0fc8b3122979439ea8bac29c61c95eec8a + languageName: node + linkType: hard + +"statuses@npm:2.0.1": + version: 2.0.1 + resolution: "statuses@npm:2.0.1" + checksum: 10c0/34378b207a1620a24804ce8b5d230fea0c279f00b18a7209646d5d47e419d1cc23e7cbf33a25a1e51ac38973dc2ac2e1e9c647a8e481ef365f77668d72becfd0 + languageName: node + linkType: hard + +"string-length@npm:^4.0.1": + version: 4.0.2 + resolution: "string-length@npm:4.0.2" + dependencies: + char-regex: "npm:^1.0.2" + strip-ansi: "npm:^6.0.0" + checksum: 10c0/1cd77409c3d7db7bc59406f6bcc9ef0783671dcbabb23597a1177c166906ef2ee7c8290f78cae73a8aec858768f189d2cb417797df5e15ec4eb5e16b3346340c + languageName: node + linkType: hard + +"string-width-cjs@npm:string-width@^4.2.0, string-width@npm:^1.0.2 || 2 || 3 || 4, string-width@npm:^4.1.0, string-width@npm:^4.2.0, string-width@npm:^4.2.3": + version: 4.2.3 + resolution: "string-width@npm:4.2.3" + dependencies: + emoji-regex: "npm:^8.0.0" + is-fullwidth-code-point: "npm:^3.0.0" + strip-ansi: "npm:^6.0.1" + checksum: 10c0/1e525e92e5eae0afd7454086eed9c818ee84374bb80328fc41217ae72ff5f065ef1c9d7f72da41de40c75fa8bb3dee63d92373fd492c84260a552c636392a47b + languageName: node + linkType: hard + +"string-width@npm:^5.0.1, string-width@npm:^5.1.2": + version: 5.1.2 + resolution: "string-width@npm:5.1.2" + dependencies: + eastasianwidth: "npm:^0.2.0" + emoji-regex: "npm:^9.2.2" + strip-ansi: "npm:^7.0.1" + checksum: 10c0/ab9c4264443d35b8b923cbdd513a089a60de339216d3b0ed3be3ba57d6880e1a192b70ae17225f764d7adbf5994e9bb8df253a944736c15a0240eff553c678ca + languageName: node + linkType: hard + +"string_decoder@npm:^1.1.1": + version: 1.3.0 + resolution: "string_decoder@npm:1.3.0" + dependencies: + safe-buffer: "npm:~5.2.0" + checksum: 10c0/810614ddb030e271cd591935dcd5956b2410dd079d64ff92a1844d6b7588bf992b3e1b69b0f4d34a3e06e0bd73046ac646b5264c1987b20d0601f81ef35d731d + languageName: node + linkType: hard + +"strip-ansi-cjs@npm:strip-ansi@^6.0.1, strip-ansi@npm:^6.0.0, strip-ansi@npm:^6.0.1": + version: 6.0.1 + resolution: "strip-ansi@npm:6.0.1" + dependencies: + ansi-regex: "npm:^5.0.1" + checksum: 10c0/1ae5f212a126fe5b167707f716942490e3933085a5ff6c008ab97ab2f272c8025d3aa218b7bd6ab25729ca20cc81cddb252102f8751e13482a5199e873680952 + languageName: node + linkType: hard + +"strip-ansi@npm:^7.0.1": + version: 7.1.0 + resolution: "strip-ansi@npm:7.1.0" + dependencies: + ansi-regex: "npm:^6.0.1" + checksum: 10c0/a198c3762e8832505328cbf9e8c8381de14a4fa50a4f9b2160138158ea88c0f5549fb50cb13c651c3088f47e63a108b34622ec18c0499b6c8c3a5ddf6b305ac4 + languageName: node + linkType: hard + +"strip-bom@npm:^4.0.0": + version: 4.0.0 + resolution: "strip-bom@npm:4.0.0" + checksum: 10c0/26abad1172d6bc48985ab9a5f96c21e440f6e7e476686de49be813b5a59b3566dccb5c525b831ec54fe348283b47f3ffb8e080bc3f965fde12e84df23f6bb7ef + languageName: node + linkType: hard + +"strip-final-newline@npm:^2.0.0": + version: 2.0.0 + resolution: "strip-final-newline@npm:2.0.0" + checksum: 10c0/bddf8ccd47acd85c0e09ad7375409d81653f645fda13227a9d459642277c253d877b68f2e5e4d819fe75733b0e626bac7e954c04f3236f6d196f79c94fa4a96f + languageName: node + linkType: hard + +"strip-json-comments@npm:^3.1.1": + version: 3.1.1 + resolution: "strip-json-comments@npm:3.1.1" + checksum: 10c0/9681a6257b925a7fa0f285851c0e613cc934a50661fa7bb41ca9cbbff89686bb4a0ee366e6ecedc4daafd01e83eee0720111ab294366fe7c185e935475ebcecd + languageName: node + linkType: hard + +"strip-json-comments@npm:~2.0.1": + version: 2.0.1 + resolution: "strip-json-comments@npm:2.0.1" + checksum: 10c0/b509231cbdee45064ff4f9fd73609e2bcc4e84a4d508e9dd0f31f70356473fde18abfb5838c17d56fb236f5a06b102ef115438de0600b749e818a35fbbc48c43 + languageName: node + linkType: hard + +"superagent@npm:^8.1.2": + version: 8.1.2 + resolution: "superagent@npm:8.1.2" + dependencies: + component-emitter: "npm:^1.3.0" + cookiejar: "npm:^2.1.4" + debug: "npm:^4.3.4" + fast-safe-stringify: "npm:^2.1.1" + form-data: "npm:^4.0.0" + formidable: "npm:^2.1.2" + methods: "npm:^1.1.2" + mime: "npm:2.6.0" + qs: "npm:^6.11.0" + semver: "npm:^7.3.8" + checksum: 10c0/016416fc9c3d3a04fb648bc0efb3d3d5c9d96da00de47e4a625d9976d28c6c37ab0a7f185f2c3ec6d653ee8bb522f70fba0c1072aea7774341a6c0269a9fa77f + languageName: node + linkType: hard + +"supertest@npm:^6.3.1": + version: 6.3.4 + resolution: "supertest@npm:6.3.4" + dependencies: + methods: "npm:^1.1.2" + superagent: "npm:^8.1.2" + checksum: 10c0/f8c0b6c73b5e87da31feee6ccb36e7af766a438513cad89d6907f22c97edd83b1e765b4c8de955d5f7af4bca5fd0aaf9149ff48e21567dd290b326a8633af2a7 + languageName: node + linkType: hard + +"supports-color@npm:^5.3.0": + version: 5.5.0 + resolution: "supports-color@npm:5.5.0" + dependencies: + has-flag: "npm:^3.0.0" + checksum: 10c0/6ae5ff319bfbb021f8a86da8ea1f8db52fac8bd4d499492e30ec17095b58af11f0c55f8577390a749b1c4dde691b6a0315dab78f5f54c9b3d83f8fb5905c1c05 + languageName: node + linkType: hard + +"supports-color@npm:^7.1.0": + version: 7.2.0 + resolution: "supports-color@npm:7.2.0" + dependencies: + has-flag: "npm:^4.0.0" + checksum: 10c0/afb4c88521b8b136b5f5f95160c98dee7243dc79d5432db7efc27efb219385bbc7d9427398e43dd6cc730a0f87d5085ce1652af7efbe391327bc0a7d0f7fc124 + languageName: node + linkType: hard + +"supports-color@npm:^8.0.0": + version: 8.1.1 + resolution: "supports-color@npm:8.1.1" + dependencies: + has-flag: "npm:^4.0.0" + checksum: 10c0/ea1d3c275dd604c974670f63943ed9bd83623edc102430c05adb8efc56ba492746b6e95386e7831b872ec3807fd89dd8eb43f735195f37b5ec343e4234cc7e89 + languageName: node + linkType: hard + +"supports-preserve-symlinks-flag@npm:^1.0.0": + version: 1.0.0 + resolution: "supports-preserve-symlinks-flag@npm:1.0.0" + checksum: 10c0/6c4032340701a9950865f7ae8ef38578d8d7053f5e10518076e6554a9381fa91bd9c6850193695c141f32b21f979c985db07265a758867bac95de05f7d8aeb39 + languageName: node + linkType: hard + +"tar-fs@npm:^2.0.0": + version: 2.1.1 + resolution: "tar-fs@npm:2.1.1" + dependencies: + chownr: "npm:^1.1.1" + mkdirp-classic: "npm:^0.5.2" + pump: "npm:^3.0.0" + tar-stream: "npm:^2.1.4" + checksum: 10c0/871d26a934bfb7beeae4c4d8a09689f530b565f79bd0cf489823ff0efa3705da01278160da10bb006d1a793fa0425cf316cec029b32a9159eacbeaff4965fb6d + languageName: node + linkType: hard + +"tar-stream@npm:^2.1.4": + version: 2.2.0 + resolution: "tar-stream@npm:2.2.0" + dependencies: + bl: "npm:^4.0.3" + end-of-stream: "npm:^1.4.1" + fs-constants: "npm:^1.0.0" + inherits: "npm:^2.0.3" + readable-stream: "npm:^3.1.1" + checksum: 10c0/2f4c910b3ee7196502e1ff015a7ba321ec6ea837667220d7bcb8d0852d51cb04b87f7ae471008a6fb8f5b1a1b5078f62f3a82d30c706f20ada1238ac797e7692 + languageName: node + linkType: hard + +"tar@npm:^6.1.11, tar@npm:^6.1.2": + version: 6.2.1 + resolution: "tar@npm:6.2.1" + dependencies: + chownr: "npm:^2.0.0" + fs-minipass: "npm:^2.0.0" + minipass: "npm:^5.0.0" + minizlib: "npm:^2.1.1" + mkdirp: "npm:^1.0.3" + yallist: "npm:^4.0.0" + checksum: 10c0/a5eca3eb50bc11552d453488344e6507156b9193efd7635e98e867fab275d527af53d8866e2370cd09dfe74378a18111622ace35af6a608e5223a7d27fe99537 + languageName: node + linkType: hard + +"test-exclude@npm:^6.0.0": + version: 6.0.0 + resolution: "test-exclude@npm:6.0.0" + dependencies: + "@istanbuljs/schema": "npm:^0.1.2" + glob: "npm:^7.1.4" + minimatch: "npm:^3.0.4" + checksum: 10c0/019d33d81adff3f9f1bfcff18125fb2d3c65564f437d9be539270ee74b994986abb8260c7c2ce90e8f30162178b09dbbce33c6389273afac4f36069c48521f57 + languageName: node + linkType: hard + +"text-hex@npm:1.0.x": + version: 1.0.0 + resolution: "text-hex@npm:1.0.0" + checksum: 10c0/57d8d320d92c79d7c03ffb8339b825bb9637c2cbccf14304309f51d8950015c44464b6fd1b6820a3d4821241c68825634f09f5a2d9d501e84f7c6fd14376860d + languageName: node + linkType: hard + +"text-table@npm:^0.2.0": + version: 0.2.0 + resolution: "text-table@npm:0.2.0" + checksum: 10c0/02805740c12851ea5982686810702e2f14369a5f4c5c40a836821e3eefc65ffeec3131ba324692a37608294b0fd8c1e55a2dd571ffed4909822787668ddbee5c + languageName: node + linkType: hard + +"tmpl@npm:1.0.5": + version: 1.0.5 + resolution: "tmpl@npm:1.0.5" + checksum: 10c0/f935537799c2d1922cb5d6d3805f594388f75338fe7a4a9dac41504dd539704ca4db45b883b52e7b0aa5b2fd5ddadb1452bf95cd23a69da2f793a843f9451cc9 + languageName: node + linkType: hard + +"to-fast-properties@npm:^2.0.0": + version: 2.0.0 + resolution: "to-fast-properties@npm:2.0.0" + checksum: 10c0/b214d21dbfb4bce3452b6244b336806ffea9c05297148d32ebb428d5c43ce7545bdfc65a1ceb58c9ef4376a65c0cb2854d645f33961658b3e3b4f84910ddcdd7 + languageName: node + linkType: hard + +"to-regex-range@npm:^5.0.1": + version: 5.0.1 + resolution: "to-regex-range@npm:5.0.1" + dependencies: + is-number: "npm:^7.0.0" + checksum: 10c0/487988b0a19c654ff3e1961b87f471702e708fa8a8dd02a298ef16da7206692e8552a0250e8b3e8759270f62e9d8314616f6da274734d3b558b1fc7b7724e892 + languageName: node + linkType: hard + +"toidentifier@npm:1.0.1": + version: 1.0.1 + resolution: "toidentifier@npm:1.0.1" + checksum: 10c0/93937279934bd66cc3270016dd8d0afec14fb7c94a05c72dc57321f8bd1fa97e5bea6d1f7c89e728d077ca31ea125b78320a616a6c6cd0e6b9cb94cb864381c1 + languageName: node + linkType: hard + +"tr46@npm:~0.0.3": + version: 0.0.3 + resolution: "tr46@npm:0.0.3" + checksum: 10c0/047cb209a6b60c742f05c9d3ace8fa510bff609995c129a37ace03476a9b12db4dbf975e74600830ef0796e18882b2381fb5fb1f6b4f96b832c374de3ab91a11 + languageName: node + linkType: hard + +"triple-beam@npm:^1.3.0": + version: 1.4.1 + resolution: "triple-beam@npm:1.4.1" + checksum: 10c0/4bf1db71e14fe3ff1c3adbe3c302f1fdb553b74d7591a37323a7badb32dc8e9c290738996cbb64f8b10dc5a3833645b5d8c26221aaaaa12e50d1251c9aba2fea + languageName: node + linkType: hard + +"tslib@npm:^1.8.1": + version: 1.14.1 + resolution: "tslib@npm:1.14.1" + checksum: 10c0/69ae09c49eea644bc5ebe1bca4fa4cc2c82b7b3e02f43b84bd891504edf66dbc6b2ec0eef31a957042de2269139e4acff911e6d186a258fb14069cd7f6febce2 + languageName: node + linkType: hard + +"tsutils@npm:^3.21.0": + version: 3.21.0 + resolution: "tsutils@npm:3.21.0" + dependencies: + tslib: "npm:^1.8.1" + peerDependencies: + typescript: ">=2.8.0 || >= 3.2.0-dev || >= 3.3.0-dev || >= 3.4.0-dev || >= 3.5.0-dev || >= 3.6.0-dev || >= 3.6.0-beta || >= 3.7.0-dev || >= 3.7.0-beta" + checksum: 10c0/02f19e458ec78ead8fffbf711f834ad8ecd2cc6ade4ec0320790713dccc0a412b99e7fd907c4cda2a1dc602c75db6f12e0108e87a5afad4b2f9e90a24cabd5a2 + languageName: node + linkType: hard + +"tunnel-agent@npm:^0.6.0": + version: 0.6.0 + resolution: "tunnel-agent@npm:0.6.0" + dependencies: + safe-buffer: "npm:^5.0.1" + checksum: 10c0/4c7a1b813e7beae66fdbf567a65ec6d46313643753d0beefb3c7973d66fcec3a1e7f39759f0a0b4465883499c6dc8b0750ab8b287399af2e583823e40410a17a + languageName: node + linkType: hard + +"type-check@npm:^0.4.0, type-check@npm:~0.4.0": + version: 0.4.0 + resolution: "type-check@npm:0.4.0" + dependencies: + prelude-ls: "npm:^1.2.1" + checksum: 10c0/7b3fd0ed43891e2080bf0c5c504b418fbb3e5c7b9708d3d015037ba2e6323a28152ec163bcb65212741fa5d2022e3075ac3c76440dbd344c9035f818e8ecee58 + languageName: node + linkType: hard + +"type-detect@npm:4.0.8": + version: 4.0.8 + resolution: "type-detect@npm:4.0.8" + checksum: 10c0/8fb9a51d3f365a7de84ab7f73b653534b61b622aa6800aecdb0f1095a4a646d3f5eb295322127b6573db7982afcd40ab492d038cf825a42093a58b1e1353e0bd + languageName: node + linkType: hard + +"type-fest@npm:^0.20.2": + version: 0.20.2 + resolution: "type-fest@npm:0.20.2" + checksum: 10c0/dea9df45ea1f0aaa4e2d3bed3f9a0bfe9e5b2592bddb92eb1bf06e50bcf98dbb78189668cd8bc31a0511d3fc25539b4cd5c704497e53e93e2d40ca764b10bfc3 + languageName: node + linkType: hard + +"type-fest@npm:^0.21.3": + version: 0.21.3 + resolution: "type-fest@npm:0.21.3" + checksum: 10c0/902bd57bfa30d51d4779b641c2bc403cdf1371fb9c91d3c058b0133694fcfdb817aef07a47f40faf79039eecbaa39ee9d3c532deff244f3a19ce68cea71a61e8 + languageName: node + linkType: hard + +"type-is@npm:~1.6.18": + version: 1.6.18 + resolution: "type-is@npm:1.6.18" + dependencies: + media-typer: "npm:0.3.0" + mime-types: "npm:~2.1.24" + checksum: 10c0/a23daeb538591b7efbd61ecf06b6feb2501b683ffdc9a19c74ef5baba362b4347e42f1b4ed81f5882a8c96a3bfff7f93ce3ffaf0cbbc879b532b04c97a55db9d + languageName: node + linkType: hard + +"typescript@npm:^4.9.5": + version: 4.9.5 + resolution: "typescript@npm:4.9.5" + bin: + tsc: bin/tsc + tsserver: bin/tsserver + checksum: 10c0/5f6cad2e728a8a063521328e612d7876e12f0d8a8390d3b3aaa452a6a65e24e9ac8ea22beb72a924fd96ea0a49ea63bb4e251fb922b12eedfb7f7a26475e5c56 + languageName: node + linkType: hard + +"typescript@patch:typescript@npm%3A^4.9.5#optional!builtin<compat/typescript>": + version: 4.9.5 + resolution: "typescript@patch:typescript@npm%3A4.9.5#optional!builtin<compat/typescript>::version=4.9.5&hash=289587" + bin: + tsc: bin/tsc + tsserver: bin/tsserver + checksum: 10c0/e3333f887c6829dfe0ab6c1dbe0dd1e3e2aeb56c66460cb85c5440c566f900c833d370ca34eb47558c0c69e78ced4bfe09b8f4f98b6de7afed9b84b8d1dd06a1 + languageName: node + linkType: hard + +"undici-types@npm:~5.26.4": + version: 5.26.5 + resolution: "undici-types@npm:5.26.5" + checksum: 10c0/bb673d7876c2d411b6eb6c560e0c571eef4a01c1c19925175d16e3a30c4c428181fb8d7ae802a261f283e4166a0ac435e2f505743aa9e45d893f9a3df017b501 + languageName: node + linkType: hard + +"unique-filename@npm:^3.0.0": + version: 3.0.0 + resolution: "unique-filename@npm:3.0.0" + dependencies: + unique-slug: "npm:^4.0.0" + checksum: 10c0/6363e40b2fa758eb5ec5e21b3c7fb83e5da8dcfbd866cc0c199d5534c42f03b9ea9ab069769cc388e1d7ab93b4eeef28ef506ab5f18d910ef29617715101884f + languageName: node + linkType: hard + +"unique-slug@npm:^4.0.0": + version: 4.0.0 + resolution: "unique-slug@npm:4.0.0" + dependencies: + imurmurhash: "npm:^0.1.4" + checksum: 10c0/cb811d9d54eb5821b81b18205750be84cb015c20a4a44280794e915f5a0a70223ce39066781a354e872df3572e8155c228f43ff0cce94c7cbf4da2cc7cbdd635 + languageName: node + linkType: hard + +"unpipe@npm:1.0.0, unpipe@npm:~1.0.0": + version: 1.0.0 + resolution: "unpipe@npm:1.0.0" + checksum: 10c0/193400255bd48968e5c5383730344fbb4fa114cdedfab26e329e50dd2d81b134244bb8a72c6ac1b10ab0281a58b363d06405632c9d49ca9dfd5e90cbd7d0f32c + languageName: node + linkType: hard + +"update-browserslist-db@npm:^1.0.16": + version: 1.0.16 + resolution: "update-browserslist-db@npm:1.0.16" + dependencies: + escalade: "npm:^3.1.2" + picocolors: "npm:^1.0.1" + peerDependencies: + browserslist: ">= 4.21.0" + bin: + update-browserslist-db: cli.js + checksum: 10c0/5995399fc202adbb51567e4810e146cdf7af630a92cc969365a099150cb00597e425cc14987ca7080b09a4d0cfd2a3de53fbe72eebff171aed7f9bb81f9bf405 + languageName: node + linkType: hard + +"uri-js@npm:^4.2.2": + version: 4.4.1 + resolution: "uri-js@npm:4.4.1" + dependencies: + punycode: "npm:^2.1.0" + checksum: 10c0/4ef57b45aa820d7ac6496e9208559986c665e49447cb072744c13b66925a362d96dd5a46c4530a6b8e203e5db5fe849369444440cb22ecfc26c679359e5dfa3c + languageName: node + linkType: hard + +"util-deprecate@npm:^1.0.1": + version: 1.0.2 + resolution: "util-deprecate@npm:1.0.2" + checksum: 10c0/41a5bdd214df2f6c3ecf8622745e4a366c4adced864bc3c833739791aeeeb1838119af7daed4ba36428114b5c67dcda034a79c882e97e43c03e66a4dd7389942 + languageName: node + linkType: hard + +"utils-merge@npm:1.0.1": + version: 1.0.1 + resolution: "utils-merge@npm:1.0.1" + checksum: 10c0/02ba649de1b7ca8854bfe20a82f1dfbdda3fb57a22ab4a8972a63a34553cf7aa51bc9081cf7e001b035b88186d23689d69e71b510e610a09a4c66f68aa95b672 + languageName: node + linkType: hard + +"uuid@npm:^9.0.0": + version: 9.0.1 + resolution: "uuid@npm:9.0.1" + bin: + uuid: dist/bin/uuid + checksum: 10c0/1607dd32ac7fc22f2d8f77051e6a64845c9bce5cd3dd8aa0070c074ec73e666a1f63c7b4e0f4bf2bc8b9d59dc85a15e17807446d9d2b17c8485fbc2147b27f9b + languageName: node + linkType: hard + +"v8-to-istanbul@npm:^9.0.1": + version: 9.2.0 + resolution: "v8-to-istanbul@npm:9.2.0" + dependencies: + "@jridgewell/trace-mapping": "npm:^0.3.12" + "@types/istanbul-lib-coverage": "npm:^2.0.1" + convert-source-map: "npm:^2.0.0" + checksum: 10c0/e691ba4dd0dea4a884e52c37dbda30cce6f9eeafe9b26721e449429c6bb0f4b6d1e33fabe7711d0f67f7a34c3bfd56c873f7375bba0b1534e6a2843ce99550e5 + languageName: node + linkType: hard + +"vary@npm:^1, vary@npm:~1.1.2": + version: 1.1.2 + resolution: "vary@npm:1.1.2" + checksum: 10c0/f15d588d79f3675135ba783c91a4083dcd290a2a5be9fcb6514220a1634e23df116847b1cc51f66bfb0644cf9353b2abb7815ae499bab06e46dd33c1a6bf1f4f + languageName: node + linkType: hard + +"walker@npm:^1.0.8": + version: 1.0.8 + resolution: "walker@npm:1.0.8" + dependencies: + makeerror: "npm:1.0.12" + checksum: 10c0/a17e037bccd3ca8a25a80cb850903facdfed0de4864bd8728f1782370715d679fa72e0a0f5da7c1c1379365159901e5935f35be531229da53bbfc0efdabdb48e + languageName: node + linkType: hard + +"webidl-conversions@npm:^3.0.0": + version: 3.0.1 + resolution: "webidl-conversions@npm:3.0.1" + checksum: 10c0/5612d5f3e54760a797052eb4927f0ddc01383550f542ccd33d5238cfd65aeed392a45ad38364970d0a0f4fea32e1f4d231b3d8dac4a3bdd385e5cf802ae097db + languageName: node + linkType: hard + +"whatwg-url@npm:^5.0.0": + version: 5.0.0 + resolution: "whatwg-url@npm:5.0.0" + dependencies: + tr46: "npm:~0.0.3" + webidl-conversions: "npm:^3.0.0" + checksum: 10c0/1588bed84d10b72d5eec1d0faa0722ba1962f1821e7539c535558fb5398d223b0c50d8acab950b8c488b4ba69043fd833cc2697056b167d8ad46fac3995a55d5 + languageName: node + linkType: hard + +"which@npm:^2.0.1": + version: 2.0.2 + resolution: "which@npm:2.0.2" + dependencies: + isexe: "npm:^2.0.0" + bin: + node-which: ./bin/node-which + checksum: 10c0/66522872a768b60c2a65a57e8ad184e5372f5b6a9ca6d5f033d4b0dc98aff63995655a7503b9c0a2598936f532120e81dd8cc155e2e92ed662a2b9377cc4374f + languageName: node + linkType: hard + +"which@npm:^4.0.0": + version: 4.0.0 + resolution: "which@npm:4.0.0" + dependencies: + isexe: "npm:^3.1.1" + bin: + node-which: bin/which.js + checksum: 10c0/449fa5c44ed120ccecfe18c433296a4978a7583bf2391c50abce13f76878d2476defde04d0f79db8165bdf432853c1f8389d0485ca6e8ebce3bbcded513d5e6a + languageName: node + linkType: hard + +"wide-align@npm:^1.1.2": + version: 1.1.5 + resolution: "wide-align@npm:1.1.5" + dependencies: + string-width: "npm:^1.0.2 || 2 || 3 || 4" + checksum: 10c0/1d9c2a3e36dfb09832f38e2e699c367ef190f96b82c71f809bc0822c306f5379df87bab47bed27ea99106d86447e50eb972d3c516c2f95782807a9d082fbea95 + languageName: node + linkType: hard + +"winston-transport@npm:^4.7.0": + version: 4.7.1 + resolution: "winston-transport@npm:4.7.1" + dependencies: + logform: "npm:^2.6.1" + readable-stream: "npm:^3.6.2" + triple-beam: "npm:^1.3.0" + checksum: 10c0/99b7b55cc2ef7f38988ab1717e7fd946c81b856b42a9530aef8ee725490ef2f2811f9cb06d63aa2f76a85fe99ae15b3bef10a54afde3be8b5059ce325e78481f + languageName: node + linkType: hard + +"winston@npm:^3.14.2": + version: 3.14.2 + resolution: "winston@npm:3.14.2" + dependencies: + "@colors/colors": "npm:^1.6.0" + "@dabh/diagnostics": "npm:^2.0.2" + async: "npm:^3.2.3" + is-stream: "npm:^2.0.0" + logform: "npm:^2.6.0" + one-time: "npm:^1.0.0" + readable-stream: "npm:^3.4.0" + safe-stable-stringify: "npm:^2.3.1" + stack-trace: "npm:0.0.x" + triple-beam: "npm:^1.3.0" + winston-transport: "npm:^4.7.0" + checksum: 10c0/3f8fe505ea18310982e60452f335dd2b22fdbc9b25839b6ad882971b2416d5adc94a1f1a46e24cb37d967ad01dfe5499adaf5e53575626b5ebb2a25ff30f4e1d + languageName: node + linkType: hard + +"word-wrap@npm:^1.2.5": + version: 1.2.5 + resolution: "word-wrap@npm:1.2.5" + checksum: 10c0/e0e4a1ca27599c92a6ca4c32260e8a92e8a44f4ef6ef93f803f8ed823f486e0889fc0b93be4db59c8d51b3064951d25e43d434e95dc8c960cc3a63d65d00ba20 + languageName: node + linkType: hard + +"wrap-ansi-cjs@npm:wrap-ansi@^7.0.0, wrap-ansi@npm:^7.0.0": + version: 7.0.0 + resolution: "wrap-ansi@npm:7.0.0" + dependencies: + ansi-styles: "npm:^4.0.0" + string-width: "npm:^4.1.0" + strip-ansi: "npm:^6.0.0" + checksum: 10c0/d15fc12c11e4cbc4044a552129ebc75ee3f57aa9c1958373a4db0292d72282f54373b536103987a4a7594db1ef6a4f10acf92978f79b98c49306a4b58c77d4da + languageName: node + linkType: hard + +"wrap-ansi@npm:^8.1.0": + version: 8.1.0 + resolution: "wrap-ansi@npm:8.1.0" + dependencies: + ansi-styles: "npm:^6.1.0" + string-width: "npm:^5.0.1" + strip-ansi: "npm:^7.0.1" + checksum: 10c0/138ff58a41d2f877eae87e3282c0630fc2789012fc1af4d6bd626eeb9a2f9a65ca92005e6e69a75c7b85a68479fe7443c7dbe1eb8fbaa681a4491364b7c55c60 + languageName: node + linkType: hard + +"wrappy@npm:1": + version: 1.0.2 + resolution: "wrappy@npm:1.0.2" + checksum: 10c0/56fece1a4018c6a6c8e28fbc88c87e0fbf4ea8fd64fc6c63b18f4acc4bd13e0ad2515189786dd2c30d3eec9663d70f4ecf699330002f8ccb547e4a18231fc9f0 + languageName: node + linkType: hard + +"write-file-atomic@npm:^4.0.2": + version: 4.0.2 + resolution: "write-file-atomic@npm:4.0.2" + dependencies: + imurmurhash: "npm:^0.1.4" + signal-exit: "npm:^3.0.7" + checksum: 10c0/a2c282c95ef5d8e1c27b335ae897b5eca00e85590d92a3fd69a437919b7b93ff36a69ea04145da55829d2164e724bc62202cdb5f4b208b425aba0807889375c7 + languageName: node + linkType: hard + +"y18n@npm:^5.0.5": + version: 5.0.8 + resolution: "y18n@npm:5.0.8" + checksum: 10c0/4df2842c36e468590c3691c894bc9cdbac41f520566e76e24f59401ba7d8b4811eb1e34524d57e54bc6d864bcb66baab7ffd9ca42bf1eda596618f9162b91249 + languageName: node + linkType: hard + +"yallist@npm:^3.0.2": + version: 3.1.1 + resolution: "yallist@npm:3.1.1" + checksum: 10c0/c66a5c46bc89af1625476f7f0f2ec3653c1a1791d2f9407cfb4c2ba812a1e1c9941416d71ba9719876530e3340a99925f697142989371b72d93b9ee628afd8c1 + languageName: node + linkType: hard + +"yallist@npm:^4.0.0": + version: 4.0.0 + resolution: "yallist@npm:4.0.0" + checksum: 10c0/2286b5e8dbfe22204ab66e2ef5cc9bbb1e55dfc873bbe0d568aa943eb255d131890dfd5bf243637273d31119b870f49c18fcde2c6ffbb7a7a092b870dc90625a + languageName: node + linkType: hard + +"yargs-parser@npm:^21.1.1": + version: 21.1.1 + resolution: "yargs-parser@npm:21.1.1" + checksum: 10c0/f84b5e48169479d2f402239c59f084cfd1c3acc197a05c59b98bab067452e6b3ea46d4dd8ba2985ba7b3d32a343d77df0debd6b343e5dae3da2aab2cdf5886b2 + languageName: node + linkType: hard + +"yargs@npm:^17.3.1": + version: 17.7.2 + resolution: "yargs@npm:17.7.2" + dependencies: + cliui: "npm:^8.0.1" + escalade: "npm:^3.1.1" + get-caller-file: "npm:^2.0.5" + require-directory: "npm:^2.1.1" + string-width: "npm:^4.2.3" + y18n: "npm:^5.0.5" + yargs-parser: "npm:^21.1.1" + checksum: 10c0/ccd7e723e61ad5965fffbb791366db689572b80cca80e0f96aad968dfff4156cd7cd1ad18607afe1046d8241e6fb2d6c08bf7fa7bfb5eaec818735d8feac8f05 + languageName: node + linkType: hard + +"yocto-queue@npm:^0.1.0": + version: 0.1.0 + resolution: "yocto-queue@npm:0.1.0" + checksum: 10c0/dceb44c28578b31641e13695d200d34ec4ab3966a5729814d5445b194933c096b7ced71494ce53a0e8820685d1d010df8b2422e5bf2cdea7e469d97ffbea306f + languageName: node + linkType: hard From 606fe7e40746d15e38a1ad8a0358a12eb87ed418 Mon Sep 17 00:00:00 2001 From: Mike Clark <mclarkgb@gmail.com> Date: Fri, 6 Dec 2024 20:23:33 +0000 Subject: [PATCH 50/55] update the lock --- packages/sync-server/package.json | 4 ++-- yarn.lock | 8 ++++---- 2 files changed, 6 insertions(+), 6 deletions(-) diff --git a/packages/sync-server/package.json b/packages/sync-server/package.json index db910718278..4f908b8422a 100644 --- a/packages/sync-server/package.json +++ b/packages/sync-server/package.json @@ -20,8 +20,8 @@ "health-check": "node src/scripts/health-check.js" }, "dependencies": { - "@actual-app/crdt": "2.1.0", - "@actual-app/web": "24.12.0", + "@actual-app/crdt": "*", + "@actual-app/web": "*", "bcrypt": "^5.1.1", "better-sqlite3": "^9.6.0", "body-parser": "^1.20.3", diff --git a/yarn.lock b/yarn.lock index 05f4e3875e9..6d6d880a08e 100644 --- a/yarn.lock +++ b/yarn.lock @@ -38,7 +38,7 @@ __metadata: languageName: unknown linkType: soft -"@actual-app/crdt@npm:*, @actual-app/crdt@npm:2.1.0, @actual-app/crdt@workspace:^, @actual-app/crdt@workspace:packages/crdt": +"@actual-app/crdt@npm:*, @actual-app/crdt@workspace:^, @actual-app/crdt@workspace:packages/crdt": version: 0.0.0-use.local resolution: "@actual-app/crdt@workspace:packages/crdt" dependencies: @@ -55,7 +55,7 @@ __metadata: languageName: unknown linkType: soft -"@actual-app/web@npm:24.11.0, @actual-app/web@workspace:packages/desktop-client": +"@actual-app/web@npm:*, @actual-app/web@workspace:packages/desktop-client": version: 0.0.0-use.local resolution: "@actual-app/web@workspace:packages/desktop-client" dependencies: @@ -7582,8 +7582,8 @@ __metadata: version: 0.0.0-use.local resolution: "actual-sync@workspace:packages/sync-server" dependencies: - "@actual-app/crdt": "npm:2.1.0" - "@actual-app/web": "npm:24.11.0" + "@actual-app/crdt": "npm:*" + "@actual-app/web": "npm:*" "@babel/preset-typescript": "npm:^7.20.2" "@types/bcrypt": "npm:^5.0.2" "@types/better-sqlite3": "npm:^7.6.7" From 626c72ac266186cca0a9b7ab44e610afc362b122 Mon Sep 17 00:00:00 2001 From: Mike Clark <mclarkgb@gmail.com> Date: Fri, 10 Jan 2025 19:18:23 +0000 Subject: [PATCH 51/55] fallout --- .../banks/1822_direkt_heladef1822.js | 16 + .../app-gocardless/banks/abanca_caglesmm.js | 24 ++ .../banks/american_express_aesudef1.js | 51 +++ .../banks/bancsabadell_bsabesbbb.js | 37 +++ .../banks/bank_of_ireland_b365_bofiie2d.js | 39 +++ .../banks/bankinter_bkbkesmm.js | 30 ++ .../app-gocardless/banks/bnp_be_gebabebb.js | 79 +++++ .../banks/danskebank_dabno22.js | 48 +++ .../app-gocardless/banks/easybank_bawaatww.js | 61 ++++ .../banks/entercard_swednokk.js | 42 +++ .../src/app-gocardless/banks/ing_ingbrobu.js | 70 ++++ .../src/app-gocardless/banks/ing_ingddeff.js | 52 +++ .../app-gocardless/banks/ing_pl_ingbplpw.js | 49 +++ .../app-gocardless/banks/isybank_itbbitmm.js | 18 ++ .../banks/mbank_retail_brexplpw.js | 45 +++ .../banks/nationwide_naiagb21.js | 44 +++ .../banks/norwegian_xx_norwnok1.js | 85 +++++ .../banks/sandboxfinance_sfin0000.js | 47 +++ .../app-gocardless/banks/seb_kort_bank_ab.js | 59 ++++ .../src/app-gocardless/banks/seb_privat.js | 47 +++ .../app-gocardless/banks/sparnord_spnodk22.js | 30 ++ .../banks/spk_karlsruhe_karsde66.js | 86 +++++ .../spk_marburg_biedenkopf_heladef1mar.js | 51 +++ .../banks/spk_worms_alzey_ried_malade51wor.js | 29 ++ .../banks/ssk_dusseldorf_dussdeddxxx.js | 35 ++ .../app-gocardless/banks/swedbank_habalv22.js | 51 +++ .../banks/tests/abanca_caglesmm.spec.js | 25 ++ .../tests/bancsabadell_bsabesbbb.spec.js | 57 ++++ .../banks/tests/easybank_bawaatww.spec.js | 54 ++++ .../banks/tests/ing_ingddeff.spec.js | 300 ++++++++++++++++++ .../banks/tests/ing_pl_ingbplpw.spec.js | 200 ++++++++++++ .../banks/tests/integration_bank.spec.js | 157 +++++++++ .../banks/tests/mbank_retail_brexplpw.spec.js | 169 ++++++++++ .../banks/tests/nationwide_naiagb21.spec.js | 105 ++++++ .../tests/sandboxfinance_sfin0000.spec.js | 131 ++++++++ ...spk_marburg_biedenkopf_heladef1mar.spec.js | 254 +++++++++++++++ .../banks/tests/swedbank_habalv22.spec.js | 62 ++++ .../sync-server/upcoming-release-notes/531.md | 6 + .../sync-server/upcoming-release-notes/533.md | 6 + .../sync-server/upcoming-release-notes/534.md | 6 + .../sync-server/upcoming-release-notes/535.md | 6 + .../sync-server/upcoming-release-notes/538.md | 6 + .../sync-server/upcoming-release-notes/539.md | 6 + .../sync-server/upcoming-release-notes/541.md | 6 + 44 files changed, 2781 insertions(+) create mode 100644 packages/sync-server/src/app-gocardless/banks/1822_direkt_heladef1822.js create mode 100644 packages/sync-server/src/app-gocardless/banks/abanca_caglesmm.js create mode 100644 packages/sync-server/src/app-gocardless/banks/american_express_aesudef1.js create mode 100644 packages/sync-server/src/app-gocardless/banks/bancsabadell_bsabesbbb.js create mode 100644 packages/sync-server/src/app-gocardless/banks/bank_of_ireland_b365_bofiie2d.js create mode 100644 packages/sync-server/src/app-gocardless/banks/bankinter_bkbkesmm.js create mode 100644 packages/sync-server/src/app-gocardless/banks/bnp_be_gebabebb.js create mode 100644 packages/sync-server/src/app-gocardless/banks/danskebank_dabno22.js create mode 100644 packages/sync-server/src/app-gocardless/banks/easybank_bawaatww.js create mode 100644 packages/sync-server/src/app-gocardless/banks/entercard_swednokk.js create mode 100644 packages/sync-server/src/app-gocardless/banks/ing_ingbrobu.js create mode 100644 packages/sync-server/src/app-gocardless/banks/ing_ingddeff.js create mode 100644 packages/sync-server/src/app-gocardless/banks/ing_pl_ingbplpw.js create mode 100644 packages/sync-server/src/app-gocardless/banks/isybank_itbbitmm.js create mode 100644 packages/sync-server/src/app-gocardless/banks/mbank_retail_brexplpw.js create mode 100644 packages/sync-server/src/app-gocardless/banks/nationwide_naiagb21.js create mode 100644 packages/sync-server/src/app-gocardless/banks/norwegian_xx_norwnok1.js create mode 100644 packages/sync-server/src/app-gocardless/banks/sandboxfinance_sfin0000.js create mode 100644 packages/sync-server/src/app-gocardless/banks/seb_kort_bank_ab.js create mode 100644 packages/sync-server/src/app-gocardless/banks/seb_privat.js create mode 100644 packages/sync-server/src/app-gocardless/banks/sparnord_spnodk22.js create mode 100644 packages/sync-server/src/app-gocardless/banks/spk_karlsruhe_karsde66.js create mode 100644 packages/sync-server/src/app-gocardless/banks/spk_marburg_biedenkopf_heladef1mar.js create mode 100644 packages/sync-server/src/app-gocardless/banks/spk_worms_alzey_ried_malade51wor.js create mode 100644 packages/sync-server/src/app-gocardless/banks/ssk_dusseldorf_dussdeddxxx.js create mode 100644 packages/sync-server/src/app-gocardless/banks/swedbank_habalv22.js create mode 100644 packages/sync-server/src/app-gocardless/banks/tests/abanca_caglesmm.spec.js create mode 100644 packages/sync-server/src/app-gocardless/banks/tests/bancsabadell_bsabesbbb.spec.js create mode 100644 packages/sync-server/src/app-gocardless/banks/tests/easybank_bawaatww.spec.js create mode 100644 packages/sync-server/src/app-gocardless/banks/tests/ing_ingddeff.spec.js create mode 100644 packages/sync-server/src/app-gocardless/banks/tests/ing_pl_ingbplpw.spec.js create mode 100644 packages/sync-server/src/app-gocardless/banks/tests/integration_bank.spec.js create mode 100644 packages/sync-server/src/app-gocardless/banks/tests/mbank_retail_brexplpw.spec.js create mode 100644 packages/sync-server/src/app-gocardless/banks/tests/nationwide_naiagb21.spec.js create mode 100644 packages/sync-server/src/app-gocardless/banks/tests/sandboxfinance_sfin0000.spec.js create mode 100644 packages/sync-server/src/app-gocardless/banks/tests/spk_marburg_biedenkopf_heladef1mar.spec.js create mode 100644 packages/sync-server/src/app-gocardless/banks/tests/swedbank_habalv22.spec.js create mode 100644 packages/sync-server/upcoming-release-notes/531.md create mode 100644 packages/sync-server/upcoming-release-notes/533.md create mode 100644 packages/sync-server/upcoming-release-notes/534.md create mode 100644 packages/sync-server/upcoming-release-notes/535.md create mode 100644 packages/sync-server/upcoming-release-notes/538.md create mode 100644 packages/sync-server/upcoming-release-notes/539.md create mode 100644 packages/sync-server/upcoming-release-notes/541.md diff --git a/packages/sync-server/src/app-gocardless/banks/1822_direkt_heladef1822.js b/packages/sync-server/src/app-gocardless/banks/1822_direkt_heladef1822.js new file mode 100644 index 00000000000..74a2393547b --- /dev/null +++ b/packages/sync-server/src/app-gocardless/banks/1822_direkt_heladef1822.js @@ -0,0 +1,16 @@ +import Fallback from './integration-bank.js'; + +/** @type {import('./bank.interface.js').IBank} */ +export default { + ...Fallback, + + institutionIds: ['DIREKT_HELADEF1822'], + + normalizeTransaction(transaction, booked) { + transaction.remittanceInformationUnstructured = + transaction.remittanceInformationUnstructured ?? + transaction.remittanceInformationStructured; + + return Fallback.normalizeTransaction(transaction, booked); + }, +}; diff --git a/packages/sync-server/src/app-gocardless/banks/abanca_caglesmm.js b/packages/sync-server/src/app-gocardless/banks/abanca_caglesmm.js new file mode 100644 index 00000000000..bd485331bbb --- /dev/null +++ b/packages/sync-server/src/app-gocardless/banks/abanca_caglesmm.js @@ -0,0 +1,24 @@ +import Fallback from './integration-bank.js'; + +import { formatPayeeName } from '../../util/payee-name.js'; + +/** @type {import('./bank.interface.js').IBank} */ +export default { + ...Fallback, + + institutionIds: ['ABANCA_CAGLESMM', 'ABANCA_CAGLPTPL'], + + accessValidForDays: 180, + + // Abanca transactions doesn't get the creditorName/debtorName properly + normalizeTransaction(transaction, _booked) { + transaction.creditorName = transaction.remittanceInformationStructured; + transaction.debtorName = transaction.remittanceInformationStructured; + + return { + ...transaction, + payeeName: formatPayeeName(transaction), + date: transaction.bookingDate || transaction.valueDate, + }; + }, +}; diff --git a/packages/sync-server/src/app-gocardless/banks/american_express_aesudef1.js b/packages/sync-server/src/app-gocardless/banks/american_express_aesudef1.js new file mode 100644 index 00000000000..2ae8168dfc9 --- /dev/null +++ b/packages/sync-server/src/app-gocardless/banks/american_express_aesudef1.js @@ -0,0 +1,51 @@ +import Fallback from './integration-bank.js'; + +import { amountToInteger } from '../utils.js'; +import { formatPayeeName } from '../../util/payee-name.js'; + +/** @type {import('./bank.interface.js').IBank} */ +export default { + ...Fallback, + + institutionIds: ['AMERICAN_EXPRESS_AESUDEF1'], + + accessValidForDays: 180, + + normalizeAccount(account) { + return { + ...Fallback.normalizeAccount(account), + // The `iban` field for these American Express cards is actually a masked + // version of the PAN. No IBAN is provided. + mask: account.iban.slice(-5), + iban: null, + name: [account.details, `(${account.iban.slice(-5)})`].join(' '), + official_name: account.details, + }; + }, + + normalizeTransaction(transaction, _booked) { + return { + ...transaction, + payeeName: formatPayeeName(transaction), + date: transaction.bookingDate, + }; + }, + + /** + * For AMERICAN_EXPRESS_AESUDEF1 we don't know what balance was + * after each transaction so we have to calculate it by getting + * current balance from the account and subtract all the transactions + * + * As a current balance we use the non-standard `information` balance type + * which is the only one provided for American Express. + */ + calculateStartingBalance(sortedTransactions = [], balances = []) { + const currentBalance = balances.find( + (balance) => 'information' === balance.balanceType.toString(), + ); + + return sortedTransactions.reduce((total, trans) => { + return total - amountToInteger(trans.transactionAmount.amount); + }, amountToInteger(currentBalance.balanceAmount.amount)); + }, +}; diff --git a/packages/sync-server/src/app-gocardless/banks/bancsabadell_bsabesbbb.js b/packages/sync-server/src/app-gocardless/banks/bancsabadell_bsabesbbb.js new file mode 100644 index 00000000000..832fb8953cf --- /dev/null +++ b/packages/sync-server/src/app-gocardless/banks/bancsabadell_bsabesbbb.js @@ -0,0 +1,37 @@ +import Fallback from './integration-bank.js'; + +import { formatPayeeName } from '../../util/payee-name.js'; + +/** @type {import('./bank.interface.js').IBank} */ +export default { + ...Fallback, + + institutionIds: ['BANCSABADELL_BSABESBB'], + + accessValidForDays: 180, + + // Sabadell transactions don't get the creditorName/debtorName properly + normalizeTransaction(transaction, _booked) { + const amount = transaction.transactionAmount.amount; + + // The amount is negative for outgoing transactions, positive for incoming transactions. + const isCreditorPayee = Number.parseFloat(amount) < 0; + + const payeeName = transaction.remittanceInformationUnstructuredArray + .join(' ') + .trim(); + + // The payee name is the creditor name for outgoing transactions and the debtor name for incoming transactions. + const creditorName = isCreditorPayee ? payeeName : null; + const debtorName = isCreditorPayee ? null : payeeName; + + transaction.creditorName = creditorName; + transaction.debtorName = debtorName; + + return { + ...transaction, + payeeName: formatPayeeName(transaction), + date: transaction.bookingDate || transaction.valueDate, + }; + }, +}; diff --git a/packages/sync-server/src/app-gocardless/banks/bank_of_ireland_b365_bofiie2d.js b/packages/sync-server/src/app-gocardless/banks/bank_of_ireland_b365_bofiie2d.js new file mode 100644 index 00000000000..6e677775d3b --- /dev/null +++ b/packages/sync-server/src/app-gocardless/banks/bank_of_ireland_b365_bofiie2d.js @@ -0,0 +1,39 @@ +import Fallback from './integration-bank.js'; + +/** @type {import('./bank.interface.js').IBank} */ +export default { + ...Fallback, + + institutionIds: ['BANK_OF_IRELAND_B365_BOFIIE2D'], + + accessValidForDays: 180, + + normalizeTransaction(transaction, booked) { + transaction.remittanceInformationUnstructured = fixupPayee( + transaction.remittanceInformationUnstructured, + ); + + return Fallback.normalizeTransaction(transaction, booked); + }, +}; + +function fixupPayee(/** @type {string} */ payee) { + let fixedPayee = payee; + + // remove all duplicate whitespace + fixedPayee = fixedPayee.replace(/\s+/g, ' ').trim(); + + // remove date prefix + fixedPayee = fixedPayee.replace(/^(POS)?(C)?[0-9]{1,2}\w{3}/, '').trim(); + + // remove direct debit postfix + fixedPayee = fixedPayee.replace(/sepa dd$/i, '').trim(); + + // remove bank transfer prefix + fixedPayee = fixedPayee.replace(/^365 online/i, '').trim(); + + // remove curve card prefix + fixedPayee = fixedPayee.replace(/^CRV\*/, '').trim(); + + return fixedPayee; +} diff --git a/packages/sync-server/src/app-gocardless/banks/bankinter_bkbkesmm.js b/packages/sync-server/src/app-gocardless/banks/bankinter_bkbkesmm.js new file mode 100644 index 00000000000..92672d6defb --- /dev/null +++ b/packages/sync-server/src/app-gocardless/banks/bankinter_bkbkesmm.js @@ -0,0 +1,30 @@ +import Fallback from './integration-bank.js'; + +import { formatPayeeName } from '../../util/payee-name.js'; + +/** @type {import('./bank.interface.js').IBank} */ +export default { + ...Fallback, + + institutionIds: ['BANKINTER_BKBKESMM'], + + accessValidForDays: 180, + + normalizeTransaction(transaction, _booked) { + transaction.remittanceInformationUnstructured = + transaction.remittanceInformationUnstructured + .replaceAll(/\/Txt\/(\w\|)?/gi, '') + .replaceAll(';', ' '); + + transaction.debtorName = transaction.debtorName?.replaceAll(';', ' '); + transaction.creditorName = + transaction.creditorName?.replaceAll(';', ' ') ?? + transaction.remittanceInformationUnstructured; + + return { + ...transaction, + payeeName: formatPayeeName(transaction), + date: transaction.bookingDate || transaction.valueDate, + }; + }, +}; diff --git a/packages/sync-server/src/app-gocardless/banks/bnp_be_gebabebb.js b/packages/sync-server/src/app-gocardless/banks/bnp_be_gebabebb.js new file mode 100644 index 00000000000..9af2fa674b9 --- /dev/null +++ b/packages/sync-server/src/app-gocardless/banks/bnp_be_gebabebb.js @@ -0,0 +1,79 @@ +import Fallback from './integration-bank.js'; + +import { formatPayeeName } from '../../util/payee-name.js'; + +/** @type {import('./bank.interface.js').IBank} */ +export default { + ...Fallback, + + institutionIds: [ + 'FINTRO_BE_GEBABEBB', + 'HELLO_BE_GEBABEBB', + 'BNP_BE_GEBABEBB', + ], + + accessValidForDays: 180, + + /** BNP_BE_GEBABEBB provides a lot of useful information via the 'additionalField' + * There does not seem to be a specification of this field, but the following information is contained in its subfields: + * - for pending transactions: the 'atmPosName' + * - for booked transactions: the 'narrative'. + * This narrative subfield is most useful as it contains information required to identify the transaction, + * especially in case of debit card or instant payment transactions. + * Do note that the narrative subfield ALSO contains the remittance information if any. + * The goal of the normalization is to place any relevant information of the additionalInformation + * field in the remittanceInformationUnstructuredArray field. + */ + normalizeTransaction(transaction, _booked) { + // Extract the creditor name to fill it in with information from the + // additionalInformation field in case it's not yet defined. + let creditorName = transaction.creditorName; + + if (transaction.additionalInformation) { + let additionalInformationObject = {}; + const additionalInfoRegex = /(, )?([^:]+): ((\[.*?\])|([^,]*))/g; + let matches = + transaction.additionalInformation.matchAll(additionalInfoRegex); + if (matches) { + let creditorNameFromNarrative; // Possible value for creditorName + for (let match of matches) { + let key = match[2].trim(); + let value = (match[4] || match[5]).trim(); + if (key === 'narrative') { + // Set narrativeName to the first element in the "narrative" array. + let first_value = value.matchAll(/'(.+?)'/g)?.next().value; + creditorNameFromNarrative = first_value + ? first_value[1].trim() + : undefined; + } + // Remove square brackets and single quotes and commas + value = value.replace(/[[\]',]/g, ''); + additionalInformationObject[key] = value; + } + // Keep existing unstructuredArray and add atmPosName and narrative + transaction.remittanceInformationUnstructuredArray = [ + transaction.remittanceInformationUnstructuredArray ?? '', + additionalInformationObject?.atmPosName ?? '', + additionalInformationObject?.narrative ?? '', + ].filter(Boolean); + + // If the creditor name doesn't exist in the original transactions, + // set it to the atmPosName or narrativeName if they exist; otherwise + // leave empty and let the default rules handle it. + creditorName = + creditorName ?? + additionalInformationObject?.atmPosName ?? + creditorNameFromNarrative ?? + null; + } + } + + transaction.creditorName = creditorName; + + return { + ...transaction, + payeeName: formatPayeeName(transaction), + date: transaction.valueDate || transaction.bookingDate, + }; + }, +}; diff --git a/packages/sync-server/src/app-gocardless/banks/danskebank_dabno22.js b/packages/sync-server/src/app-gocardless/banks/danskebank_dabno22.js new file mode 100644 index 00000000000..3d83ea95067 --- /dev/null +++ b/packages/sync-server/src/app-gocardless/banks/danskebank_dabno22.js @@ -0,0 +1,48 @@ +import Fallback from './integration-bank.js'; + +import { amountToInteger } from '../utils.js'; +import { formatPayeeName } from '../../util/payee-name.js'; + +/** @type {import('./bank.interface.js').IBank} */ +export default { + ...Fallback, + + institutionIds: ['DANSKEBANK_DABANO22'], + + accessValidForDays: 180, + + normalizeTransaction(transaction, _booked) { + /** + * Danske Bank appends the EndToEndID: NOTPROVIDED to + * remittanceInformationUnstructured, cluttering the data. + * + * We clean thais up by removing any instances of this string from all transactions. + * + */ + transaction.remittanceInformationUnstructured = + transaction.remittanceInformationUnstructured.replace( + '\nEndToEndID: NOTPROVIDED', + '', + ); + + /** + * The valueDate in transactions from Danske Bank is not the one expected, but rather the date + * the funds are expected to be paid back for credit accounts. + */ + return { + ...transaction, + payeeName: formatPayeeName(transaction), + date: transaction.bookingDate, + }; + }, + + calculateStartingBalance(sortedTransactions = [], balances = []) { + const currentBalance = balances.find( + (balance) => balance.balanceType === 'interimAvailable', + ); + + return sortedTransactions.reduce((total, trans) => { + return total - amountToInteger(trans.transactionAmount.amount); + }, amountToInteger(currentBalance.balanceAmount.amount)); + }, +}; diff --git a/packages/sync-server/src/app-gocardless/banks/easybank_bawaatww.js b/packages/sync-server/src/app-gocardless/banks/easybank_bawaatww.js new file mode 100644 index 00000000000..f93ab951f4a --- /dev/null +++ b/packages/sync-server/src/app-gocardless/banks/easybank_bawaatww.js @@ -0,0 +1,61 @@ +import Fallback from './integration-bank.js'; + +import { formatPayeeName } from '../../util/payee-name.js'; +import d from 'date-fns'; +import { title } from '../../util/title/index.js'; + +/** @type {import('./bank.interface.js').IBank} */ +export default { + ...Fallback, + + institutionIds: ['EASYBANK_BAWAATWW'], + + accessValidForDays: 179, + + // If date is same, sort by transactionId + sortTransactions: (transactions = []) => + transactions.sort((a, b) => { + const diff = + +new Date(b.valueDate || b.bookingDate) - + +new Date(a.valueDate || a.bookingDate); + if (diff != 0) return diff; + return parseInt(b.transactionId) - parseInt(a.transactionId); + }), + + normalizeTransaction(transaction, _booked) { + const date = transaction.bookingDate || transaction.valueDate; + + // If we couldn't find a valid date field we filter out this transaction + // and hope that we will import it again once the bank has processed the + // transaction further. + if (!date) { + return null; + } + + let payeeName = formatPayeeName(transaction); + if (!payeeName) payeeName = extractPayeeName(transaction); + + return { + ...transaction, + payeeName: payeeName, + date: d.format(d.parseISO(date), 'yyyy-MM-dd'), + }; + }, +}; + +/** + * Extracts the payee name from the remittanceInformationStructured + * @param {import('../gocardless-node.types.js').Transaction} transaction + */ +function extractPayeeName(transaction) { + const structured = transaction.remittanceInformationStructured; + // The payee name is betweeen the transaction timestamp (11.07. 11:36) and the location, that starts with \\ + const regex = /\d{2}\.\d{2}\. \d{2}:\d{2}(.*)\\\\/; + const matches = structured.match(regex); + if (matches && matches.length > 1 && matches[1]) { + return title(matches[1]); + } else { + // As a fallback if still no payee is found, the whole information is used + return structured; + } +} diff --git a/packages/sync-server/src/app-gocardless/banks/entercard_swednokk.js b/packages/sync-server/src/app-gocardless/banks/entercard_swednokk.js new file mode 100644 index 00000000000..daafbfb89c5 --- /dev/null +++ b/packages/sync-server/src/app-gocardless/banks/entercard_swednokk.js @@ -0,0 +1,42 @@ +import * as d from 'date-fns'; + +import Fallback from './integration-bank.js'; + +import { amountToInteger } from '../utils.js'; +import { formatPayeeName } from '../../util/payee-name.js'; + +/** @type {import('./bank.interface.js').IBank} */ +export default { + ...Fallback, + + institutionIds: ['ENTERCARD_SWEDNOKK'], + + accessValidForDays: 180, + + normalizeTransaction(transaction, _booked) { + // GoCardless's Entercard integration returns forex transactions with the + // foreign amount in `transactionAmount`, but at least the amount actually + // billed to the account is now available in + // `remittanceInformationUnstructured`. + const remittanceInformationUnstructured = + transaction.remittanceInformationUnstructured; + if (remittanceInformationUnstructured.startsWith('billingAmount: ')) { + transaction.transactionAmount = { + amount: remittanceInformationUnstructured.substring(15), + currency: 'SEK', + }; + } + + return { + ...transaction, + payeeName: formatPayeeName(transaction), + date: d.format(d.parseISO(transaction.valueDate), 'yyyy-MM-dd'), + }; + }, + + calculateStartingBalance(sortedTransactions = [], balances = []) { + return sortedTransactions.reduce((total, trans) => { + return total - amountToInteger(trans.transactionAmount.amount); + }, amountToInteger(balances[0]?.balanceAmount?.amount || 0)); + }, +}; diff --git a/packages/sync-server/src/app-gocardless/banks/ing_ingbrobu.js b/packages/sync-server/src/app-gocardless/banks/ing_ingbrobu.js new file mode 100644 index 00000000000..4f90da51c9d --- /dev/null +++ b/packages/sync-server/src/app-gocardless/banks/ing_ingbrobu.js @@ -0,0 +1,70 @@ +import Fallback from './integration-bank.js'; + +/** @type {import('./bank.interface.js').IBank} */ +export default { + ...Fallback, + + institutionIds: ['ING_INGBROBU'], + + accessValidForDays: 180, + + normalizeTransaction(transaction, booked) { + //Merchant transactions all have the same transactionId of 'NOTPROVIDED'. + //For booked transactions, this can be set to the internalTransactionId + //For pending transactions, this needs to be removed for them to show up in Actual + + //For deduplication to work better, payeeName needs to be standardized + //and converted from a pending transaction form ("payeeName":"Card no: xxxxxxxxxxxx1111"') to a booked transaction form ("payeeName":"Card no: Xxxx Xxxx Xxxx 1111") + if (transaction.transactionId === 'NOTPROVIDED') { + //Some corner case transactions only have the `proprietaryBankTransactionCode` field, this need to be copied to `remittanceInformationUnstructured` + if ( + transaction.proprietaryBankTransactionCode && + !transaction.remittanceInformationUnstructured + ) { + transaction.remittanceInformationUnstructured = + transaction.proprietaryBankTransactionCode; + } + + if (booked) { + transaction.transactionId = transaction.internalTransactionId; + if ( + transaction.remittanceInformationUnstructured && + transaction.remittanceInformationUnstructured + .toLowerCase() + .includes('card no:') + ) { + transaction.creditorName = + transaction.remittanceInformationUnstructured.split(',')[0]; + //Catch all case for other types of payees + } else { + transaction.creditorName = + transaction.remittanceInformationUnstructured; + } + } else { + transaction.transactionId = null; + + if ( + transaction.remittanceInformationUnstructured && + transaction.remittanceInformationUnstructured + .toLowerCase() + .includes('card no:') + ) { + transaction.creditorName = + transaction.remittanceInformationUnstructured.replace( + /x{4}/g, + 'Xxxx ', + ); + //Catch all case for other types of payees + } else { + transaction.creditorName = + transaction.remittanceInformationUnstructured; + } + //Remove remittanceInformationUnstructured from pending transactions, so the `notes` field remains empty (there is no merchant information) + //Once booked, the right `notes` (containing the merchant) will be populated + transaction.remittanceInformationUnstructured = null; + } + } + + return Fallback.normalizeTransaction(transaction, booked); + }, +}; diff --git a/packages/sync-server/src/app-gocardless/banks/ing_ingddeff.js b/packages/sync-server/src/app-gocardless/banks/ing_ingddeff.js new file mode 100644 index 00000000000..3eabb990845 --- /dev/null +++ b/packages/sync-server/src/app-gocardless/banks/ing_ingddeff.js @@ -0,0 +1,52 @@ +import Fallback from './integration-bank.js'; + +import { amountToInteger } from '../utils.js'; +import { formatPayeeName } from '../../util/payee-name.js'; + +/** @type {import('./bank.interface.js').IBank} */ +export default { + ...Fallback, + + institutionIds: ['ING_INGDDEFF'], + + accessValidForDays: 180, + + normalizeTransaction(transaction, _booked) { + const remittanceInformationMatch = /remittanceinformation:(.*)$/.exec( + transaction.remittanceInformationUnstructured, + ); + + transaction.remittanceInformationUnstructured = remittanceInformationMatch + ? remittanceInformationMatch[1] + : transaction.remittanceInformationUnstructured; + + return { + ...transaction, + payeeName: formatPayeeName(transaction), + date: transaction.bookingDate || transaction.valueDate, + }; + }, + + sortTransactions(transactions = []) { + return transactions.sort((a, b) => { + const diff = + +new Date(b.valueDate || b.bookingDate) - + +new Date(a.valueDate || a.bookingDate); + if (diff) return diff; + const idA = parseInt(a.transactionId); + const idB = parseInt(b.transactionId); + if (!isNaN(idA) && !isNaN(idB)) return idB - idA; + return 0; + }); + }, + + calculateStartingBalance(sortedTransactions = [], balances = []) { + const currentBalance = balances.find( + (balance) => 'interimBooked' === balance.balanceType, + ); + + return sortedTransactions.reduce((total, trans) => { + return total - amountToInteger(trans.transactionAmount.amount); + }, amountToInteger(currentBalance.balanceAmount.amount)); + }, +}; diff --git a/packages/sync-server/src/app-gocardless/banks/ing_pl_ingbplpw.js b/packages/sync-server/src/app-gocardless/banks/ing_pl_ingbplpw.js new file mode 100644 index 00000000000..248068cb84d --- /dev/null +++ b/packages/sync-server/src/app-gocardless/banks/ing_pl_ingbplpw.js @@ -0,0 +1,49 @@ +import Fallback from './integration-bank.js'; + +import { amountToInteger } from '../utils.js'; +import { formatPayeeName } from '../../util/payee-name.js'; + +/** @type {import('./bank.interface.js').IBank} */ +export default { + ...Fallback, + + institutionIds: ['ING_PL_INGBPLPW'], + + accessValidForDays: 180, + + normalizeTransaction(transaction, _booked) { + return { + ...transaction, + payeeName: formatPayeeName(transaction), + date: transaction.valueDate ?? transaction.bookingDate, + }; + }, + + sortTransactions(transactions = []) { + return transactions.sort((a, b) => { + return ( + Number(b.transactionId.substr(2)) - Number(a.transactionId.substr(2)) + ); + }); + }, + + calculateStartingBalance(sortedTransactions = [], balances = []) { + if (sortedTransactions.length) { + const oldestTransaction = + sortedTransactions[sortedTransactions.length - 1]; + const oldestKnownBalance = amountToInteger( + oldestTransaction.balanceAfterTransaction.balanceAmount.amount, + ); + const oldestTransactionAmount = amountToInteger( + oldestTransaction.transactionAmount.amount, + ); + + return oldestKnownBalance - oldestTransactionAmount; + } else { + return amountToInteger( + balances.find((balance) => 'interimBooked' === balance.balanceType) + .balanceAmount.amount, + ); + } + }, +}; diff --git a/packages/sync-server/src/app-gocardless/banks/isybank_itbbitmm.js b/packages/sync-server/src/app-gocardless/banks/isybank_itbbitmm.js new file mode 100644 index 00000000000..cc7e78ac399 --- /dev/null +++ b/packages/sync-server/src/app-gocardless/banks/isybank_itbbitmm.js @@ -0,0 +1,18 @@ +import Fallback from './integration-bank.js'; + +/** @type {import('./bank.interface.js').IBank} */ +export default { + ...Fallback, + + institutionIds: ['ISYBANK_ITBBITMM'], + + accessValidForDays: 180, + + // It has been reported that valueDate is more accurate than booking date + // when it is provided + normalizeTransaction(transaction, booked) { + transaction.bookingDate = transaction.valueDate ?? transaction.bookingDate; + + return Fallback.normalizeTransaction(transaction, booked); + }, +}; diff --git a/packages/sync-server/src/app-gocardless/banks/mbank_retail_brexplpw.js b/packages/sync-server/src/app-gocardless/banks/mbank_retail_brexplpw.js new file mode 100644 index 00000000000..2f6e3c943bf --- /dev/null +++ b/packages/sync-server/src/app-gocardless/banks/mbank_retail_brexplpw.js @@ -0,0 +1,45 @@ +import Fallback from './integration-bank.js'; + +import { amountToInteger } from '../utils.js'; +import { formatPayeeName } from '../../util/payee-name.js'; + +/** @type {import('./bank.interface.js').IBank} */ +export default { + ...Fallback, + + institutionIds: ['MBANK_RETAIL_BREXPLPW'], + + accessValidForDays: 179, + + normalizeTransaction(transaction, _booked) { + return { + ...transaction, + payeeName: formatPayeeName(transaction), + date: transaction.bookingDate || transaction.valueDate, + }; + }, + + sortTransactions(transactions = []) { + return transactions.sort( + (a, b) => Number(b.transactionId) - Number(a.transactionId), + ); + }, + + /** + * For MBANK_RETAIL_BREXPLPW we don't know what balance was + * after each transaction so we have to calculate it by getting + * current balance from the account and subtract all the transactions + * + * As a current balance we use `interimBooked` balance type because + * it includes transaction placed during current day + */ + calculateStartingBalance(sortedTransactions = [], balances = []) { + const currentBalance = balances.find( + (balance) => 'interimBooked' === balance.balanceType, + ); + + return sortedTransactions.reduce((total, trans) => { + return total - amountToInteger(trans.transactionAmount.amount); + }, amountToInteger(currentBalance.balanceAmount.amount)); + }, +}; diff --git a/packages/sync-server/src/app-gocardless/banks/nationwide_naiagb21.js b/packages/sync-server/src/app-gocardless/banks/nationwide_naiagb21.js new file mode 100644 index 00000000000..fdcb933527d --- /dev/null +++ b/packages/sync-server/src/app-gocardless/banks/nationwide_naiagb21.js @@ -0,0 +1,44 @@ +import Fallback from './integration-bank.js'; + +/** @type {import('./bank.interface.js').IBank} */ +export default { + ...Fallback, + + institutionIds: ['NATIONWIDE_NAIAGB21'], + + normalizeTransaction(transaction, booked) { + // Nationwide can sometimes return pending transactions with a date + // representing the latest a transaction could be booked. This stops + // actual's deduplication logic from working as it only checks 7 days + // ahead/behind and the transactionID from Nationwide changes when a + // transaction is booked + if (!booked) { + const useDate = new Date( + Math.min( + new Date(transaction.bookingDate).getTime(), + new Date().getTime(), + ), + ); + transaction.bookingDate = useDate.toISOString().slice(0, 10); + } + + // Nationwide also occasionally returns erroneous transaction_ids + // that are malformed and can even change after import. This will ignore + // these ids and unset them. When a correct ID is returned then it will + // update via the deduplication logic + const debitCreditRegex = /^00(DEB|CRED)IT.+$/; + const validLengths = [ + 40, // Nationwide credit cards + 32, // Nationwide current accounts + ]; + + if ( + transaction.transactionId?.match(debitCreditRegex) || + !validLengths.includes(transaction.transactionId?.length) + ) { + transaction.transactionId = null; + } + + return Fallback.normalizeTransaction(transaction, booked); + }, +}; diff --git a/packages/sync-server/src/app-gocardless/banks/norwegian_xx_norwnok1.js b/packages/sync-server/src/app-gocardless/banks/norwegian_xx_norwnok1.js new file mode 100644 index 00000000000..0a00a34e988 --- /dev/null +++ b/packages/sync-server/src/app-gocardless/banks/norwegian_xx_norwnok1.js @@ -0,0 +1,85 @@ +import Fallback from './integration-bank.js'; + +import { amountToInteger } from '../utils.js'; +import { formatPayeeName } from '../../util/payee-name.js'; + +/** @type {import('./bank.interface.js').IBank} */ +export default { + ...Fallback, + + institutionIds: [ + 'NORWEGIAN_NO_NORWNOK1', + 'NORWEGIAN_SE_NORWNOK1', + 'NORWEGIAN_DE_NORWNOK1', + 'NORWEGIAN_DK_NORWNOK1', + 'NORWEGIAN_ES_NORWNOK1', + 'NORWEGIAN_FI_NORWNOK1', + ], + + accessValidForDays: 180, + + normalizeTransaction(transaction, booked) { + if (booked) { + return { + ...transaction, + payeeName: formatPayeeName(transaction), + date: transaction.bookingDate, + }; + } + + /** + * For pending transactions there are two possibilities: + * + * - Either a `valueDate` was set, in which case it corresponds to when the + * transaction actually occurred, or + * - There is no date field, in which case we try to parse the correct date + * out of the `remittanceInformationStructured` field. + * + * If neither case succeeds then we return `null` causing this transaction + * to be filtered out for now, and hopefully we'll be able to import it + * once the bank has processed it further. + */ + if (transaction.valueDate !== undefined) { + return { + ...transaction, + payeeName: formatPayeeName(transaction), + date: transaction.valueDate, + }; + } + + if (transaction.remittanceInformationStructured) { + const remittanceInfoRegex = / (\d{4}-\d{2}-\d{2}) /; + const matches = + transaction.remittanceInformationStructured.match(remittanceInfoRegex); + if (matches) { + transaction.valueDate = matches[1]; + return { + ...transaction, + payeeName: formatPayeeName(transaction), + date: matches[1], + }; + } + } + + return null; + }, + + /** + * For NORWEGIAN_XX_NORWNOK1 we don't know what balance was + * after each transaction so we have to calculate it by getting + * current balance from the account and subtract all the transactions + * + * As a current balance we use `expected` balance type because it + * corresponds to the current running balance, whereas `interimAvailable` + * holds the remaining credit limit. + */ + calculateStartingBalance(sortedTransactions = [], balances = []) { + const currentBalance = balances.find( + (balance) => 'expected' === balance.balanceType, + ); + + return sortedTransactions.reduce((total, trans) => { + return total - amountToInteger(trans.transactionAmount.amount); + }, amountToInteger(currentBalance.balanceAmount.amount)); + }, +}; diff --git a/packages/sync-server/src/app-gocardless/banks/sandboxfinance_sfin0000.js b/packages/sync-server/src/app-gocardless/banks/sandboxfinance_sfin0000.js new file mode 100644 index 00000000000..0debc603062 --- /dev/null +++ b/packages/sync-server/src/app-gocardless/banks/sandboxfinance_sfin0000.js @@ -0,0 +1,47 @@ +import Fallback from './integration-bank.js'; + +import { amountToInteger } from '../utils.js'; +import { formatPayeeName } from '../../util/payee-name.js'; + +/** @type {import('./bank.interface.js').IBank} */ +export default { + ...Fallback, + + institutionIds: ['SANDBOXFINANCE_SFIN0000'], + + accessValidForDays: 180, + + /** + * Following the GoCardless documentation[0] we should prefer `bookingDate` + * here, though some of their bank integrations uses the date field + * differently from what's described in their documentation and so it's + * sometimes necessary to use `valueDate` instead. + * + * [0]: https://nordigen.zendesk.com/hc/en-gb/articles/7899367372829-valueDate-and-bookingDate-for-transactions + */ + normalizeTransaction(transaction, _booked) { + return { + ...transaction, + payeeName: formatPayeeName(transaction), + date: transaction.bookingDate || transaction.valueDate, + }; + }, + + /** + * For SANDBOXFINANCE_SFIN0000 we don't know what balance was + * after each transaction so we have to calculate it by getting + * current balance from the account and subtract all the transactions + * + * As a current balance we use `interimBooked` balance type because + * it includes transaction placed during current day + */ + calculateStartingBalance(sortedTransactions = [], balances = []) { + const currentBalance = balances.find( + (balance) => 'interimAvailable' === balance.balanceType, + ); + + return sortedTransactions.reduce((total, trans) => { + return total - amountToInteger(trans.transactionAmount.amount); + }, amountToInteger(currentBalance.balanceAmount.amount)); + }, +}; diff --git a/packages/sync-server/src/app-gocardless/banks/seb_kort_bank_ab.js b/packages/sync-server/src/app-gocardless/banks/seb_kort_bank_ab.js new file mode 100644 index 00000000000..3b465641f21 --- /dev/null +++ b/packages/sync-server/src/app-gocardless/banks/seb_kort_bank_ab.js @@ -0,0 +1,59 @@ +import Fallback from './integration-bank.js'; + +import { amountToInteger } from '../utils.js'; +import { formatPayeeName } from '../../util/payee-name.js'; + +/** @type {import('./bank.interface.js').IBank} */ +export default { + ...Fallback, + + institutionIds: [ + 'SEB_KORT_AB_NO_SKHSFI21', + 'SEB_KORT_AB_SE_SKHSFI21', + 'SEB_CARD_ESSESESS', + ], + + accessValidForDays: 180, + + /** + * Sign of transaction amount needs to be flipped for SEB credit cards + */ + normalizeTransaction(transaction, _booked) { + // Creditor name is stored in additionInformation for SEB + transaction.creditorName = transaction.additionalInformation; + transaction.transactionAmount = { + // Flip transaction amount sign + amount: (-parseFloat(transaction.transactionAmount.amount)).toString(), + currency: transaction.transactionAmount.currency, + }; + + return { + ...transaction, + payeeName: formatPayeeName(transaction), + date: transaction.valueDate, + }; + }, + + /** + * For SEB_KORT_AB_NO_SKHSFI21 and SEB_KORT_AB_SE_SKHSFI21 we don't know what balance was + * after each transaction so we have to calculate it by getting + * current balance from the account and subtract all the transactions + * + * As a current balance we use `expected` and `nonInvoiced` balance types because it + * corresponds to the current running balance, whereas `interimAvailable` + * holds the remaining credit limit. + */ + calculateStartingBalance(sortedTransactions = [], balances = []) { + const currentBalance = balances.find( + (balance) => 'expected' === balance.balanceType, + ); + + const nonInvoiced = balances.find( + (balance) => 'nonInvoiced' === balance.balanceType, + ); + + return sortedTransactions.reduce((total, trans) => { + return total - amountToInteger(trans.transactionAmount.amount); + }, -amountToInteger(currentBalance.balanceAmount.amount) + amountToInteger(nonInvoiced.balanceAmount.amount)); + }, +}; diff --git a/packages/sync-server/src/app-gocardless/banks/seb_privat.js b/packages/sync-server/src/app-gocardless/banks/seb_privat.js new file mode 100644 index 00000000000..0ff079ebf80 --- /dev/null +++ b/packages/sync-server/src/app-gocardless/banks/seb_privat.js @@ -0,0 +1,47 @@ +import Fallback from './integration-bank.js'; + +import * as d from 'date-fns'; +import { amountToInteger } from '../utils.js'; +import { formatPayeeName } from '../../util/payee-name.js'; + +/** @type {import('./bank.interface.js').IBank} */ +export default { + ...Fallback, + + institutionIds: ['SEB_ESSESESS_PRIVATE'], + + accessValidForDays: 180, + + normalizeTransaction(transaction, _booked) { + const date = + transaction.bookingDate || + transaction.bookingDateTime || + transaction.valueDate || + transaction.valueDateTime; + // If we couldn't find a valid date field we filter out this transaction + // and hope that we will import it again once the bank has processed the + // transaction further. + if (!date) { + return null; + } + + // Creditor name is stored in additionInformation for SEB + transaction.creditorName = transaction.additionalInformation; + + return { + ...transaction, + payeeName: formatPayeeName(transaction), + date: d.format(d.parseISO(date), 'yyyy-MM-dd'), + }; + }, + + calculateStartingBalance(sortedTransactions = [], balances = []) { + const currentBalance = balances.find( + (balance) => 'interimBooked' === balance.balanceType, + ); + + return sortedTransactions.reduce((total, trans) => { + return total - amountToInteger(trans.transactionAmount.amount); + }, amountToInteger(currentBalance.balanceAmount.amount)); + }, +}; diff --git a/packages/sync-server/src/app-gocardless/banks/sparnord_spnodk22.js b/packages/sync-server/src/app-gocardless/banks/sparnord_spnodk22.js new file mode 100644 index 00000000000..37980fafbb4 --- /dev/null +++ b/packages/sync-server/src/app-gocardless/banks/sparnord_spnodk22.js @@ -0,0 +1,30 @@ +import Fallback from './integration-bank.js'; + +import { formatPayeeName } from '../../util/payee-name.js'; + +/** @type {import('./bank.interface.js').IBank} */ +export default { + ...Fallback, + + institutionIds: [ + 'SPARNORD_SPNODK22', + 'LAGERNES_BANK_LAPNDKK1', + 'ANDELSKASSEN_FALLESKASSEN_FAELDKK1', + ], + + accessValidForDays: 180, + + /** + * Banks on the BEC backend only give information regarding the transaction in additionalInformation + */ + normalizeTransaction(transaction, _booked) { + transaction.remittanceInformationUnstructured = + transaction.additionalInformation; + + return { + ...transaction, + payeeName: formatPayeeName(transaction), + date: transaction.bookingDate, + }; + }, +}; diff --git a/packages/sync-server/src/app-gocardless/banks/spk_karlsruhe_karsde66.js b/packages/sync-server/src/app-gocardless/banks/spk_karlsruhe_karsde66.js new file mode 100644 index 00000000000..9b8e6a2153e --- /dev/null +++ b/packages/sync-server/src/app-gocardless/banks/spk_karlsruhe_karsde66.js @@ -0,0 +1,86 @@ +import Fallback from './integration-bank.js'; + +import { amountToInteger } from '../utils.js'; +import { formatPayeeName } from '../../util/payee-name.js'; + +/** @type {import('./bank.interface.js').IBank} */ +export default { + ...Fallback, + + institutionIds: ['SPK_KARLSRUHE_KARSDE66XXX'], + + accessValidForDays: 180, + + /** + * Following the GoCardless documentation[0] we should prefer `bookingDate` + * here, though some of their bank integrations uses the date field + * differently from what's described in their documentation and so it's + * sometimes necessary to use `valueDate` instead. + * + * [0]: https://nordigen.zendesk.com/hc/en-gb/articles/7899367372829-valueDate-and-bookingDate-for-transactions + */ + normalizeTransaction(transaction, _booked) { + const date = + transaction.bookingDate || + transaction.bookingDateTime || + transaction.valueDate || + transaction.valueDateTime; + + // If we couldn't find a valid date field we filter out this transaction + // and hope that we will import it again once the bank has processed the + // transaction further. + if (!date) { + return null; + } + + let remittanceInformationUnstructured; + + if (transaction.remittanceInformationUnstructured) { + remittanceInformationUnstructured = + transaction.remittanceInformationUnstructured; + } else if (transaction.remittanceInformationStructured) { + remittanceInformationUnstructured = + transaction.remittanceInformationStructured; + } else if (transaction.remittanceInformationStructuredArray?.length > 0) { + remittanceInformationUnstructured = + transaction.remittanceInformationStructuredArray?.join(' '); + } + + if (transaction.additionalInformation) + remittanceInformationUnstructured += + ' ' + transaction.additionalInformation; + + const usefulCreditorName = + transaction.ultimateCreditor || + transaction.creditorName || + transaction.debtorName; + + transaction.creditorName = usefulCreditorName; + transaction.remittanceInformationUnstructured = + remittanceInformationUnstructured; + + return { + ...transaction, + payeeName: formatPayeeName(transaction), + date: transaction.bookingDate || transaction.valueDate, + }; + }, + + /** + * For SANDBOXFINANCE_SFIN0000 we don't know what balance was + * after each transaction so we have to calculate it by getting + * current balance from the account and subtract all the transactions + * + * As a current balance we use `interimBooked` balance type because + * it includes transaction placed during current day + */ + calculateStartingBalance(sortedTransactions = [], balances = []) { + const currentBalance = balances.find( + (balance) => 'interimAvailable' === balance.balanceType, + ); + + return sortedTransactions.reduce((total, trans) => { + return total - amountToInteger(trans.transactionAmount.amount); + }, amountToInteger(currentBalance.balanceAmount.amount)); + }, +}; diff --git a/packages/sync-server/src/app-gocardless/banks/spk_marburg_biedenkopf_heladef1mar.js b/packages/sync-server/src/app-gocardless/banks/spk_marburg_biedenkopf_heladef1mar.js new file mode 100644 index 00000000000..3491b131bbb --- /dev/null +++ b/packages/sync-server/src/app-gocardless/banks/spk_marburg_biedenkopf_heladef1mar.js @@ -0,0 +1,51 @@ +import d from 'date-fns'; + +import Fallback from './integration-bank.js'; + +import { formatPayeeName } from '../../util/payee-name.js'; + +/** @type {import('./bank.interface.js').IBank} */ +export default { + ...Fallback, + + institutionIds: ['SPK_MARBURG_BIEDENKOPF_HELADEF1MAR'], + + accessValidForDays: 180, + + normalizeTransaction(transaction, _booked) { + const date = + transaction.bookingDate || + transaction.bookingDateTime || + transaction.valueDate || + transaction.valueDateTime; + + // If we couldn't find a valid date field we filter out this transaction + // and hope that we will import it again once the bank has processed the + // transaction further. + if (!date) { + return null; + } + + let remittanceInformationUnstructured; + + if (transaction.remittanceInformationUnstructured) { + remittanceInformationUnstructured = + transaction.remittanceInformationUnstructured; + } else if (transaction.remittanceInformationStructured) { + remittanceInformationUnstructured = + transaction.remittanceInformationStructured; + } else if (transaction.remittanceInformationStructuredArray?.length > 0) { + remittanceInformationUnstructured = + transaction.remittanceInformationStructuredArray?.join(' '); + } + + transaction.remittanceInformationUnstructured = + remittanceInformationUnstructured; + + return { + ...transaction, + payeeName: formatPayeeName(transaction), + date: d.format(d.parseISO(date), 'yyyy-MM-dd'), + }; + }, +}; diff --git a/packages/sync-server/src/app-gocardless/banks/spk_worms_alzey_ried_malade51wor.js b/packages/sync-server/src/app-gocardless/banks/spk_worms_alzey_ried_malade51wor.js new file mode 100644 index 00000000000..0d38a0244e1 --- /dev/null +++ b/packages/sync-server/src/app-gocardless/banks/spk_worms_alzey_ried_malade51wor.js @@ -0,0 +1,29 @@ +import Fallback from './integration-bank.js'; + +import { formatPayeeName } from '../../util/payee-name.js'; + +/** @type {import('./bank.interface.js').IBank} */ +export default { + ...Fallback, + + institutionIds: ['SPK_WORMS_ALZEY_RIED_MALADE51WOR'], + + accessValidForDays: 180, + + normalizeTransaction(transaction, _booked) { + const date = transaction.bookingDate || transaction.valueDate; + if (!date) { + return null; + } + + transaction.remittanceInformationUnstructured = + transaction.remittanceInformationUnstructured ?? + transaction.remittanceInformationStructured ?? + transaction.remittanceInformationStructuredArray?.join(' '); + return { + ...transaction, + payeeName: formatPayeeName(transaction), + date: transaction.bookingDate || transaction.valueDate, + }; + }, +}; diff --git a/packages/sync-server/src/app-gocardless/banks/ssk_dusseldorf_dussdeddxxx.js b/packages/sync-server/src/app-gocardless/banks/ssk_dusseldorf_dussdeddxxx.js new file mode 100644 index 00000000000..2e320d366af --- /dev/null +++ b/packages/sync-server/src/app-gocardless/banks/ssk_dusseldorf_dussdeddxxx.js @@ -0,0 +1,35 @@ +import Fallback from './integration-bank.js'; + +/** @type {import('./bank.interface.js').IBank} */ +export default { + ...Fallback, + + institutionIds: ['SSK_DUSSELDORF_DUSSDEDDXXX'], + + accessValidForDays: 180, + + normalizeTransaction(transaction, _booked) { + // Prioritize unstructured information, falling back to structured formats + let remittanceInformationUnstructured = + transaction.remittanceInformationUnstructured ?? + transaction.remittanceInformationStructured ?? + transaction.remittanceInformationStructuredArray?.join(' '); + + if (transaction.additionalInformation) + remittanceInformationUnstructured = + (remittanceInformationUnstructured ?? '') + + ' ' + + transaction.additionalInformation; + + const usefulCreditorName = + transaction.ultimateCreditor || + transaction.creditorName || + transaction.debtorName; + + transaction.creditorName = usefulCreditorName; + transaction.remittanceInformationUnstructured = + remittanceInformationUnstructured; + + return Fallback.normalizeTransaction(transaction, _booked); + }, +}; diff --git a/packages/sync-server/src/app-gocardless/banks/swedbank_habalv22.js b/packages/sync-server/src/app-gocardless/banks/swedbank_habalv22.js new file mode 100644 index 00000000000..8bea1360a7a --- /dev/null +++ b/packages/sync-server/src/app-gocardless/banks/swedbank_habalv22.js @@ -0,0 +1,51 @@ +import d from 'date-fns'; + +import Fallback from './integration-bank.js'; + +/** @type {import('./bank.interface.js').IBank} */ +export default { + ...Fallback, + + institutionIds: ['SWEDBANK_HABALV22'], + + accessValidForDays: 180, + + /** + * The actual transaction date for card transactions is only available in the remittanceInformationUnstructured field when the transaction is booked. + */ + normalizeTransaction(transaction, booked) { + const isCardTransaction = + transaction.remittanceInformationUnstructured?.startsWith('PIRKUMS'); + + if (isCardTransaction) { + if (!booked && !transaction.creditorName) { + const creditorNameMatch = + transaction.remittanceInformationUnstructured?.match( + /PIRKUMS [\d*]+ \d{2}\.\d{2}\.\d{2} \d{2}:\d{2} [\d.]+ \w{3} \(\d+\) (.+)/, + ); + + if (creditorNameMatch) { + transaction = { + ...transaction, + creditorName: creditorNameMatch[1], + }; + } + } + + const dateMatch = transaction.remittanceInformationUnstructured?.match( + /PIRKUMS [\d*]+ (\d{2}\.\d{2}\.\d{4})/, + ); + + if (dateMatch) { + const extractedDate = d.parse(dateMatch[1], 'dd.MM.yyyy', new Date()); + + transaction = { + ...transaction, + bookingDate: d.format(extractedDate, 'yyyy-MM-dd'), + }; + } + } + + return Fallback.normalizeTransaction(transaction, booked); + }, +}; diff --git a/packages/sync-server/src/app-gocardless/banks/tests/abanca_caglesmm.spec.js b/packages/sync-server/src/app-gocardless/banks/tests/abanca_caglesmm.spec.js new file mode 100644 index 00000000000..3ceb0817c31 --- /dev/null +++ b/packages/sync-server/src/app-gocardless/banks/tests/abanca_caglesmm.spec.js @@ -0,0 +1,25 @@ +import Abanca from '../abanca_caglesmm.js'; +import { mockTransactionAmount } from '../../services/tests/fixtures.js'; + +describe('Abanca', () => { + describe('#normalizeTransaction', () => { + it('returns the creditorName and debtorName as remittanceInformationStructured', () => { + const transaction = { + transactionId: 'non-unique-id', + internalTransactionId: 'D202301180000003', + transactionAmount: mockTransactionAmount, + remittanceInformationStructured: 'some-creditor-name', + }; + const normalizedTransaction = Abanca.normalizeTransaction( + transaction, + true, + ); + expect(normalizedTransaction.creditorName).toEqual( + transaction.remittanceInformationStructured, + ); + expect(normalizedTransaction.debtorName).toEqual( + transaction.remittanceInformationStructured, + ); + }); + }); +}); diff --git a/packages/sync-server/src/app-gocardless/banks/tests/bancsabadell_bsabesbbb.spec.js b/packages/sync-server/src/app-gocardless/banks/tests/bancsabadell_bsabesbbb.spec.js new file mode 100644 index 00000000000..1e7cb914018 --- /dev/null +++ b/packages/sync-server/src/app-gocardless/banks/tests/bancsabadell_bsabesbbb.spec.js @@ -0,0 +1,57 @@ +import Sabadell from '../bancsabadell_bsabesbbb.js'; + +describe('BancSabadell', () => { + describe('#normalizeTransaction', () => { + describe('returns the creditorName and debtorName from remittanceInformationUnstructuredArray', () => { + it('debtor role - amount < 0', () => { + const transaction = { + transactionAmount: { amount: '-100', currency: 'EUR' }, + remittanceInformationUnstructuredArray: ['some-creditor-name'], + internalTransactionId: 'd7dca139cf31d9', + transactionId: '04704109322', + bookingDate: '2022-05-01', + }; + const normalizedTransaction = Sabadell.normalizeTransaction( + transaction, + true, + ); + expect(normalizedTransaction.creditorName).toEqual( + 'some-creditor-name', + ); + expect(normalizedTransaction.debtorName).toEqual(null); + }); + + it('creditor role - amount > 0', () => { + const transaction = { + transactionAmount: { amount: '100', currency: 'EUR' }, + remittanceInformationUnstructuredArray: ['some-debtor-name'], + internalTransactionId: 'd7dca139cf31d9', + transactionId: '04704109322', + bookingDate: '2022-05-01', + }; + const normalizedTransaction = Sabadell.normalizeTransaction( + transaction, + true, + ); + expect(normalizedTransaction.debtorName).toEqual('some-debtor-name'); + expect(normalizedTransaction.creditorName).toEqual(null); + }); + }); + + it('extract date', () => { + const transaction = { + transactionAmount: { amount: '-100', currency: 'EUR' }, + remittanceInformationUnstructuredArray: ['some-creditor-name'], + internalTransactionId: 'd7dca139cf31d9', + transactionId: '04704109322', + bookingDate: '2024-10-02', + valueDate: '2024-10-05', + }; + const normalizedTransaction = Sabadell.normalizeTransaction( + transaction, + true, + ); + expect(normalizedTransaction.date).toEqual('2024-10-02'); + }); + }); +}); diff --git a/packages/sync-server/src/app-gocardless/banks/tests/easybank_bawaatww.spec.js b/packages/sync-server/src/app-gocardless/banks/tests/easybank_bawaatww.spec.js new file mode 100644 index 00000000000..c55be72a916 --- /dev/null +++ b/packages/sync-server/src/app-gocardless/banks/tests/easybank_bawaatww.spec.js @@ -0,0 +1,54 @@ +import EasybankBawaatww from '../easybank_bawaatww.js'; +import { mockTransactionAmount } from '../../services/tests/fixtures.js'; + +describe('easybank', () => { + describe('#normalizeTransaction', () => { + it('returns the expected payeeName from a transaction with a set creditorName', () => { + const transaction = { + creditorName: 'Some Payee Name', + transactionAmount: mockTransactionAmount, + bookingDate: '2024-01-01', + creditorAccount: 'AT611904300234573201', + }; + + const normalizedTransaction = EasybankBawaatww.normalizeTransaction( + transaction, + true, + ); + + expect(normalizedTransaction.payeeName).toEqual('Some Payee Name'); + }); + + it('returns the expected payeeName from a transaction with payee name inside structuredInformation', () => { + const transaction = { + payeeName: '', + transactionAmount: mockTransactionAmount, + remittanceInformationStructured: + 'Bezahlung Karte MC/000001234POS 1234 K001 12.12. 23:59SOME PAYEE NAME\\\\LOCATION\\1', + bookingDate: '2023-12-31', + }; + const normalizedTransaction = EasybankBawaatww.normalizeTransaction( + transaction, + true, + ); + expect(normalizedTransaction.payeeName).toEqual('Some Payee Name'); + }); + + it('returns the full structured information as payeeName from a transaction with no payee name', () => { + const transaction = { + payeeName: '', + transactionAmount: mockTransactionAmount, + remittanceInformationStructured: + 'Auszahlung Karte MC/000001234AUTOMAT 00012345 K001 31.12. 23:59', + bookingDate: '2023-12-31', + }; + const normalizedTransaction = EasybankBawaatww.normalizeTransaction( + transaction, + true, + ); + expect(normalizedTransaction.payeeName).toEqual( + 'Auszahlung Karte MC/000001234AUTOMAT 00012345 K001 31.12. 23:59', + ); + }); + }); +}); diff --git a/packages/sync-server/src/app-gocardless/banks/tests/ing_ingddeff.spec.js b/packages/sync-server/src/app-gocardless/banks/tests/ing_ingddeff.spec.js new file mode 100644 index 00000000000..156875bdfed --- /dev/null +++ b/packages/sync-server/src/app-gocardless/banks/tests/ing_ingddeff.spec.js @@ -0,0 +1,300 @@ +import IngIngddeff from '../ing_ingddeff.js'; + +describe('IngIngddeff', () => { + describe('#normalizeAccount', () => { + /** @type {import('../../gocardless.types.js').DetailedAccountWithInstitution} */ + const accountRaw = { + resourceId: 'e896eec6-6096-4efc-a941-756bd9d74765', + iban: 'DE02500105170137075030', + currency: 'EUR', + ownerName: 'Jane Doe', + product: 'Girokonto', + id: 'a787ba27-02ee-4fd6-be86-73831adc5498', + created: '2023-12-29T14:17:11.630352Z', + last_accessed: '2023-12-29T14:19:42.709478Z', + institution_id: 'ING_INGDDEFF', + status: 'READY', + owner_name: 'Jane Doe', + institution: { + id: 'ING_INGDDEFF', + name: 'ING', + bic: 'INGDDEFFXXX', + transaction_total_days: '390', + countries: ['DE'], + logo: 'https://storage.googleapis.com/gc-prd-institution_icons-production/DE/PNG/ing.png', + supported_payments: { + 'single-payment': ['SCT'], + }, + supported_features: [ + 'account_selection', + 'business_accounts', + 'corporate_accounts', + 'payments', + 'pending_transactions', + 'private_accounts', + ], + /*identification_codes: [],*/ + }, + }; + + it('returns normalized account data returned to Frontend', () => { + expect(IngIngddeff.normalizeAccount(accountRaw)).toEqual({ + account_id: 'a787ba27-02ee-4fd6-be86-73831adc5498', + iban: 'DE02500105170137075030', + institution: { + bic: 'INGDDEFFXXX', + countries: ['DE'], + id: 'ING_INGDDEFF', + logo: 'https://storage.googleapis.com/gc-prd-institution_icons-production/DE/PNG/ing.png', + name: 'ING', + supported_features: [ + 'account_selection', + 'business_accounts', + 'corporate_accounts', + 'payments', + 'pending_transactions', + 'private_accounts', + ], + supported_payments: { + 'single-payment': ['SCT'], + }, + transaction_total_days: '390', + }, + mask: '5030', + name: 'Girokonto (XXX 5030) EUR', + official_name: 'Girokonto', + type: 'checking', + }); + }); + }); + + const transactionsRaw = [ + { + transactionId: '000010348081381', + endToEndId: 'NOTPROVIDED', + bookingDate: '2023-12-29', + valueDate: '2023-12-29', + transactionAmount: { + amount: '-4.00', + currency: 'EUR', + }, + creditorName: 'VISA XXXXXXXXXXXXXXXXXXXX ', + remittanceInformationUnstructured: + 'mandatereference:,creditorid:,remittanceinformation:NR XXXX 63053 51590342815 KAUFUMSATZ 24.90 2311825 ARN044873748454374484719431 Google Pay ', + proprietaryBankTransactionCode: 'Lastschrifteinzug', + internalTransactionId: '085179a2e5fa34b0ff71b3f2c9f4876f', + date: '2023-12-29', + }, + { + transactionId: '000010348081380', + endToEndId: 'NOTPROVIDED', + bookingDate: '2023-12-29', + valueDate: '2023-12-29', + transactionAmount: { + amount: '-2.00', + currency: 'EUR', + }, + creditorName: 'VISA XXXXXXXXXXXXXXXXXXXX ', + remittanceInformationUnstructured: + 'mandatereference:,creditorid:,remittanceinformation:NR XXXX 8987 90671935362 KAUFUMSATZ 94.81 929614 ARN54795476045598005130492 Google Pay ', + proprietaryBankTransactionCode: 'Lastschrifteinzug', + internalTransactionId: '0707bbe2de27e5aabfd5dc614c584951', + date: '2023-12-29', + }, + { + transactionId: '000010348081379', + endToEndId: 'NOTPROVIDED', + bookingDate: '2023-12-29', + valueDate: '2023-12-29', + transactionAmount: { + amount: '-6.00', + currency: 'EUR', + }, + creditorName: 'VISA XXXXXXXXXXXXXXXXXXXX ', + remittanceInformationUnstructured: + 'mandatereference:,creditorid:,remittanceinformation:NR XXXX 2206 17679024325 KAUFUMSATZ 55.25 819456 ARN08595270353806495555431 Google Pay ', + proprietaryBankTransactionCode: 'Lastschrifteinzug', + internalTransactionId: '4b15b590652c9ebdc3f974591b15b250', + date: '2023-12-29', + }, + { + transactionId: '000010348081378', + endToEndId: 'NOTPROVIDED', + bookingDate: '2023-12-29', + valueDate: '2023-12-29', + transactionAmount: { + amount: '-12.99', + currency: 'EUR', + }, + creditorName: 'VISA XXXXXXXXXXXXXXXXXXXX ', + remittanceInformationUnstructured: + 'mandatereference:,creditorid:,remittanceinformation:NR XXXX 9437 535-182-825 LU KAUFUMSATZ 43.79 665448 ARN86236748928277201384604 ', + proprietaryBankTransactionCode: 'Lastschrifteinzug', + internalTransactionId: 'f930f8c153f3e37fb9906e4b3a2b4552', + date: '2023-12-29', + }, + { + transactionId: '000010348081377', + endToEndId: 'NOTPROVIDED', + bookingDate: '2023-12-29', + valueDate: '2023-12-29', + transactionAmount: { + amount: '-9.00', + currency: 'EUR', + }, + creditorName: 'VISA XXXXXXXXXXXXXXXXXXXX ', + remittanceInformationUnstructured: + 'mandatereference:,creditorid:,remittanceinformation:NR XXXX 3582 98236826123 KAUFUMSATZ 88.90 477561 ARN64452564252952225664357 Google Pay ', + proprietaryBankTransactionCode: 'Lastschrifteinzug', + internalTransactionId: '1ce866282deb78cc4ff4cd108e11b8cc', + date: '2023-12-29', + }, + { + transactionId: '000010347374680', + endToEndId: '9212020-0900000070-2023121711315956', + bookingDate: '2023-12-29', + valueDate: '2023-12-29', + transactionAmount: { + amount: '2892.61', + currency: 'EUR', + }, + debtorName: 'XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX', + remittanceInformationUnstructured: + 'mandatereference:,creditorid:,remittanceinformation:F22685813 Gehalt 80/6586', + proprietaryBankTransactionCode: 'Gehalt/Rente', + internalTransactionId: 'e731d8eb47f1ae96ccc11e1fb8b76a60', + date: '2023-12-29', + }, + { + transactionId: '000010336959253', + endToEndId: 'NOTPROVIDED', + bookingDate: '2023-12-28', + valueDate: '2023-12-28', + transactionAmount: { + amount: '-85.80', + currency: 'EUR', + }, + creditorName: 'VISA XXXXXXXXXXXXXXXXXXXX ', + remittanceInformationUnstructured: + 'mandatereference:,creditorid:,remittanceinformation:NR XXXX 7082 FAUCOGNEY E FR KAUFUMSATZ 38.20 265113 ARN47998616225906149245029 ', + proprietaryBankTransactionCode: 'Lastschrifteinzug', + internalTransactionId: '2bbc054ae7ba299482a7849fded864f3', + date: '2023-12-28', + }, + { + transactionId: '000010350537843', + endToEndId: 'NOTPROVIDED', + bookingDate: '2023-12-29', + valueDate: '2023-12-27', + transactionAmount: { + amount: '2.79', + currency: 'EUR', + }, + debtorName: ' ', + remittanceInformationUnstructured: + 'mandatereference:,creditorid:,remittanceinformation: Zins/Dividende ISIN IE36B9RBWM04 VANG.FTSE', + proprietaryBankTransactionCode: 'Zins / Dividende WP', + internalTransactionId: '3bb7c58199d3fa5a44e85871d9001798', + date: '2023-12-29', + }, + { + transactionId: '000010341786083', + endToEndId: 'NOTPROVIDED', + bookingDate: '2023-12-28', + valueDate: '2023-12-27', + transactionAmount: { + amount: '79.80', + currency: 'EUR', + }, + debtorName: 'VISA XXXXXXXXXXXXXXXXXXXX ', + remittanceInformationUnstructured: + 'mandatereference:,creditorid:,remittanceinformation:NR XXXX 4619 GUTSCHRIFTSBELEG 03.91 134870 ', + proprietaryBankTransactionCode: 'Gutschrift', + internalTransactionId: '5570eefb7213e39153a6c7fb97d7dc6f', + date: '2023-12-28', + }, + { + transactionId: '000010328399902', + endToEndId: 'NOTPROVIDED', + bookingDate: '2023-12-27', + valueDate: '2023-12-27', + transactionAmount: { + amount: '-10.90', + currency: 'EUR', + }, + debtorName: 'VISA XXXXXXXXXXXXXXXXXXXX ', + remittanceInformationUnstructured: + 'mandatereference:,creditorid:,remittanceinformation:NR XXXX 3465 XXXXXXXXX KAUFUMSATZ 90.40 505416 ARN63639757770303957985044 Google Pay ', + proprietaryBankTransactionCode: 'Lastschrifteinzug', + internalTransactionId: '1b1bf30b23afb56ba4d41b9c65cf0efa', + date: '2023-12-27', + }, + ]; + + describe('#sortTransactions', () => { + it('handles empty arrays', () => { + const transactions = []; + const sortedTransactions = IngIngddeff.sortTransactions(transactions); + expect(sortedTransactions).toEqual([]); + }); + + it('returns empty array for undefined input', () => { + const sortedTransactions = IngIngddeff.sortTransactions(undefined); + expect(sortedTransactions).toEqual([]); + }); + + it('returns sorted array for unsorted inputs', () => { + const normalizeTransactions = transactionsRaw.map((tx) => + IngIngddeff.normalizeTransaction(tx, true), + ); + const originalOrder = Array.from(normalizeTransactions); + const swap = (a, b) => { + const swap = normalizeTransactions[a]; + normalizeTransactions[a] = normalizeTransactions[b]; + normalizeTransactions[b] = swap; + }; + swap(1, 4); + swap(3, 6); + swap(0, 7); + const sortedTransactions = IngIngddeff.sortTransactions( + normalizeTransactions, + ); + expect(sortedTransactions).toEqual(originalOrder); + }); + }); + + describe('#countStartingBalance', () => { + /** @type {import('../../gocardless-node.types.js').Balance[]} */ + const balances = [ + { + balanceAmount: { amount: '3596.87', currency: 'EUR' }, + balanceType: 'interimBooked', + lastChangeDateTime: '2023-12-29T16:44:06.479Z', + }, + ]; + + it('should calculate the starting balance correctly', () => { + const normalizeTransactions = transactionsRaw.map((tx) => + IngIngddeff.normalizeTransaction(tx, true), + ); + const sortedTransactions = IngIngddeff.sortTransactions( + normalizeTransactions, + ); + + const startingBalance = IngIngddeff.calculateStartingBalance( + sortedTransactions, + balances, + ); + + expect(startingBalance).toEqual(75236); + }); + + it('returns the same balance amount when no transactions', () => { + const transactions = []; + + expect( + IngIngddeff.calculateStartingBalance(transactions, balances), + ).toEqual(359687); + }); + }); +}); diff --git a/packages/sync-server/src/app-gocardless/banks/tests/ing_pl_ingbplpw.spec.js b/packages/sync-server/src/app-gocardless/banks/tests/ing_pl_ingbplpw.spec.js new file mode 100644 index 00000000000..a38a6164667 --- /dev/null +++ b/packages/sync-server/src/app-gocardless/banks/tests/ing_pl_ingbplpw.spec.js @@ -0,0 +1,200 @@ +import IngPlIngbplpw from '../ing_pl_ingbplpw.js'; +import { mockTransactionAmount } from '../../services/tests/fixtures.js'; + +describe('IngPlIngbplpw', () => { + describe('#normalizeAccount', () => { + /** @type {import('../../gocardless.types.js').DetailedAccountWithInstitution} */ + const accountRaw = { + resourceId: 'PL00000000000000000987654321', + iban: 'PL00000000000000000987654321', + currency: 'PLN', + ownerName: 'John Example', + product: 'Current Account for Individuals (Retail)', + bic: 'INGBPLPW', + ownerAddressUnstructured: [ + 'UL. EXAMPLE STREET 10 M.1', + '00-000 WARSZAWA', + ], + id: 'd3eccc94-9536-48d3-98be-813f79199ee3', + created: '2022-07-24T20:45:47.929582Z', + last_accessed: '2023-01-24T22:12:00.193558Z', + institution_id: 'ING_PL_INGBPLPW', + status: 'READY', + owner_name: '', + institution: { + id: 'ING_PL_INGBPLPW', + name: 'ING', + bic: 'INGBPLPW', + transaction_total_days: '365', + countries: ['PL'], + logo: 'https://cdn.nordigen.com/ais/ING_PL_INGBPLPW.png', + supported_payments: {}, + supported_features: [ + 'access_scopes', + 'business_accounts', + 'card_accounts', + 'corporate_accounts', + 'pending_transactions', + 'private_accounts', + ], + }, + }; + + it('returns normalized account data returned to Frontend', () => { + const normalizedAccount = IngPlIngbplpw.normalizeAccount(accountRaw); + expect(normalizedAccount).toMatchInlineSnapshot(` + { + "account_id": "d3eccc94-9536-48d3-98be-813f79199ee3", + "iban": "PL00000000000000000987654321", + "institution": { + "bic": "INGBPLPW", + "countries": [ + "PL", + ], + "id": "ING_PL_INGBPLPW", + "logo": "https://cdn.nordigen.com/ais/ING_PL_INGBPLPW.png", + "name": "ING", + "supported_features": [ + "access_scopes", + "business_accounts", + "card_accounts", + "corporate_accounts", + "pending_transactions", + "private_accounts", + ], + "supported_payments": {}, + "transaction_total_days": "365", + }, + "mask": "4321", + "name": "Current Account for Individuals (Retail) (XXX 4321) PLN", + "official_name": "Current Account for Individuals (Retail)", + "type": "checking", + } + `); + }); + }); + + describe('#sortTransactions', () => { + it('sorts transactions by time and sequence from newest to oldest', () => { + const transactions = [ + { + transactionId: 'D202301180000003', + transactionAmount: mockTransactionAmount, + }, + { + transactionId: 'D202301180000004', + transactionAmount: mockTransactionAmount, + }, + { + transactionId: 'D202301230000001', + transactionAmount: mockTransactionAmount, + }, + { + transactionId: 'D202301180000002', + transactionAmount: mockTransactionAmount, + }, + { + transactionId: 'D202301200000001', + transactionAmount: mockTransactionAmount, + }, + ]; + const sortedTransactions = IngPlIngbplpw.sortTransactions(transactions); + expect(sortedTransactions).toEqual([ + { + transactionId: 'D202301230000001', + transactionAmount: mockTransactionAmount, + }, + { + transactionId: 'D202301200000001', + transactionAmount: mockTransactionAmount, + }, + { + transactionId: 'D202301180000004', + transactionAmount: mockTransactionAmount, + }, + { + transactionId: 'D202301180000003', + transactionAmount: mockTransactionAmount, + }, + { + transactionId: 'D202301180000002', + transactionAmount: mockTransactionAmount, + }, + ]); + }); + + it('handles empty arrays', () => { + const transactions = []; + const sortedTransactions = IngPlIngbplpw.sortTransactions(transactions); + expect(sortedTransactions).toEqual([]); + }); + + it('returns empty array for undefined input', () => { + const sortedTransactions = IngPlIngbplpw.sortTransactions(undefined); + expect(sortedTransactions).toEqual([]); + }); + }); + + describe('#countStartingBalance', () => { + it('should calculate the starting balance correctly', () => { + /** @type {import('../../gocardless-node.types.js').Transaction[]} */ + const sortedTransactions = [ + { + transactionAmount: { amount: '-100.00', currency: 'USD' }, + balanceAfterTransaction: { + balanceAmount: { amount: '400.00', currency: 'USD' }, + balanceType: 'interimBooked', + }, + }, + { + transactionAmount: { amount: '50.00', currency: 'USD' }, + balanceAfterTransaction: { + balanceAmount: { amount: '450.00', currency: 'USD' }, + balanceType: 'interimBooked', + }, + }, + { + transactionAmount: { amount: '-25.00', currency: 'USD' }, + balanceAfterTransaction: { + balanceAmount: { amount: '475.00', currency: 'USD' }, + balanceType: 'interimBooked', + }, + }, + ]; + + /** @type {import('../../gocardless-node.types.js').Balance[]} */ + const balances = [ + { + balanceType: 'interimBooked', + balanceAmount: { amount: '500.00', currency: 'USD' }, + }, + { + balanceType: 'closingBooked', + balanceAmount: { amount: '600.00', currency: 'USD' }, + }, + ]; + + const startingBalance = IngPlIngbplpw.calculateStartingBalance( + sortedTransactions, + balances, + ); + + expect(startingBalance).toEqual(50000); + }); + + it('returns the same balance amount when no transactions', () => { + const transactions = []; + + /** @type {import('../../gocardless-node.types.js').Balance[]} */ + const balances = [ + { + balanceType: 'interimBooked', + balanceAmount: { amount: '500.00', currency: 'USD' }, + }, + ]; + expect( + IngPlIngbplpw.calculateStartingBalance(transactions, balances), + ).toEqual(50000); + }); + }); +}); diff --git a/packages/sync-server/src/app-gocardless/banks/tests/integration_bank.spec.js b/packages/sync-server/src/app-gocardless/banks/tests/integration_bank.spec.js new file mode 100644 index 00000000000..a73da647cb0 --- /dev/null +++ b/packages/sync-server/src/app-gocardless/banks/tests/integration_bank.spec.js @@ -0,0 +1,157 @@ +import { jest } from '@jest/globals'; +import IntegrationBank from '../integration-bank.js'; +import { + mockExtendAccountsAboutInstitutions, + mockInstitution, +} from '../../services/tests/fixtures.js'; + +describe('IntegrationBank', () => { + let consoleSpy; + + beforeEach(() => { + consoleSpy = jest.spyOn(console, 'debug'); + }); + + describe('normalizeAccount', () => { + const account = mockExtendAccountsAboutInstitutions[0]; + + it('should return a normalized account object', () => { + const normalizedAccount = IntegrationBank.normalizeAccount(account); + expect(normalizedAccount).toEqual({ + account_id: account.id, + institution: mockInstitution, + mask: '4321', + iban: account.iban, + name: 'account-example-one (XXX 4321) PLN', + official_name: 'Savings Account for Individuals (Retail)', + type: 'checking', + }); + }); + + it('should return a normalized account object with masked value "0000" when no iban property is provided', () => { + const normalizedAccount = IntegrationBank.normalizeAccount({ + ...account, + iban: undefined, + }); + expect(normalizedAccount).toEqual({ + account_id: account.id, + institution: mockInstitution, + mask: '0000', + iban: null, + name: 'account-example-one PLN', + official_name: 'Savings Account for Individuals (Retail)', + type: 'checking', + }); + }); + + it('normalizeAccount logs available account properties', () => { + IntegrationBank.normalizeAccount(account); + expect(consoleSpy).toHaveBeenCalledWith( + 'Available account properties for new institution integration', + { + account: JSON.stringify(account), + }, + ); + }); + }); + + describe('sortTransactions', () => { + const transactions = [ + { + date: '2022-01-01', + bookingDate: '2022-01-01', + transactionAmount: { amount: '100', currency: 'EUR' }, + }, + { + date: '2022-01-03', + bookingDate: '2022-01-03', + transactionAmount: { amount: '100', currency: 'EUR' }, + }, + { + date: '2022-01-02', + bookingDate: '2022-01-02', + transactionAmount: { amount: '100', currency: 'EUR' }, + }, + ]; + const sortedTransactions = [ + { + date: '2022-01-03', + bookingDate: '2022-01-03', + transactionAmount: { amount: '100', currency: 'EUR' }, + }, + { + date: '2022-01-02', + bookingDate: '2022-01-02', + transactionAmount: { amount: '100', currency: 'EUR' }, + }, + { + date: '2022-01-01', + bookingDate: '2022-01-01', + transactionAmount: { amount: '100', currency: 'EUR' }, + }, + ]; + + it('should return transactions sorted by bookingDate', () => { + const sortedTransactions = IntegrationBank.sortTransactions(transactions); + expect(sortedTransactions).toEqual(sortedTransactions); + }); + + it('sortTransactions logs available transactions properties', () => { + IntegrationBank.sortTransactions(transactions); + expect(consoleSpy).toHaveBeenCalledWith( + 'Available (first 10) transactions properties for new integration of institution in sortTransactions function', + { top10Transactions: JSON.stringify(sortedTransactions.slice(0, 10)) }, + ); + }); + }); + + describe('calculateStartingBalance', () => { + /** @type {import('../../gocardless-node.types.js').Transaction[]} */ + const transactions = [ + { + bookingDate: '2022-01-01', + transactionAmount: { amount: '100', currency: 'EUR' }, + }, + { + bookingDate: '2022-02-01', + transactionAmount: { amount: '100', currency: 'EUR' }, + }, + { + bookingDate: '2022-03-01', + transactionAmount: { amount: '100', currency: 'EUR' }, + }, + ]; + + /** @type {import('../../gocardless-node.types.js').Balance[]} */ + const balances = [ + { + balanceAmount: { amount: '1000.00', currency: 'EUR' }, + balanceType: 'interimBooked', + }, + ]; + + it('should return 0 when no transactions or balances are provided', () => { + const startingBalance = IntegrationBank.calculateStartingBalance([], []); + expect(startingBalance).toEqual(0); + }); + + it('should return 70000 when transactions and balances are provided', () => { + const startingBalance = IntegrationBank.calculateStartingBalance( + transactions, + balances, + ); + expect(startingBalance).toEqual(70000); + }); + + it('logs available transactions and balances properties', () => { + IntegrationBank.calculateStartingBalance(transactions, balances); + expect(consoleSpy).toHaveBeenCalledWith( + 'Available (first 10) transactions properties for new integration of institution in calculateStartingBalance function', + { + balances: JSON.stringify(balances), + top10SortedTransactions: JSON.stringify(transactions.slice(0, 10)), + }, + ); + }); + }); +}); diff --git a/packages/sync-server/src/app-gocardless/banks/tests/mbank_retail_brexplpw.spec.js b/packages/sync-server/src/app-gocardless/banks/tests/mbank_retail_brexplpw.spec.js new file mode 100644 index 00000000000..d4113df8ffa --- /dev/null +++ b/packages/sync-server/src/app-gocardless/banks/tests/mbank_retail_brexplpw.spec.js @@ -0,0 +1,169 @@ +import MbankRetailBrexplpw from '../mbank_retail_brexplpw.js'; + +describe('MbankRetailBrexplpw', () => { + describe('#normalizeAccount', () => { + /** @type {import('../../gocardless.types.js').DetailedAccountWithInstitution} */ + const accountRaw = { + iban: 'PL00000000000000000987654321', + currency: 'PLN', + ownerName: 'John Example', + displayName: 'EKONTO', + product: 'RACHUNEK BIEŻĄCY', + usage: 'PRIV', + ownerAddressUnstructured: [ + 'POL', + 'UL. EXAMPLE STREET 10 M.1', + '00-000 WARSZAWA', + ], + id: 'd3eccc94-9536-48d3-98be-813f79199ee3', + created: '2023-01-18T13:24:55.879512Z', + last_accessed: null, + institution_id: 'MBANK_RETAIL_BREXPLPW', + status: 'READY', + owner_name: '', + institution: { + id: 'MBANK_RETAIL_BREXPLPW', + name: 'mBank Retail', + bic: 'BREXPLPW', + transaction_total_days: '90', + countries: ['PL'], + logo: 'https://cdn.nordigen.com/ais/MBANK_RETAIL_BREXCZPP.png', + supported_payments: {}, + supported_features: [ + 'access_scopes', + 'business_accounts', + 'card_accounts', + 'corporate_accounts', + 'pending_transactions', + 'private_accounts', + ], + }, + }; + it('returns normalized account data returned to Frontend', () => { + expect(MbankRetailBrexplpw.normalizeAccount(accountRaw)) + .toMatchInlineSnapshot(` + { + "account_id": "d3eccc94-9536-48d3-98be-813f79199ee3", + "iban": "PL00000000000000000987654321", + "institution": { + "bic": "BREXPLPW", + "countries": [ + "PL", + ], + "id": "MBANK_RETAIL_BREXPLPW", + "logo": "https://cdn.nordigen.com/ais/MBANK_RETAIL_BREXCZPP.png", + "name": "mBank Retail", + "supported_features": [ + "access_scopes", + "business_accounts", + "card_accounts", + "corporate_accounts", + "pending_transactions", + "private_accounts", + ], + "supported_payments": {}, + "transaction_total_days": "90", + }, + "mask": "4321", + "name": "EKONTO (XXX 4321) PLN", + "official_name": "RACHUNEK BIEŻĄCY", + "type": "checking", + } + `); + }); + }); + + describe('#sortTransactions', () => { + it('returns transactions from newest to oldest', () => { + const sortedTransactions = MbankRetailBrexplpw.sortTransactions([ + { + transactionId: '202212300001', + transactionAmount: { amount: '100', currency: 'EUR' }, + }, + { + transactionId: '202212300003', + transactionAmount: { amount: '100', currency: 'EUR' }, + }, + { + transactionId: '202212300002', + transactionAmount: { amount: '100', currency: 'EUR' }, + }, + { + transactionId: '202212300000', + transactionAmount: { amount: '100', currency: 'EUR' }, + }, + { + transactionId: '202112300001', + transactionAmount: { amount: '100', currency: 'EUR' }, + }, + ]); + + expect(sortedTransactions).toEqual([ + { + transactionId: '202212300003', + transactionAmount: { amount: '100', currency: 'EUR' }, + }, + { + transactionId: '202212300002', + transactionAmount: { amount: '100', currency: 'EUR' }, + }, + { + transactionId: '202212300001', + transactionAmount: { amount: '100', currency: 'EUR' }, + }, + { + transactionId: '202212300000', + transactionAmount: { amount: '100', currency: 'EUR' }, + }, + { + transactionId: '202112300001', + transactionAmount: { amount: '100', currency: 'EUR' }, + }, + ]); + }); + + it('returns empty array for empty input', () => { + const sortedTransactions = MbankRetailBrexplpw.sortTransactions([]); + expect(sortedTransactions).toEqual([]); + }); + + it('returns empty array for undefined input', () => { + const sortedTransactions = + MbankRetailBrexplpw.sortTransactions(undefined); + expect(sortedTransactions).toEqual([]); + }); + }); + + describe('#countStartingBalance', () => { + /** @type {import('../../gocardless-node.types.js').Balance[]} */ + const balances = [ + { + balanceAmount: { amount: '1000.00', currency: 'PLN' }, + balanceType: 'interimBooked', + }, + ]; + + it('returns the same balance amount when no transactions', () => { + const transactions = []; + + expect( + MbankRetailBrexplpw.calculateStartingBalance(transactions, balances), + ).toEqual(100000); + }); + + it('returns the balance minus the available transactions', () => { + const transactions = [ + { + transactionAmount: { amount: '200.00', currency: 'PLN' }, + }, + { + transactionAmount: { amount: '300.50', currency: 'PLN' }, + }, + ]; + + expect( + MbankRetailBrexplpw.calculateStartingBalance(transactions, balances), + ).toEqual(49950); + }); + }); +}); diff --git a/packages/sync-server/src/app-gocardless/banks/tests/nationwide_naiagb21.spec.js b/packages/sync-server/src/app-gocardless/banks/tests/nationwide_naiagb21.spec.js new file mode 100644 index 00000000000..9e95c85f41d --- /dev/null +++ b/packages/sync-server/src/app-gocardless/banks/tests/nationwide_naiagb21.spec.js @@ -0,0 +1,105 @@ +import Nationwide from '../nationwide_naiagb21.js'; +import { mockTransactionAmount } from '../../services/tests/fixtures.js'; + +describe('Nationwide', () => { + describe('#normalizeTransaction', () => { + it('retains date for booked transaction', () => { + const d = new Date(); + d.setDate(d.getDate() - 7); + + const date = d.toISOString().split('T')[0]; + + const transaction = { + bookingDate: date, + transactionAmount: mockTransactionAmount, + }; + + const normalizedTransaction = Nationwide.normalizeTransaction( + transaction, + true, + ); + + expect(normalizedTransaction.date).toEqual(date); + }); + + it('fixes date for pending transactions', () => { + const d = new Date(); + const date = d.toISOString().split('T')[0]; + + const transaction = { + bookingDate: date, + transactionAmount: mockTransactionAmount, + }; + + const normalizedTransaction = Nationwide.normalizeTransaction( + transaction, + false, + ); + + expect(new Date(normalizedTransaction.date).getTime()).toBeLessThan( + d.getTime(), + ); + }); + + it('keeps transactionId if in the correct format', () => { + const transactionId = 'a896729bb8b30b5ca862fe70bd5967185e2b5d3a'; + const transaction = { + bookingDate: '2024-01-01T00:00:00Z', + transactionId, + transactionAmount: mockTransactionAmount, + }; + + const normalizedTransaction = Nationwide.normalizeTransaction( + transaction, + false, + ); + + expect(normalizedTransaction.transactionId).toBe(transactionId); + }); + + it('unsets transactionId if not valid length', () => { + const transaction = { + bookingDate: '2024-01-01T00:00:00Z', + transactionId: '0123456789', + transactionAmount: mockTransactionAmount, + }; + + const normalizedTransaction = Nationwide.normalizeTransaction( + transaction, + false, + ); + + expect(normalizedTransaction.transactionId).toBeNull(); + }); + + it('unsets transactionId if debit placeholder found', () => { + const transaction = { + bookingDate: '2024-01-01T00:00:00Z', + transactionId: '00DEBIT202401010000000000-1000SUPERMARKET', + transactionAmount: mockTransactionAmount, + }; + + const normalizedTransaction = Nationwide.normalizeTransaction( + transaction, + false, + ); + + expect(normalizedTransaction.transactionId).toBeNull(); + }); + + it('unsets transactionId if credit placeholder found', () => { + const transaction = { + bookingDate: '2024-01-01T00:00:00Z', + transactionId: '00CREDIT202401010000000000-1000SUPERMARKET', + transactionAmount: mockTransactionAmount, + }; + + const normalizedTransaction = Nationwide.normalizeTransaction( + transaction, + false, + ); + + expect(normalizedTransaction.transactionId).toBeNull(); + }); + }); +}); diff --git a/packages/sync-server/src/app-gocardless/banks/tests/sandboxfinance_sfin0000.spec.js b/packages/sync-server/src/app-gocardless/banks/tests/sandboxfinance_sfin0000.spec.js new file mode 100644 index 00000000000..a0ac23c1f19 --- /dev/null +++ b/packages/sync-server/src/app-gocardless/banks/tests/sandboxfinance_sfin0000.spec.js @@ -0,0 +1,131 @@ +import SandboxfinanceSfin0000 from '../sandboxfinance_sfin0000.js'; + +describe('SandboxfinanceSfin0000', () => { + describe('#normalizeAccount', () => { + /** @type {import('../../gocardless.types.js').DetailedAccountWithInstitution} */ + const accountRaw = { + resourceId: '01F3NS5ASCNMVCTEJDT0G215YE', + iban: 'GL0865354374424724', + currency: 'EUR', + ownerName: 'Jane Doe', + name: 'Main Account', + product: 'Checkings', + cashAccountType: 'CACC', + id: '99a0bfe2-0bef-46df-bff2-e9ae0c6c5838', + created: '2022-02-21T13:43:55.608911Z', + last_accessed: '2023-01-25T16:50:15.078264Z', + institution_id: 'SANDBOXFINANCE_SFIN0000', + status: 'READY', + owner_name: 'Jane Doe', + institution: { + id: 'SANDBOXFINANCE_SFIN0000', + name: 'Sandbox Finance', + bic: 'SFIN0000', + transaction_total_days: '90', + countries: ['XX'], + logo: 'https://cdn.nordigen.com/ais/SANDBOXFINANCE_SFIN0000.png', + supported_payments: {}, + supported_features: [], + }, + }; + + it('returns normalized account data returned to Frontend', () => { + expect(SandboxfinanceSfin0000.normalizeAccount(accountRaw)) + .toMatchInlineSnapshot(` + { + "account_id": "99a0bfe2-0bef-46df-bff2-e9ae0c6c5838", + "iban": "GL0865354374424724", + "institution": { + "bic": "SFIN0000", + "countries": [ + "XX", + ], + "id": "SANDBOXFINANCE_SFIN0000", + "logo": "https://cdn.nordigen.com/ais/SANDBOXFINANCE_SFIN0000.png", + "name": "Sandbox Finance", + "supported_features": [], + "supported_payments": {}, + "transaction_total_days": "90", + }, + "mask": "4724", + "name": "Main Account (XXX 4724) EUR", + "official_name": "Checkings", + "type": "checking", + } + `); + }); + }); + + describe('#sortTransactions', () => { + it('handles empty arrays', () => { + const transactions = []; + const sortedTransactions = + SandboxfinanceSfin0000.sortTransactions(transactions); + expect(sortedTransactions).toEqual([]); + }); + + it('returns empty array for undefined input', () => { + const sortedTransactions = + SandboxfinanceSfin0000.sortTransactions(undefined); + expect(sortedTransactions).toEqual([]); + }); + }); + + describe('#countStartingBalance', () => { + /** @type {import('../../gocardless-node.types.js').Balance[]} */ + const balances = [ + { + balanceAmount: { amount: '1000.00', currency: 'PLN' }, + balanceType: 'interimAvailable', + }, + ]; + + it('should calculate the starting balance correctly', () => { + const sortedTransactions = [ + { + transactionId: '2022-01-01-1', + transactionAmount: { amount: '-100.00', currency: 'USD' }, + }, + { + transactionId: '2022-01-01-2', + transactionAmount: { amount: '50.00', currency: 'USD' }, + }, + { + transactionId: '2022-01-01-3', + transactionAmount: { amount: '-25.00', currency: 'USD' }, + }, + ]; + + const startingBalance = SandboxfinanceSfin0000.calculateStartingBalance( + sortedTransactions, + balances, + ); + + expect(startingBalance).toEqual(107500); + }); + + it('returns the same balance amount when no transactions', () => { + const transactions = []; + + expect( + SandboxfinanceSfin0000.calculateStartingBalance(transactions, balances), + ).toEqual(100000); + }); + + it('returns the balance minus the available transactions', () => { + /** @type {import('../../gocardless-node.types.js').Transaction[]} */ + const transactions = [ + { + transactionAmount: { amount: '200.00', currency: 'PLN' }, + }, + { + transactionAmount: { amount: '300.50', currency: 'PLN' }, + }, + ]; + + expect( + SandboxfinanceSfin0000.calculateStartingBalance(transactions, balances), + ).toEqual(49950); + }); + }); +}); diff --git a/packages/sync-server/src/app-gocardless/banks/tests/spk_marburg_biedenkopf_heladef1mar.spec.js b/packages/sync-server/src/app-gocardless/banks/tests/spk_marburg_biedenkopf_heladef1mar.spec.js new file mode 100644 index 00000000000..bf3bf365b00 --- /dev/null +++ b/packages/sync-server/src/app-gocardless/banks/tests/spk_marburg_biedenkopf_heladef1mar.spec.js @@ -0,0 +1,254 @@ +import SpkMarburgBiedenkopfHeladef1mar from '../spk_marburg_biedenkopf_heladef1mar.js'; + +describe('SpkMarburgBiedenkopfHeladef1mar', () => { + describe('#normalizeAccount', () => { + /** @type {import('../../gocardless.types.js').DetailedAccountWithInstitution} */ + const accountRaw = { + resourceId: 'e896eec6-6096-4efc-a941-756bd9d74765', + iban: 'DE50533500000123456789', + currency: 'EUR', + ownerName: 'JANE DOE', + product: 'Sichteinlagen', + bic: 'HELADEF1MAR', + usage: 'PRIV', + id: 'a787ba27-02ee-4fd6-be86-73831adc5498', + created: '2024-01-01T14:17:11.630352Z', + last_accessed: '2024-01-01T14:19:42.709478Z', + institution_id: 'SPK_MARBURG_BIEDENKOPF_HELADEF1MAR', + status: 'READY', + owner_name: 'JANE DOE', + institution: { + id: 'SPK_MARBURG_BIEDENKOPF_HELADEF1MAR', + name: 'Sparkasse Marburg-Biedenkopf', + bic: 'HELADEF1MAR', + transaction_total_days: '360', + countries: ['DE'], + logo: 'https://storage.googleapis.com/gc-prd-institution_icons-production/DE/PNG/sparkasse.png', + supported_payments: { + 'single-payment': ['SCT', 'ISCT'], + }, + supported_features: [ + 'card_accounts', + 'payments', + 'pending_transactions', + ], + /*"identification_codes": []*/ + }, + }; + + it('returns normalized account data returned to Frontend', () => { + expect( + SpkMarburgBiedenkopfHeladef1mar.normalizeAccount(accountRaw), + ).toEqual({ + account_id: 'a787ba27-02ee-4fd6-be86-73831adc5498', + iban: 'DE50533500000123456789', + institution: { + bic: 'HELADEF1MAR', + countries: ['DE'], + id: 'SPK_MARBURG_BIEDENKOPF_HELADEF1MAR', + logo: 'https://storage.googleapis.com/gc-prd-institution_icons-production/DE/PNG/sparkasse.png', + name: 'Sparkasse Marburg-Biedenkopf', + supported_features: [ + 'card_accounts', + 'payments', + 'pending_transactions', + ], + supported_payments: { + 'single-payment': ['SCT', 'ISCT'], + }, + transaction_total_days: '360', + }, + mask: '6789', + name: 'Sichteinlagen (XXX 6789) EUR', + official_name: 'Sichteinlagen', + type: 'checking', + }); + }); + }); + + const transactionsRaw = [ + { + transactionId: 'fefa0b605ac14a7eb14f4c8ab6a6af55', + bookingDate: '2023-12-29', + valueDate: '2023-12-29', + transactionAmount: { + amount: '-40.00', + currency: 'EUR', + }, + creditorName: 'JET Tankstelle', + remittanceInformationStructured: 'AUTORISATION 28.12. 18:30', + proprietaryBankTransactionCode: 'NSTO+000+0000+000-AA', + internalTransactionId: '761660c052ed48e78c2be39775f08da9', + date: '2023-12-29', + }, + { + transactionId: '1a8e5d0df259472694f13132001af0a6', + bookingDate: '2023-12-28', + valueDate: '2023-12-28', + transactionAmount: { + amount: '-1242.47', + currency: 'EUR', + }, + creditorName: 'Peter Muster', + remittanceInformationStructured: 'Miete 12/2023', + proprietaryBankTransactionCode: 'NSTO+111+1111+111-BB', + internalTransactionId: '5a20ac78b146401e940b6fee30ee404b', + date: '2023-12-28', + }, + { + transactionId: '166983e65ec54000a361a952e6161f33', + bookingDate: '2023-12-27', + valueDate: '2023-12-27', + transactionAmount: { + amount: '1541.23', + currency: 'EUR', + }, + debtorName: 'Arbeitgeber AG', + remittanceInformationStructured: 'Lohn/Gehalt 12/2023', + proprietaryBankTransactionCode: 'NSTO+222+2222+222-CC', + internalTransactionId: '51630dda877f45f186d315b8058d891a', + date: '2023-12-27', + }, + { + transactionId: '4dd9f4c9968a45739c0705ebc675b54b', + bookingDate: '2023-12-26', + valueDate: '2023-12-26', + transactionAmount: { + amount: '-8.00', + currency: 'EUR', + }, + remittanceInformationStructuredArray: [ + 'Entgeltabrechnung', + 'siehe Anlage', + ], + proprietaryBankTransactionCode: 'NSTO+333+3333+333-DD', + internalTransactionId: '9c58c87c2d1644e4a5e149c837c16bbb', + date: '2023-12-26', + }, + ]; + + describe('#normalizeTransaction', () => { + it('fallbacks to remittanceInformationStructured when remittanceInformationUnstructed is not set', () => { + const transaction = { + transactionId: 'fefa0b605ac14a7eb14f4c8ab6a6af55', + bookingDate: '2023-12-29', + valueDate: '2023-12-29', + transactionAmount: { + amount: '-40.00', + currency: 'EUR', + }, + creditorName: 'JET Tankstelle', + remittanceInformationStructured: 'AUTORISATION 28.12. 18:30', + proprietaryBankTransactionCode: 'NSTO+000+0000+000-AA', + internalTransactionId: '761660c052ed48e78c2be39775f08da9', + date: '2023-12-29', + }; + + expect( + SpkMarburgBiedenkopfHeladef1mar.normalizeTransaction(transaction, true) + .remittanceInformationUnstructured, + ).toEqual('AUTORISATION 28.12. 18:30'); + }); + + it('fallbacks to remittanceInformationStructuredArray when remittanceInformationUnstructed and remittanceInformationStructured is not set', () => { + const transaction = { + transactionId: '4dd9f4c9968a45739c0705ebc675b54b', + bookingDate: '2023-12-26', + valueDate: '2023-12-26', + transactionAmount: { + amount: '-8.00', + currency: 'EUR', + }, + remittanceInformationStructuredArray: [ + 'Entgeltabrechnung', + 'siehe Anlage', + ], + proprietaryBankTransactionCode: 'NSTO+333+3333+333-DD', + internalTransactionId: '9c58c87c2d1644e4a5e149c837c16bbb', + date: '2023-12-26', + }; + + expect( + SpkMarburgBiedenkopfHeladef1mar.normalizeTransaction(transaction, true) + .remittanceInformationUnstructured, + ).toEqual('Entgeltabrechnung siehe Anlage'); + }); + }); + + describe('#sortTransactions', () => { + it('handles empty arrays', () => { + const transactions = []; + const sortedTransactions = + SpkMarburgBiedenkopfHeladef1mar.sortTransactions(transactions); + expect(sortedTransactions).toEqual([]); + }); + + it('returns empty array for undefined input', () => { + const sortedTransactions = + SpkMarburgBiedenkopfHeladef1mar.sortTransactions(undefined); + expect(sortedTransactions).toEqual([]); + }); + + it('returns sorted array for unsorted inputs', () => { + const normalizeTransactions = transactionsRaw.map((tx) => + SpkMarburgBiedenkopfHeladef1mar.normalizeTransaction(tx, true), + ); + const originalOrder = Array.from(normalizeTransactions); + const swap = (a, b) => { + const swap = normalizeTransactions[a]; + normalizeTransactions[a] = normalizeTransactions[b]; + normalizeTransactions[b] = swap; + }; + swap(1, 4); + swap(3, 6); + swap(0, 7); + const sortedTransactions = + SpkMarburgBiedenkopfHeladef1mar.sortTransactions(normalizeTransactions); + expect(sortedTransactions).toEqual(originalOrder); + }); + }); + + describe('#countStartingBalance', () => { + /** @type {import('../../gocardless-node.types.js').Balance[]} */ + const balances = [ + { + balanceAmount: { amount: '3596.87', currency: 'EUR' }, + balanceType: 'closingBooked', + referenceDate: '2023-12-29', + }, + ]; + + it('should return 0 when no transactions or balances are provided', () => { + const startingBalance = + SpkMarburgBiedenkopfHeladef1mar.calculateStartingBalance([], []); + expect(startingBalance).toEqual(0); + }); + + it('should calculate the starting balance correctly', () => { + const normalizeTransactions = transactionsRaw.map((tx) => + SpkMarburgBiedenkopfHeladef1mar.normalizeTransaction(tx, true), + ); + const sortedTransactions = + SpkMarburgBiedenkopfHeladef1mar.sortTransactions(normalizeTransactions); + + const startingBalance = + SpkMarburgBiedenkopfHeladef1mar.calculateStartingBalance( + sortedTransactions, + balances, + ); + + expect(startingBalance).toEqual(334611); + }); + + it('returns the same balance amount when no transactions', () => { + const transactions = []; + + expect( + SpkMarburgBiedenkopfHeladef1mar.calculateStartingBalance( + transactions, + balances, + ), + ).toEqual(359687); + }); + }); +}); diff --git a/packages/sync-server/src/app-gocardless/banks/tests/swedbank_habalv22.spec.js b/packages/sync-server/src/app-gocardless/banks/tests/swedbank_habalv22.spec.js new file mode 100644 index 00000000000..31673a4eb33 --- /dev/null +++ b/packages/sync-server/src/app-gocardless/banks/tests/swedbank_habalv22.spec.js @@ -0,0 +1,62 @@ +import SwedbankHabaLV22 from '../swedbank_habalv22.js'; + +describe('#normalizeTransaction', () => { + const bookedCardTransaction = { + transactionId: '2024102900000000-1', + bookingDate: '2024-10-29', + valueDate: '2024-10-29', + transactionAmount: { + amount: '-22.99', + currency: 'EUR', + }, + creditorName: 'SOME CREDITOR NAME', + remittanceInformationUnstructured: + 'PIRKUMS 424242******4242 28.10.2024 22.99 EUR (111111) SOME CREDITOR NAME', + bankTransactionCode: 'PMNT-CCRD-POSD', + internalTransactionId: 'fa000f86afb2cc7678bcff0000000000', + }; + + it('extracts card transaction date', () => { + expect( + SwedbankHabaLV22.normalizeTransaction(bookedCardTransaction, true) + .bookingDate, + ).toEqual('2024-10-28'); + + expect( + SwedbankHabaLV22.normalizeTransaction(bookedCardTransaction, true).date, + ).toEqual('2024-10-28'); + }); + + it.each([ + ['regular text', 'Some info'], + ['partial card text', 'PIRKUMS xxx'], + ['null value', null], + ])('normalizes non-card transaction with %s', (_, remittanceInfo) => { + const transaction = { + ...bookedCardTransaction, + remittanceInformationUnstructured: remittanceInfo, + }; + const normalized = SwedbankHabaLV22.normalizeTransaction(transaction, true); + + expect(normalized.bookingDate).toEqual('2024-10-29'); + expect(normalized.date).toEqual('2024-10-29'); + }); + + const pendingCardTransaction = { + transactionId: '2024102900000000-1', + valueDate: '2024-10-29', + transactionAmount: { + amount: '-22.99', + currency: 'EUR', + }, + remittanceInformationUnstructured: + 'PIRKUMS 424242******4242 28.10.24 13:37 22.99 EUR (111111) SOME CREDITOR NAME', + }; + + it('extracts pending card transaction creditor name', () => { + expect( + SwedbankHabaLV22.normalizeTransaction(pendingCardTransaction, false) + .creditorName, + ).toEqual('SOME CREDITOR NAME'); + }); +}); diff --git a/packages/sync-server/upcoming-release-notes/531.md b/packages/sync-server/upcoming-release-notes/531.md new file mode 100644 index 00000000000..bb2262bc503 --- /dev/null +++ b/packages/sync-server/upcoming-release-notes/531.md @@ -0,0 +1,6 @@ +--- +category: Enhancements +authors: [DennaGherlyn] +--- + +Add GoCardless formatter for `SSK_DUSSELDORF_DUSSDEDDXXX` Stadtsparkasse Düsseldorf (Germany) diff --git a/packages/sync-server/upcoming-release-notes/533.md b/packages/sync-server/upcoming-release-notes/533.md new file mode 100644 index 00000000000..93f58649de6 --- /dev/null +++ b/packages/sync-server/upcoming-release-notes/533.md @@ -0,0 +1,6 @@ +--- +category: Bugfix +authors: [robxgd] +--- + +Fixed issue when no payeename is given for KBC transaction \ No newline at end of file diff --git a/packages/sync-server/upcoming-release-notes/534.md b/packages/sync-server/upcoming-release-notes/534.md new file mode 100644 index 00000000000..3d03094a666 --- /dev/null +++ b/packages/sync-server/upcoming-release-notes/534.md @@ -0,0 +1,6 @@ +--- +category: Enhancements +authors: [UnderKoen] +--- + +Make Google Pay transactions work for ABNAMRO_ABNANL2A diff --git a/packages/sync-server/upcoming-release-notes/535.md b/packages/sync-server/upcoming-release-notes/535.md new file mode 100644 index 00000000000..95df9343005 --- /dev/null +++ b/packages/sync-server/upcoming-release-notes/535.md @@ -0,0 +1,6 @@ +--- +category: Bugfix +authors: [spideraxal] +--- + +Add corner case transaction for ING Bank Romania diff --git a/packages/sync-server/upcoming-release-notes/538.md b/packages/sync-server/upcoming-release-notes/538.md new file mode 100644 index 00000000000..fd2b853516e --- /dev/null +++ b/packages/sync-server/upcoming-release-notes/538.md @@ -0,0 +1,6 @@ +--- +category: Maintenance +authors: [lnagel] +--- + +Fix WARN: FromAsCasing: 'as' and 'FROM' keywords' casing do not match in Dockerfiles diff --git a/packages/sync-server/upcoming-release-notes/539.md b/packages/sync-server/upcoming-release-notes/539.md new file mode 100644 index 00000000000..28b6968b7ed --- /dev/null +++ b/packages/sync-server/upcoming-release-notes/539.md @@ -0,0 +1,6 @@ +--- +category: Enhancements +authors: [MatissJanis] +--- + +Add GoCardless formatter for `BANK_OF_IRELAND_B365_BOFIIE2D` Bank of Ireland. diff --git a/packages/sync-server/upcoming-release-notes/541.md b/packages/sync-server/upcoming-release-notes/541.md new file mode 100644 index 00000000000..56a0e2b54e2 --- /dev/null +++ b/packages/sync-server/upcoming-release-notes/541.md @@ -0,0 +1,6 @@ +--- +category: Maintenance +authors: [matt-fidd] +--- + +Standardize GoCardless bank handlers From eda19f58561f6a84e5b29eae37ae204b924d33aa Mon Sep 17 00:00:00 2001 From: Mike Clark <mclarkgb@gmail.com> Date: Sat, 11 Jan 2025 11:42:53 +0000 Subject: [PATCH 52/55] fixed refs --- packages/sync-server/package.json | 4 ++-- yarn.lock | 8 ++++---- 2 files changed, 6 insertions(+), 6 deletions(-) diff --git a/packages/sync-server/package.json b/packages/sync-server/package.json index 36aa60ad32c..9f40c826446 100644 --- a/packages/sync-server/package.json +++ b/packages/sync-server/package.json @@ -22,8 +22,8 @@ "health-check": "node src/scripts/health-check.js" }, "dependencies": { - "@actual-app/crdt": "2.1.0", - "@actual-app/web": "25.1.0", + "@actual-app/crdt": "*", + "@actual-app/web": "*", "bcrypt": "^5.1.1", "better-sqlite3": "^11.7.0", "body-parser": "^1.20.3", diff --git a/yarn.lock b/yarn.lock index 0b918b4da3d..d6d3297eacd 100644 --- a/yarn.lock +++ b/yarn.lock @@ -38,7 +38,7 @@ __metadata: languageName: unknown linkType: soft -"@actual-app/crdt@npm:*, @actual-app/crdt@npm:2.1.0, @actual-app/crdt@workspace:^, @actual-app/crdt@workspace:packages/crdt": +"@actual-app/crdt@npm:*, @actual-app/crdt@workspace:^, @actual-app/crdt@workspace:packages/crdt": version: 0.0.0-use.local resolution: "@actual-app/crdt@workspace:packages/crdt" dependencies: @@ -55,7 +55,7 @@ __metadata: languageName: unknown linkType: soft -"@actual-app/web@npm:25.1.0, @actual-app/web@workspace:packages/desktop-client": +"@actual-app/web@npm:*, @actual-app/web@workspace:packages/desktop-client": version: 0.0.0-use.local resolution: "@actual-app/web@workspace:packages/desktop-client" dependencies: @@ -7742,8 +7742,8 @@ __metadata: version: 0.0.0-use.local resolution: "actual-sync@workspace:packages/sync-server" dependencies: - "@actual-app/crdt": "npm:2.1.0" - "@actual-app/web": "npm:25.1.0" + "@actual-app/crdt": "npm:*" + "@actual-app/web": "npm:*" "@babel/preset-typescript": "npm:^7.20.2" "@types/bcrypt": "npm:^5.0.2" "@types/better-sqlite3": "npm:^7.6.12" From 9ca996bf36052f775c7f6d524008bae32b47d852 Mon Sep 17 00:00:00 2001 From: Mike Clark <mclarkgb@gmail.com> Date: Sat, 11 Jan 2025 12:03:08 +0000 Subject: [PATCH 53/55] remove old lock --- package.json | 1 + packages/sync-server/yarn.lock | 6481 -------------------------------- 2 files changed, 1 insertion(+), 6481 deletions(-) delete mode 100644 packages/sync-server/yarn.lock diff --git a/package.json b/package.json index 88daa9fd3fd..01472d69e4e 100644 --- a/package.json +++ b/package.json @@ -28,6 +28,7 @@ "start:browser": "npm-run-all --parallel 'start:browser-*'", "start:browser-backend": "yarn workspace loot-core watch:browser", "start:browser-frontend": "yarn workspace @actual-app/web start:browser", + "start:server": "yarn workspace actual-sync start", "build:browser": "./bin/package-browser", "build:desktop": "./bin/package-electron", "build:api": "yarn workspace @actual-app/api build", diff --git a/packages/sync-server/yarn.lock b/packages/sync-server/yarn.lock deleted file mode 100644 index 48c27a66ea1..00000000000 --- a/packages/sync-server/yarn.lock +++ /dev/null @@ -1,6481 +0,0 @@ -# This file is generated by running "yarn install" inside your project. -# Manual changes might be lost - proceed with caution! - -__metadata: - version: 8 - cacheKey: 10c0 - -"@actual-app/crdt@npm:2.1.0": - version: 2.1.0 - resolution: "@actual-app/crdt@npm:2.1.0" - dependencies: - google-protobuf: "npm:^3.12.0-rc.1" - murmurhash: "npm:^2.0.1" - uuid: "npm:^9.0.0" - checksum: 10c0/131050eb42218229eebe60a954ee55275380cff3b139a08b34e25f6056b621033f28a231ce6cc59022fdc80d475500fe70e5f134f9dc3250e37516e679922d5c - languageName: node - linkType: hard - -"@actual-app/web@npm:24.12.0": - version: 24.12.0 - resolution: "@actual-app/web@npm:24.12.0" - checksum: 10c0/865fd5898e8da6347759a65d557047b80cd0c4162601fd4f57eccd1d84289d84c9f2fe563a4b4547dc2f67353042fb94a6e9c0cb13cacfda0547f84aaba73ef6 - languageName: node - linkType: hard - -"@ampproject/remapping@npm:^2.2.0": - version: 2.3.0 - resolution: "@ampproject/remapping@npm:2.3.0" - dependencies: - "@jridgewell/gen-mapping": "npm:^0.3.5" - "@jridgewell/trace-mapping": "npm:^0.3.24" - checksum: 10c0/81d63cca5443e0f0c72ae18b544cc28c7c0ec2cea46e7cb888bb0e0f411a1191d0d6b7af798d54e30777d8d1488b2ec0732aac2be342d3d7d3ffd271c6f489ed - languageName: node - linkType: hard - -"@babel/code-frame@npm:^7.0.0, @babel/code-frame@npm:^7.12.13, @babel/code-frame@npm:^7.24.7": - version: 7.24.7 - resolution: "@babel/code-frame@npm:7.24.7" - dependencies: - "@babel/highlight": "npm:^7.24.7" - picocolors: "npm:^1.0.0" - checksum: 10c0/ab0af539473a9f5aeaac7047e377cb4f4edd255a81d84a76058595f8540784cc3fbe8acf73f1e073981104562490aabfb23008cd66dc677a456a4ed5390fdde6 - languageName: node - linkType: hard - -"@babel/compat-data@npm:^7.24.7": - version: 7.24.7 - resolution: "@babel/compat-data@npm:7.24.7" - checksum: 10c0/dcd93a5632b04536498fbe2be5af1057f635fd7f7090483d8e797878559037e5130b26862ceb359acbae93ed27e076d395ddb4663db6b28a665756ffd02d324f - languageName: node - linkType: hard - -"@babel/core@npm:^7.11.6, @babel/core@npm:^7.12.3, @babel/core@npm:^7.23.9": - version: 7.24.7 - resolution: "@babel/core@npm:7.24.7" - dependencies: - "@ampproject/remapping": "npm:^2.2.0" - "@babel/code-frame": "npm:^7.24.7" - "@babel/generator": "npm:^7.24.7" - "@babel/helper-compilation-targets": "npm:^7.24.7" - "@babel/helper-module-transforms": "npm:^7.24.7" - "@babel/helpers": "npm:^7.24.7" - "@babel/parser": "npm:^7.24.7" - "@babel/template": "npm:^7.24.7" - "@babel/traverse": "npm:^7.24.7" - "@babel/types": "npm:^7.24.7" - convert-source-map: "npm:^2.0.0" - debug: "npm:^4.1.0" - gensync: "npm:^1.0.0-beta.2" - json5: "npm:^2.2.3" - semver: "npm:^6.3.1" - checksum: 10c0/4004ba454d3c20a46ea66264e06c15b82e9f6bdc35f88819907d24620da70dbf896abac1cb4cc4b6bb8642969e45f4d808497c9054a1388a386cf8c12e9b9e0d - languageName: node - linkType: hard - -"@babel/generator@npm:^7.24.7, @babel/generator@npm:^7.7.2": - version: 7.24.7 - resolution: "@babel/generator@npm:7.24.7" - dependencies: - "@babel/types": "npm:^7.24.7" - "@jridgewell/gen-mapping": "npm:^0.3.5" - "@jridgewell/trace-mapping": "npm:^0.3.25" - jsesc: "npm:^2.5.1" - checksum: 10c0/06b1f3350baf527a3309e50ffd7065f7aee04dd06e1e7db794ddfde7fe9d81f28df64edd587173f8f9295496a7ddb74b9a185d4bf4de7bb619e6d4ec45c8fd35 - languageName: node - linkType: hard - -"@babel/helper-annotate-as-pure@npm:^7.24.7": - version: 7.24.7 - resolution: "@babel/helper-annotate-as-pure@npm:7.24.7" - dependencies: - "@babel/types": "npm:^7.24.7" - checksum: 10c0/4679f7df4dffd5b3e26083ae65228116c3da34c3fff2c11ae11b259a61baec440f51e30fd236f7a0435b9d471acd93d0bc5a95df8213cbf02b1e083503d81b9a - languageName: node - linkType: hard - -"@babel/helper-compilation-targets@npm:^7.24.7": - version: 7.24.7 - resolution: "@babel/helper-compilation-targets@npm:7.24.7" - dependencies: - "@babel/compat-data": "npm:^7.24.7" - "@babel/helper-validator-option": "npm:^7.24.7" - browserslist: "npm:^4.22.2" - lru-cache: "npm:^5.1.1" - semver: "npm:^6.3.1" - checksum: 10c0/1d580a9bcacefe65e6bf02ba1dafd7ab278269fef45b5e281d8354d95c53031e019890464e7f9351898c01502dd2e633184eb0bcda49ed2ecd538675ce310f51 - languageName: node - linkType: hard - -"@babel/helper-create-class-features-plugin@npm:^7.24.7": - version: 7.24.7 - resolution: "@babel/helper-create-class-features-plugin@npm:7.24.7" - dependencies: - "@babel/helper-annotate-as-pure": "npm:^7.24.7" - "@babel/helper-environment-visitor": "npm:^7.24.7" - "@babel/helper-function-name": "npm:^7.24.7" - "@babel/helper-member-expression-to-functions": "npm:^7.24.7" - "@babel/helper-optimise-call-expression": "npm:^7.24.7" - "@babel/helper-replace-supers": "npm:^7.24.7" - "@babel/helper-skip-transparent-expression-wrappers": "npm:^7.24.7" - "@babel/helper-split-export-declaration": "npm:^7.24.7" - semver: "npm:^6.3.1" - peerDependencies: - "@babel/core": ^7.0.0 - checksum: 10c0/6b7b47d70b41c00f39f86790cff67acf2bce0289d52a7c182b28e797f4e0e6d69027e3d06eccf1d54dddc2e5dde1df663bb1932437e5f447aeb8635d8d64a6ab - languageName: node - linkType: hard - -"@babel/helper-environment-visitor@npm:^7.24.7": - version: 7.24.7 - resolution: "@babel/helper-environment-visitor@npm:7.24.7" - dependencies: - "@babel/types": "npm:^7.24.7" - checksum: 10c0/36ece78882b5960e2d26abf13cf15ff5689bf7c325b10a2895a74a499e712de0d305f8d78bb382dd3c05cfba7e47ec98fe28aab5674243e0625cd38438dd0b2d - languageName: node - linkType: hard - -"@babel/helper-function-name@npm:^7.24.7": - version: 7.24.7 - resolution: "@babel/helper-function-name@npm:7.24.7" - dependencies: - "@babel/template": "npm:^7.24.7" - "@babel/types": "npm:^7.24.7" - checksum: 10c0/e5e41e6cf86bd0f8bf272cbb6e7c5ee0f3e9660414174435a46653efba4f2479ce03ce04abff2aa2ef9359cf057c79c06cb7b134a565ad9c0e8a50dcdc3b43c4 - languageName: node - linkType: hard - -"@babel/helper-hoist-variables@npm:^7.24.7": - version: 7.24.7 - resolution: "@babel/helper-hoist-variables@npm:7.24.7" - dependencies: - "@babel/types": "npm:^7.24.7" - checksum: 10c0/19ee37563bbd1219f9d98991ad0e9abef77803ee5945fd85aa7aa62a67c69efca9a801696a1b58dda27f211e878b3327789e6fd2a6f6c725ccefe36774b5ce95 - languageName: node - linkType: hard - -"@babel/helper-member-expression-to-functions@npm:^7.24.7": - version: 7.24.7 - resolution: "@babel/helper-member-expression-to-functions@npm:7.24.7" - dependencies: - "@babel/traverse": "npm:^7.24.7" - "@babel/types": "npm:^7.24.7" - checksum: 10c0/9638c1d33cf6aba028461ccd3db6061c76ff863ca0d5013dd9a088bf841f2f77c46956493f9da18355c16759449d23b74cc1de4da357ade5c5c34c858f840f0a - languageName: node - linkType: hard - -"@babel/helper-module-imports@npm:^7.24.7": - version: 7.24.7 - resolution: "@babel/helper-module-imports@npm:7.24.7" - dependencies: - "@babel/traverse": "npm:^7.24.7" - "@babel/types": "npm:^7.24.7" - checksum: 10c0/97c57db6c3eeaea31564286e328a9fb52b0313c5cfcc7eee4bc226aebcf0418ea5b6fe78673c0e4a774512ec6c86e309d0f326e99d2b37bfc16a25a032498af0 - languageName: node - linkType: hard - -"@babel/helper-module-transforms@npm:^7.24.7": - version: 7.24.7 - resolution: "@babel/helper-module-transforms@npm:7.24.7" - dependencies: - "@babel/helper-environment-visitor": "npm:^7.24.7" - "@babel/helper-module-imports": "npm:^7.24.7" - "@babel/helper-simple-access": "npm:^7.24.7" - "@babel/helper-split-export-declaration": "npm:^7.24.7" - "@babel/helper-validator-identifier": "npm:^7.24.7" - peerDependencies: - "@babel/core": ^7.0.0 - checksum: 10c0/4f311755fcc3b4cbdb689386309cdb349cf0575a938f0b9ab5d678e1a81bbb265aa34ad93174838245f2ac7ff6d5ddbd0104638a75e4e961958ed514355687b6 - languageName: node - linkType: hard - -"@babel/helper-optimise-call-expression@npm:^7.24.7": - version: 7.24.7 - resolution: "@babel/helper-optimise-call-expression@npm:7.24.7" - dependencies: - "@babel/types": "npm:^7.24.7" - checksum: 10c0/ca6a9884705dea5c95a8b3ce132d1e3f2ae951ff74987d400d1d9c215dae9c0f9e29924d8f8e131e116533d182675bc261927be72f6a9a2968eaeeaa51eb1d0f - languageName: node - linkType: hard - -"@babel/helper-plugin-utils@npm:^7.0.0, @babel/helper-plugin-utils@npm:^7.10.4, @babel/helper-plugin-utils@npm:^7.12.13, @babel/helper-plugin-utils@npm:^7.14.5, @babel/helper-plugin-utils@npm:^7.24.7, @babel/helper-plugin-utils@npm:^7.8.0": - version: 7.24.7 - resolution: "@babel/helper-plugin-utils@npm:7.24.7" - checksum: 10c0/c3d38cd9b3520757bb4a279255cc3f956fc0ac1c193964bd0816ebd5c86e30710be8e35252227e0c9d9e0f4f56d9b5f916537f2bc588084b0988b4787a967d31 - languageName: node - linkType: hard - -"@babel/helper-replace-supers@npm:^7.24.7": - version: 7.24.7 - resolution: "@babel/helper-replace-supers@npm:7.24.7" - dependencies: - "@babel/helper-environment-visitor": "npm:^7.24.7" - "@babel/helper-member-expression-to-functions": "npm:^7.24.7" - "@babel/helper-optimise-call-expression": "npm:^7.24.7" - peerDependencies: - "@babel/core": ^7.0.0 - checksum: 10c0/0e133bb03371dee78e519c334a09c08e1493103a239d9628db0132dfaac3fc16380479ca3c590d278a9b71b624030a338c18ebbfe6d430ebb2e4653775c4b3e3 - languageName: node - linkType: hard - -"@babel/helper-simple-access@npm:^7.24.7": - version: 7.24.7 - resolution: "@babel/helper-simple-access@npm:7.24.7" - dependencies: - "@babel/traverse": "npm:^7.24.7" - "@babel/types": "npm:^7.24.7" - checksum: 10c0/7230e419d59a85f93153415100a5faff23c133d7442c19e0cd070da1784d13cd29096ee6c5a5761065c44e8164f9f80e3a518c41a0256df39e38f7ad6744fed7 - languageName: node - linkType: hard - -"@babel/helper-skip-transparent-expression-wrappers@npm:^7.24.7": - version: 7.24.7 - resolution: "@babel/helper-skip-transparent-expression-wrappers@npm:7.24.7" - dependencies: - "@babel/traverse": "npm:^7.24.7" - "@babel/types": "npm:^7.24.7" - checksum: 10c0/e3a9b8ac9c262ac976a1bcb5fe59694db5e6f0b4f9e7bdba5c7693b8b5e28113c23bdaa60fe8d3ec32a337091b67720b2053bcb3d5655f5406536c3d0584242b - languageName: node - linkType: hard - -"@babel/helper-split-export-declaration@npm:^7.24.7": - version: 7.24.7 - resolution: "@babel/helper-split-export-declaration@npm:7.24.7" - dependencies: - "@babel/types": "npm:^7.24.7" - checksum: 10c0/0254577d7086bf09b01bbde98f731d4fcf4b7c3fa9634fdb87929801307c1f6202a1352e3faa5492450fa8da4420542d44de604daf540704ff349594a78184f6 - languageName: node - linkType: hard - -"@babel/helper-string-parser@npm:^7.24.7": - version: 7.24.7 - resolution: "@babel/helper-string-parser@npm:7.24.7" - checksum: 10c0/47840c7004e735f3dc93939c77b099bb41a64bf3dda0cae62f60e6f74a5ff80b63e9b7cf77b5ec25a324516381fc994e1f62f922533236a8e3a6af57decb5e1e - languageName: node - linkType: hard - -"@babel/helper-validator-identifier@npm:^7.24.7": - version: 7.24.7 - resolution: "@babel/helper-validator-identifier@npm:7.24.7" - checksum: 10c0/87ad608694c9477814093ed5b5c080c2e06d44cb1924ae8320474a74415241223cc2a725eea2640dd783ff1e3390e5f95eede978bc540e870053152e58f1d651 - languageName: node - linkType: hard - -"@babel/helper-validator-option@npm:^7.24.7": - version: 7.24.7 - resolution: "@babel/helper-validator-option@npm:7.24.7" - checksum: 10c0/21aea2b7bc5cc8ddfb828741d5c8116a84cbc35b4a3184ec53124f08e09746f1f67a6f9217850188995ca86059a7942e36d8965a6730784901def777b7e8a436 - languageName: node - linkType: hard - -"@babel/helpers@npm:^7.24.7": - version: 7.24.7 - resolution: "@babel/helpers@npm:7.24.7" - dependencies: - "@babel/template": "npm:^7.24.7" - "@babel/types": "npm:^7.24.7" - checksum: 10c0/aa8e230f6668773e17e141dbcab63e935c514b4b0bf1fed04d2eaefda17df68e16b61a56573f7f1d4d1e605ce6cc162b5f7e9fdf159fde1fd9b77c920ae47d27 - languageName: node - linkType: hard - -"@babel/highlight@npm:^7.24.7": - version: 7.24.7 - resolution: "@babel/highlight@npm:7.24.7" - dependencies: - "@babel/helper-validator-identifier": "npm:^7.24.7" - chalk: "npm:^2.4.2" - js-tokens: "npm:^4.0.0" - picocolors: "npm:^1.0.0" - checksum: 10c0/674334c571d2bb9d1c89bdd87566383f59231e16bcdcf5bb7835babdf03c9ae585ca0887a7b25bdf78f303984af028df52831c7989fecebb5101cc132da9393a - languageName: node - linkType: hard - -"@babel/parser@npm:^7.1.0, @babel/parser@npm:^7.14.7, @babel/parser@npm:^7.20.7, @babel/parser@npm:^7.23.9, @babel/parser@npm:^7.24.7": - version: 7.24.7 - resolution: "@babel/parser@npm:7.24.7" - bin: - parser: ./bin/babel-parser.js - checksum: 10c0/8b244756872185a1c6f14b979b3535e682ff08cb5a2a5fd97cc36c017c7ef431ba76439e95e419d43000c5b07720495b00cf29a7f0d9a483643d08802b58819b - languageName: node - linkType: hard - -"@babel/plugin-syntax-async-generators@npm:^7.8.4": - version: 7.8.4 - resolution: "@babel/plugin-syntax-async-generators@npm:7.8.4" - dependencies: - "@babel/helper-plugin-utils": "npm:^7.8.0" - peerDependencies: - "@babel/core": ^7.0.0-0 - checksum: 10c0/d13efb282838481348c71073b6be6245b35d4f2f964a8f71e4174f235009f929ef7613df25f8d2338e2d3e44bc4265a9f8638c6aaa136d7a61fe95985f9725c8 - languageName: node - linkType: hard - -"@babel/plugin-syntax-bigint@npm:^7.8.3": - version: 7.8.3 - resolution: "@babel/plugin-syntax-bigint@npm:7.8.3" - dependencies: - "@babel/helper-plugin-utils": "npm:^7.8.0" - peerDependencies: - "@babel/core": ^7.0.0-0 - checksum: 10c0/686891b81af2bc74c39013655da368a480f17dd237bf9fbc32048e5865cb706d5a8f65438030da535b332b1d6b22feba336da8fa931f663b6b34e13147d12dde - languageName: node - linkType: hard - -"@babel/plugin-syntax-class-properties@npm:^7.8.3": - version: 7.12.13 - resolution: "@babel/plugin-syntax-class-properties@npm:7.12.13" - dependencies: - "@babel/helper-plugin-utils": "npm:^7.12.13" - peerDependencies: - "@babel/core": ^7.0.0-0 - checksum: 10c0/95168fa186416195280b1264fb18afcdcdcea780b3515537b766cb90de6ce042d42dd6a204a39002f794ae5845b02afb0fd4861a3308a861204a55e68310a120 - languageName: node - linkType: hard - -"@babel/plugin-syntax-import-meta@npm:^7.8.3": - version: 7.10.4 - resolution: "@babel/plugin-syntax-import-meta@npm:7.10.4" - dependencies: - "@babel/helper-plugin-utils": "npm:^7.10.4" - peerDependencies: - "@babel/core": ^7.0.0-0 - checksum: 10c0/0b08b5e4c3128523d8e346f8cfc86824f0da2697b1be12d71af50a31aff7a56ceb873ed28779121051475010c28d6146a6bfea8518b150b71eeb4e46190172ee - languageName: node - linkType: hard - -"@babel/plugin-syntax-json-strings@npm:^7.8.3": - version: 7.8.3 - resolution: "@babel/plugin-syntax-json-strings@npm:7.8.3" - dependencies: - "@babel/helper-plugin-utils": "npm:^7.8.0" - peerDependencies: - "@babel/core": ^7.0.0-0 - checksum: 10c0/e98f31b2ec406c57757d115aac81d0336e8434101c224edd9a5c93cefa53faf63eacc69f3138960c8b25401315af03df37f68d316c151c4b933136716ed6906e - languageName: node - linkType: hard - -"@babel/plugin-syntax-jsx@npm:^7.24.7, @babel/plugin-syntax-jsx@npm:^7.7.2": - version: 7.24.7 - resolution: "@babel/plugin-syntax-jsx@npm:7.24.7" - dependencies: - "@babel/helper-plugin-utils": "npm:^7.24.7" - peerDependencies: - "@babel/core": ^7.0.0-0 - checksum: 10c0/f44d927a9ae8d5ef016ff5b450e1671e56629ddc12e56b938e41fd46e141170d9dfc9a53d6cb2b9a20a7dd266a938885e6a3981c60c052a2e1daed602ac80e51 - languageName: node - linkType: hard - -"@babel/plugin-syntax-logical-assignment-operators@npm:^7.8.3": - version: 7.10.4 - resolution: "@babel/plugin-syntax-logical-assignment-operators@npm:7.10.4" - dependencies: - "@babel/helper-plugin-utils": "npm:^7.10.4" - peerDependencies: - "@babel/core": ^7.0.0-0 - checksum: 10c0/2594cfbe29411ad5bc2ad4058de7b2f6a8c5b86eda525a993959438615479e59c012c14aec979e538d60a584a1a799b60d1b8942c3b18468cb9d99b8fd34cd0b - languageName: node - linkType: hard - -"@babel/plugin-syntax-nullish-coalescing-operator@npm:^7.8.3": - version: 7.8.3 - resolution: "@babel/plugin-syntax-nullish-coalescing-operator@npm:7.8.3" - dependencies: - "@babel/helper-plugin-utils": "npm:^7.8.0" - peerDependencies: - "@babel/core": ^7.0.0-0 - checksum: 10c0/2024fbb1162899094cfc81152449b12bd0cc7053c6d4bda8ac2852545c87d0a851b1b72ed9560673cbf3ef6248257262c3c04aabf73117215c1b9cc7dd2542ce - languageName: node - linkType: hard - -"@babel/plugin-syntax-numeric-separator@npm:^7.8.3": - version: 7.10.4 - resolution: "@babel/plugin-syntax-numeric-separator@npm:7.10.4" - dependencies: - "@babel/helper-plugin-utils": "npm:^7.10.4" - peerDependencies: - "@babel/core": ^7.0.0-0 - checksum: 10c0/c55a82b3113480942c6aa2fcbe976ff9caa74b7b1109ff4369641dfbc88d1da348aceb3c31b6ed311c84d1e7c479440b961906c735d0ab494f688bf2fd5b9bb9 - languageName: node - linkType: hard - -"@babel/plugin-syntax-object-rest-spread@npm:^7.8.3": - version: 7.8.3 - resolution: "@babel/plugin-syntax-object-rest-spread@npm:7.8.3" - dependencies: - "@babel/helper-plugin-utils": "npm:^7.8.0" - peerDependencies: - "@babel/core": ^7.0.0-0 - checksum: 10c0/ee1eab52ea6437e3101a0a7018b0da698545230015fc8ab129d292980ec6dff94d265e9e90070e8ae5fed42f08f1622c14c94552c77bcac784b37f503a82ff26 - languageName: node - linkType: hard - -"@babel/plugin-syntax-optional-catch-binding@npm:^7.8.3": - version: 7.8.3 - resolution: "@babel/plugin-syntax-optional-catch-binding@npm:7.8.3" - dependencies: - "@babel/helper-plugin-utils": "npm:^7.8.0" - peerDependencies: - "@babel/core": ^7.0.0-0 - checksum: 10c0/27e2493ab67a8ea6d693af1287f7e9acec206d1213ff107a928e85e173741e1d594196f99fec50e9dde404b09164f39dec5864c767212154ffe1caa6af0bc5af - languageName: node - linkType: hard - -"@babel/plugin-syntax-optional-chaining@npm:^7.8.3": - version: 7.8.3 - resolution: "@babel/plugin-syntax-optional-chaining@npm:7.8.3" - dependencies: - "@babel/helper-plugin-utils": "npm:^7.8.0" - peerDependencies: - "@babel/core": ^7.0.0-0 - checksum: 10c0/46edddf2faa6ebf94147b8e8540dfc60a5ab718e2de4d01b2c0bdf250a4d642c2bd47cbcbb739febcb2bf75514dbcefad3c52208787994b8d0f8822490f55e81 - languageName: node - linkType: hard - -"@babel/plugin-syntax-top-level-await@npm:^7.8.3": - version: 7.14.5 - resolution: "@babel/plugin-syntax-top-level-await@npm:7.14.5" - dependencies: - "@babel/helper-plugin-utils": "npm:^7.14.5" - peerDependencies: - "@babel/core": ^7.0.0-0 - checksum: 10c0/14bf6e65d5bc1231ffa9def5f0ef30b19b51c218fcecaa78cd1bdf7939dfdf23f90336080b7f5196916368e399934ce5d581492d8292b46a2fb569d8b2da106f - languageName: node - linkType: hard - -"@babel/plugin-syntax-typescript@npm:^7.24.7, @babel/plugin-syntax-typescript@npm:^7.7.2": - version: 7.24.7 - resolution: "@babel/plugin-syntax-typescript@npm:7.24.7" - dependencies: - "@babel/helper-plugin-utils": "npm:^7.24.7" - peerDependencies: - "@babel/core": ^7.0.0-0 - checksum: 10c0/cdabd2e8010fb0ad15b49c2c270efc97c4bfe109ead36c7bbcf22da7a74bc3e49702fc4f22f12d2d6049e8e22a5769258df1fd05f0420ae45e11bdd5bc07805a - languageName: node - linkType: hard - -"@babel/plugin-transform-modules-commonjs@npm:^7.24.7": - version: 7.24.7 - resolution: "@babel/plugin-transform-modules-commonjs@npm:7.24.7" - dependencies: - "@babel/helper-module-transforms": "npm:^7.24.7" - "@babel/helper-plugin-utils": "npm:^7.24.7" - "@babel/helper-simple-access": "npm:^7.24.7" - peerDependencies: - "@babel/core": ^7.0.0-0 - checksum: 10c0/9442292b3daf6a5076cdc3c4c32bf423bda824ccaeb0dd0dc8b3effaa1fecfcb0130ae6e647fef12a5d5ff25bcc99a0d6bfc6d24a7525345e1bcf46fcdf81752 - languageName: node - linkType: hard - -"@babel/plugin-transform-typescript@npm:^7.24.7": - version: 7.24.7 - resolution: "@babel/plugin-transform-typescript@npm:7.24.7" - dependencies: - "@babel/helper-annotate-as-pure": "npm:^7.24.7" - "@babel/helper-create-class-features-plugin": "npm:^7.24.7" - "@babel/helper-plugin-utils": "npm:^7.24.7" - "@babel/plugin-syntax-typescript": "npm:^7.24.7" - peerDependencies: - "@babel/core": ^7.0.0-0 - checksum: 10c0/e8dacdc153a4c4599014b66eb01b94e3dc933d58d4f0cc3039c1a8f432e77b9df14f34a61964e014b975bf466f3fefd8c4768b3e887d3da1be9dc942799bdfdf - languageName: node - linkType: hard - -"@babel/preset-typescript@npm:^7.20.2": - version: 7.24.7 - resolution: "@babel/preset-typescript@npm:7.24.7" - dependencies: - "@babel/helper-plugin-utils": "npm:^7.24.7" - "@babel/helper-validator-option": "npm:^7.24.7" - "@babel/plugin-syntax-jsx": "npm:^7.24.7" - "@babel/plugin-transform-modules-commonjs": "npm:^7.24.7" - "@babel/plugin-transform-typescript": "npm:^7.24.7" - peerDependencies: - "@babel/core": ^7.0.0-0 - checksum: 10c0/986bc0978eedb4da33aba8e1e13a3426dd1829515313b7e8f4ba5d8c18aff1663b468939d471814e7acf4045d326ae6cff37239878d169ac3fe53a8fde71f8ee - languageName: node - linkType: hard - -"@babel/runtime@npm:^7.21.0": - version: 7.24.7 - resolution: "@babel/runtime@npm:7.24.7" - dependencies: - regenerator-runtime: "npm:^0.14.0" - checksum: 10c0/b6fa3ec61a53402f3c1d75f4d808f48b35e0dfae0ec8e2bb5c6fc79fb95935da75766e0ca534d0f1c84871f6ae0d2ebdd950727cfadb745a2cdbef13faef5513 - languageName: node - linkType: hard - -"@babel/template@npm:^7.24.7, @babel/template@npm:^7.3.3": - version: 7.24.7 - resolution: "@babel/template@npm:7.24.7" - dependencies: - "@babel/code-frame": "npm:^7.24.7" - "@babel/parser": "npm:^7.24.7" - "@babel/types": "npm:^7.24.7" - checksum: 10c0/95b0b3ee80fcef685b7f4426f5713a855ea2cd5ac4da829b213f8fb5afe48a2a14683c2ea04d446dbc7f711c33c5cd4a965ef34dcbe5bc387c9e966b67877ae3 - languageName: node - linkType: hard - -"@babel/traverse@npm:^7.24.7": - version: 7.24.7 - resolution: "@babel/traverse@npm:7.24.7" - dependencies: - "@babel/code-frame": "npm:^7.24.7" - "@babel/generator": "npm:^7.24.7" - "@babel/helper-environment-visitor": "npm:^7.24.7" - "@babel/helper-function-name": "npm:^7.24.7" - "@babel/helper-hoist-variables": "npm:^7.24.7" - "@babel/helper-split-export-declaration": "npm:^7.24.7" - "@babel/parser": "npm:^7.24.7" - "@babel/types": "npm:^7.24.7" - debug: "npm:^4.3.1" - globals: "npm:^11.1.0" - checksum: 10c0/a5135e589c3f1972b8877805f50a084a04865ccb1d68e5e1f3b94a8841b3485da4142e33413d8fd76bc0e6444531d3adf1f59f359c11ffac452b743d835068ab - languageName: node - linkType: hard - -"@babel/types@npm:^7.0.0, @babel/types@npm:^7.20.7, @babel/types@npm:^7.24.7, @babel/types@npm:^7.3.3, @babel/types@npm:^7.8.3": - version: 7.24.7 - resolution: "@babel/types@npm:7.24.7" - dependencies: - "@babel/helper-string-parser": "npm:^7.24.7" - "@babel/helper-validator-identifier": "npm:^7.24.7" - to-fast-properties: "npm:^2.0.0" - checksum: 10c0/d9ecbfc3eb2b05fb1e6eeea546836ac30d990f395ef3fe3f75ced777a222c3cfc4489492f72e0ce3d9a5a28860a1ce5f81e66b88cf5088909068b3ff4fab72c1 - languageName: node - linkType: hard - -"@bcoe/v8-coverage@npm:^0.2.3": - version: 0.2.3 - resolution: "@bcoe/v8-coverage@npm:0.2.3" - checksum: 10c0/6b80ae4cb3db53f486da2dc63b6e190a74c8c3cca16bb2733f234a0b6a9382b09b146488ae08e2b22cf00f6c83e20f3e040a2f7894f05c045c946d6a090b1d52 - languageName: node - linkType: hard - -"@colors/colors@npm:1.6.0, @colors/colors@npm:^1.6.0": - version: 1.6.0 - resolution: "@colors/colors@npm:1.6.0" - checksum: 10c0/9328a0778a5b0db243af54455b79a69e3fb21122d6c15ef9e9fcc94881d8d17352d8b2b2590f9bdd46fac5c2d6c1636dcfc14358a20c70e22daf89e1a759b629 - languageName: node - linkType: hard - -"@dabh/diagnostics@npm:^2.0.2": - version: 2.0.3 - resolution: "@dabh/diagnostics@npm:2.0.3" - dependencies: - colorspace: "npm:1.1.x" - enabled: "npm:2.0.x" - kuler: "npm:^2.0.0" - checksum: 10c0/a5133df8492802465ed01f2f0a5784585241a1030c362d54a602ed1839816d6c93d71dde05cf2ddb4fd0796238c19774406bd62fa2564b637907b495f52425fe - languageName: node - linkType: hard - -"@eslint-community/eslint-utils@npm:^4.2.0": - version: 4.4.0 - resolution: "@eslint-community/eslint-utils@npm:4.4.0" - dependencies: - eslint-visitor-keys: "npm:^3.3.0" - peerDependencies: - eslint: ^6.0.0 || ^7.0.0 || >=8.0.0 - checksum: 10c0/7e559c4ce59cd3a06b1b5a517b593912e680a7f981ae7affab0d01d709e99cd5647019be8fafa38c350305bc32f1f7d42c7073edde2ab536c745e365f37b607e - languageName: node - linkType: hard - -"@eslint-community/regexpp@npm:^4.4.0, @eslint-community/regexpp@npm:^4.6.1": - version: 4.10.1 - resolution: "@eslint-community/regexpp@npm:4.10.1" - checksum: 10c0/f59376025d0c91dd9fdf18d33941df499292a3ecba3e9889c360f3f6590197d30755604588786cdca0f9030be315a26b206014af4b65c0ff85b4ec49043de780 - languageName: node - linkType: hard - -"@eslint/eslintrc@npm:^2.1.4": - version: 2.1.4 - resolution: "@eslint/eslintrc@npm:2.1.4" - dependencies: - ajv: "npm:^6.12.4" - debug: "npm:^4.3.2" - espree: "npm:^9.6.0" - globals: "npm:^13.19.0" - ignore: "npm:^5.2.0" - import-fresh: "npm:^3.2.1" - js-yaml: "npm:^4.1.0" - minimatch: "npm:^3.1.2" - strip-json-comments: "npm:^3.1.1" - checksum: 10c0/32f67052b81768ae876c84569ffd562491ec5a5091b0c1e1ca1e0f3c24fb42f804952fdd0a137873bc64303ba368a71ba079a6f691cee25beee9722d94cc8573 - languageName: node - linkType: hard - -"@eslint/js@npm:8.57.0": - version: 8.57.0 - resolution: "@eslint/js@npm:8.57.0" - checksum: 10c0/9a518bb8625ba3350613903a6d8c622352ab0c6557a59fe6ff6178bf882bf57123f9d92aa826ee8ac3ee74b9c6203fe630e9ee00efb03d753962dcf65ee4bd94 - languageName: node - linkType: hard - -"@humanwhocodes/config-array@npm:^0.11.14": - version: 0.11.14 - resolution: "@humanwhocodes/config-array@npm:0.11.14" - dependencies: - "@humanwhocodes/object-schema": "npm:^2.0.2" - debug: "npm:^4.3.1" - minimatch: "npm:^3.0.5" - checksum: 10c0/66f725b4ee5fdd8322c737cb5013e19fac72d4d69c8bf4b7feb192fcb83442b035b92186f8e9497c220e58b2d51a080f28a73f7899bc1ab288c3be172c467541 - languageName: node - linkType: hard - -"@humanwhocodes/module-importer@npm:^1.0.1": - version: 1.0.1 - resolution: "@humanwhocodes/module-importer@npm:1.0.1" - checksum: 10c0/909b69c3b86d482c26b3359db16e46a32e0fb30bd306a3c176b8313b9e7313dba0f37f519de6aa8b0a1921349e505f259d19475e123182416a506d7f87e7f529 - languageName: node - linkType: hard - -"@humanwhocodes/object-schema@npm:^2.0.2": - version: 2.0.3 - resolution: "@humanwhocodes/object-schema@npm:2.0.3" - checksum: 10c0/80520eabbfc2d32fe195a93557cef50dfe8c8905de447f022675aaf66abc33ae54098f5ea78548d925aa671cd4ab7c7daa5ad704fe42358c9b5e7db60f80696c - languageName: node - linkType: hard - -"@isaacs/cliui@npm:^8.0.2": - version: 8.0.2 - resolution: "@isaacs/cliui@npm:8.0.2" - dependencies: - string-width: "npm:^5.1.2" - string-width-cjs: "npm:string-width@^4.2.0" - strip-ansi: "npm:^7.0.1" - strip-ansi-cjs: "npm:strip-ansi@^6.0.1" - wrap-ansi: "npm:^8.1.0" - wrap-ansi-cjs: "npm:wrap-ansi@^7.0.0" - checksum: 10c0/b1bf42535d49f11dc137f18d5e4e63a28c5569de438a221c369483731e9dac9fb797af554e8bf02b6192d1e5eba6e6402cf93900c3d0ac86391d00d04876789e - languageName: node - linkType: hard - -"@istanbuljs/load-nyc-config@npm:^1.0.0": - version: 1.1.0 - resolution: "@istanbuljs/load-nyc-config@npm:1.1.0" - dependencies: - camelcase: "npm:^5.3.1" - find-up: "npm:^4.1.0" - get-package-type: "npm:^0.1.0" - js-yaml: "npm:^3.13.1" - resolve-from: "npm:^5.0.0" - checksum: 10c0/dd2a8b094887da5a1a2339543a4933d06db2e63cbbc2e288eb6431bd832065df0c099d091b6a67436e71b7d6bf85f01ce7c15f9253b4cbebcc3b9a496165ba42 - languageName: node - linkType: hard - -"@istanbuljs/schema@npm:^0.1.2, @istanbuljs/schema@npm:^0.1.3": - version: 0.1.3 - resolution: "@istanbuljs/schema@npm:0.1.3" - checksum: 10c0/61c5286771676c9ca3eb2bd8a7310a9c063fb6e0e9712225c8471c582d157392c88f5353581c8c9adbe0dff98892317d2fdfc56c3499aa42e0194405206a963a - languageName: node - linkType: hard - -"@jest/console@npm:^29.7.0": - version: 29.7.0 - resolution: "@jest/console@npm:29.7.0" - dependencies: - "@jest/types": "npm:^29.6.3" - "@types/node": "npm:*" - chalk: "npm:^4.0.0" - jest-message-util: "npm:^29.7.0" - jest-util: "npm:^29.7.0" - slash: "npm:^3.0.0" - checksum: 10c0/7be408781d0a6f657e969cbec13b540c329671819c2f57acfad0dae9dbfe2c9be859f38fe99b35dba9ff1536937dc6ddc69fdcd2794812fa3c647a1619797f6c - languageName: node - linkType: hard - -"@jest/core@npm:^29.7.0": - version: 29.7.0 - resolution: "@jest/core@npm:29.7.0" - dependencies: - "@jest/console": "npm:^29.7.0" - "@jest/reporters": "npm:^29.7.0" - "@jest/test-result": "npm:^29.7.0" - "@jest/transform": "npm:^29.7.0" - "@jest/types": "npm:^29.6.3" - "@types/node": "npm:*" - ansi-escapes: "npm:^4.2.1" - chalk: "npm:^4.0.0" - ci-info: "npm:^3.2.0" - exit: "npm:^0.1.2" - graceful-fs: "npm:^4.2.9" - jest-changed-files: "npm:^29.7.0" - jest-config: "npm:^29.7.0" - jest-haste-map: "npm:^29.7.0" - jest-message-util: "npm:^29.7.0" - jest-regex-util: "npm:^29.6.3" - jest-resolve: "npm:^29.7.0" - jest-resolve-dependencies: "npm:^29.7.0" - jest-runner: "npm:^29.7.0" - jest-runtime: "npm:^29.7.0" - jest-snapshot: "npm:^29.7.0" - jest-util: "npm:^29.7.0" - jest-validate: "npm:^29.7.0" - jest-watcher: "npm:^29.7.0" - micromatch: "npm:^4.0.4" - pretty-format: "npm:^29.7.0" - slash: "npm:^3.0.0" - strip-ansi: "npm:^6.0.0" - peerDependencies: - node-notifier: ^8.0.1 || ^9.0.0 || ^10.0.0 - peerDependenciesMeta: - node-notifier: - optional: true - checksum: 10c0/934f7bf73190f029ac0f96662c85cd276ec460d407baf6b0dbaec2872e157db4d55a7ee0b1c43b18874602f662b37cb973dda469a4e6d88b4e4845b521adeeb2 - languageName: node - linkType: hard - -"@jest/environment@npm:^29.7.0": - version: 29.7.0 - resolution: "@jest/environment@npm:29.7.0" - dependencies: - "@jest/fake-timers": "npm:^29.7.0" - "@jest/types": "npm:^29.6.3" - "@types/node": "npm:*" - jest-mock: "npm:^29.7.0" - checksum: 10c0/c7b1b40c618f8baf4d00609022d2afa086d9c6acc706f303a70bb4b67275868f620ad2e1a9efc5edd418906157337cce50589a627a6400bbdf117d351b91ef86 - languageName: node - linkType: hard - -"@jest/expect-utils@npm:^29.7.0": - version: 29.7.0 - resolution: "@jest/expect-utils@npm:29.7.0" - dependencies: - jest-get-type: "npm:^29.6.3" - checksum: 10c0/60b79d23a5358dc50d9510d726443316253ecda3a7fb8072e1526b3e0d3b14f066ee112db95699b7a43ad3f0b61b750c72e28a5a1cac361d7a2bb34747fa938a - languageName: node - linkType: hard - -"@jest/expect@npm:^29.7.0": - version: 29.7.0 - resolution: "@jest/expect@npm:29.7.0" - dependencies: - expect: "npm:^29.7.0" - jest-snapshot: "npm:^29.7.0" - checksum: 10c0/b41f193fb697d3ced134349250aed6ccea075e48c4f803159db102b826a4e473397c68c31118259868fd69a5cba70e97e1c26d2c2ff716ca39dc73a2ccec037e - languageName: node - linkType: hard - -"@jest/fake-timers@npm:^29.7.0": - version: 29.7.0 - resolution: "@jest/fake-timers@npm:29.7.0" - dependencies: - "@jest/types": "npm:^29.6.3" - "@sinonjs/fake-timers": "npm:^10.0.2" - "@types/node": "npm:*" - jest-message-util: "npm:^29.7.0" - jest-mock: "npm:^29.7.0" - jest-util: "npm:^29.7.0" - checksum: 10c0/cf0a8bcda801b28dc2e2b2ba36302200ee8104a45ad7a21e6c234148932f826cb3bc57c8df3b7b815aeea0861d7b6ca6f0d4778f93b9219398ef28749e03595c - languageName: node - linkType: hard - -"@jest/globals@npm:^29.7.0": - version: 29.7.0 - resolution: "@jest/globals@npm:29.7.0" - dependencies: - "@jest/environment": "npm:^29.7.0" - "@jest/expect": "npm:^29.7.0" - "@jest/types": "npm:^29.6.3" - jest-mock: "npm:^29.7.0" - checksum: 10c0/a385c99396878fe6e4460c43bd7bb0a5cc52befb462cc6e7f2a3810f9e7bcce7cdeb51908fd530391ee452dc856c98baa2c5f5fa8a5b30b071d31ef7f6955cea - languageName: node - linkType: hard - -"@jest/reporters@npm:^29.7.0": - version: 29.7.0 - resolution: "@jest/reporters@npm:29.7.0" - dependencies: - "@bcoe/v8-coverage": "npm:^0.2.3" - "@jest/console": "npm:^29.7.0" - "@jest/test-result": "npm:^29.7.0" - "@jest/transform": "npm:^29.7.0" - "@jest/types": "npm:^29.6.3" - "@jridgewell/trace-mapping": "npm:^0.3.18" - "@types/node": "npm:*" - chalk: "npm:^4.0.0" - collect-v8-coverage: "npm:^1.0.0" - exit: "npm:^0.1.2" - glob: "npm:^7.1.3" - graceful-fs: "npm:^4.2.9" - istanbul-lib-coverage: "npm:^3.0.0" - istanbul-lib-instrument: "npm:^6.0.0" - istanbul-lib-report: "npm:^3.0.0" - istanbul-lib-source-maps: "npm:^4.0.0" - istanbul-reports: "npm:^3.1.3" - jest-message-util: "npm:^29.7.0" - jest-util: "npm:^29.7.0" - jest-worker: "npm:^29.7.0" - slash: "npm:^3.0.0" - string-length: "npm:^4.0.1" - strip-ansi: "npm:^6.0.0" - v8-to-istanbul: "npm:^9.0.1" - peerDependencies: - node-notifier: ^8.0.1 || ^9.0.0 || ^10.0.0 - peerDependenciesMeta: - node-notifier: - optional: true - checksum: 10c0/a754402a799541c6e5aff2c8160562525e2a47e7d568f01ebfc4da66522de39cbb809bbb0a841c7052e4270d79214e70aec3c169e4eae42a03bc1a8a20cb9fa2 - languageName: node - linkType: hard - -"@jest/schemas@npm:^29.6.3": - version: 29.6.3 - resolution: "@jest/schemas@npm:29.6.3" - dependencies: - "@sinclair/typebox": "npm:^0.27.8" - checksum: 10c0/b329e89cd5f20b9278ae1233df74016ebf7b385e0d14b9f4c1ad18d096c4c19d1e687aa113a9c976b16ec07f021ae53dea811fb8c1248a50ac34fbe009fdf6be - languageName: node - linkType: hard - -"@jest/source-map@npm:^29.6.3": - version: 29.6.3 - resolution: "@jest/source-map@npm:29.6.3" - dependencies: - "@jridgewell/trace-mapping": "npm:^0.3.18" - callsites: "npm:^3.0.0" - graceful-fs: "npm:^4.2.9" - checksum: 10c0/a2f177081830a2e8ad3f2e29e20b63bd40bade294880b595acf2fc09ec74b6a9dd98f126a2baa2bf4941acd89b13a4ade5351b3885c224107083a0059b60a219 - languageName: node - linkType: hard - -"@jest/test-result@npm:^29.7.0": - version: 29.7.0 - resolution: "@jest/test-result@npm:29.7.0" - dependencies: - "@jest/console": "npm:^29.7.0" - "@jest/types": "npm:^29.6.3" - "@types/istanbul-lib-coverage": "npm:^2.0.0" - collect-v8-coverage: "npm:^1.0.0" - checksum: 10c0/7de54090e54a674ca173470b55dc1afdee994f2d70d185c80236003efd3fa2b753fff51ffcdda8e2890244c411fd2267529d42c4a50a8303755041ee493e6a04 - languageName: node - linkType: hard - -"@jest/test-sequencer@npm:^29.7.0": - version: 29.7.0 - resolution: "@jest/test-sequencer@npm:29.7.0" - dependencies: - "@jest/test-result": "npm:^29.7.0" - graceful-fs: "npm:^4.2.9" - jest-haste-map: "npm:^29.7.0" - slash: "npm:^3.0.0" - checksum: 10c0/593a8c4272797bb5628984486080cbf57aed09c7cfdc0a634e8c06c38c6bef329c46c0016e84555ee55d1cd1f381518cf1890990ff845524c1123720c8c1481b - languageName: node - linkType: hard - -"@jest/transform@npm:^29.7.0": - version: 29.7.0 - resolution: "@jest/transform@npm:29.7.0" - dependencies: - "@babel/core": "npm:^7.11.6" - "@jest/types": "npm:^29.6.3" - "@jridgewell/trace-mapping": "npm:^0.3.18" - babel-plugin-istanbul: "npm:^6.1.1" - chalk: "npm:^4.0.0" - convert-source-map: "npm:^2.0.0" - fast-json-stable-stringify: "npm:^2.1.0" - graceful-fs: "npm:^4.2.9" - jest-haste-map: "npm:^29.7.0" - jest-regex-util: "npm:^29.6.3" - jest-util: "npm:^29.7.0" - micromatch: "npm:^4.0.4" - pirates: "npm:^4.0.4" - slash: "npm:^3.0.0" - write-file-atomic: "npm:^4.0.2" - checksum: 10c0/7f4a7f73dcf45dfdf280c7aa283cbac7b6e5a904813c3a93ead7e55873761fc20d5c4f0191d2019004fac6f55f061c82eb3249c2901164ad80e362e7a7ede5a6 - languageName: node - linkType: hard - -"@jest/types@npm:^29.6.3": - version: 29.6.3 - resolution: "@jest/types@npm:29.6.3" - dependencies: - "@jest/schemas": "npm:^29.6.3" - "@types/istanbul-lib-coverage": "npm:^2.0.0" - "@types/istanbul-reports": "npm:^3.0.0" - "@types/node": "npm:*" - "@types/yargs": "npm:^17.0.8" - chalk: "npm:^4.0.0" - checksum: 10c0/ea4e493dd3fb47933b8ccab201ae573dcc451f951dc44ed2a86123cd8541b82aa9d2b1031caf9b1080d6673c517e2dcc25a44b2dc4f3fbc37bfc965d444888c0 - languageName: node - linkType: hard - -"@jridgewell/gen-mapping@npm:^0.3.5": - version: 0.3.5 - resolution: "@jridgewell/gen-mapping@npm:0.3.5" - dependencies: - "@jridgewell/set-array": "npm:^1.2.1" - "@jridgewell/sourcemap-codec": "npm:^1.4.10" - "@jridgewell/trace-mapping": "npm:^0.3.24" - checksum: 10c0/1be4fd4a6b0f41337c4f5fdf4afc3bd19e39c3691924817108b82ffcb9c9e609c273f936932b9fba4b3a298ce2eb06d9bff4eb1cc3bd81c4f4ee1b4917e25feb - languageName: node - linkType: hard - -"@jridgewell/resolve-uri@npm:^3.1.0": - version: 3.1.2 - resolution: "@jridgewell/resolve-uri@npm:3.1.2" - checksum: 10c0/d502e6fb516b35032331406d4e962c21fe77cdf1cbdb49c6142bcbd9e30507094b18972778a6e27cbad756209cfe34b1a27729e6fa08a2eb92b33943f680cf1e - languageName: node - linkType: hard - -"@jridgewell/set-array@npm:^1.2.1": - version: 1.2.1 - resolution: "@jridgewell/set-array@npm:1.2.1" - checksum: 10c0/2a5aa7b4b5c3464c895c802d8ae3f3d2b92fcbe84ad12f8d0bfbb1f5ad006717e7577ee1fd2eac00c088abe486c7adb27976f45d2941ff6b0b92b2c3302c60f4 - languageName: node - linkType: hard - -"@jridgewell/sourcemap-codec@npm:^1.4.10, @jridgewell/sourcemap-codec@npm:^1.4.14": - version: 1.4.15 - resolution: "@jridgewell/sourcemap-codec@npm:1.4.15" - checksum: 10c0/0c6b5ae663087558039052a626d2d7ed5208da36cfd707dcc5cea4a07cfc918248403dcb5989a8f7afaf245ce0573b7cc6fd94c4a30453bd10e44d9363940ba5 - languageName: node - linkType: hard - -"@jridgewell/trace-mapping@npm:^0.3.12, @jridgewell/trace-mapping@npm:^0.3.18, @jridgewell/trace-mapping@npm:^0.3.24, @jridgewell/trace-mapping@npm:^0.3.25": - version: 0.3.25 - resolution: "@jridgewell/trace-mapping@npm:0.3.25" - dependencies: - "@jridgewell/resolve-uri": "npm:^3.1.0" - "@jridgewell/sourcemap-codec": "npm:^1.4.14" - checksum: 10c0/3d1ce6ebc69df9682a5a8896b414c6537e428a1d68b02fcc8363b04284a8ca0df04d0ee3013132252ab14f2527bc13bea6526a912ecb5658f0e39fd2860b4df4 - languageName: node - linkType: hard - -"@mapbox/node-pre-gyp@npm:^1.0.11": - version: 1.0.11 - resolution: "@mapbox/node-pre-gyp@npm:1.0.11" - dependencies: - detect-libc: "npm:^2.0.0" - https-proxy-agent: "npm:^5.0.0" - make-dir: "npm:^3.1.0" - node-fetch: "npm:^2.6.7" - nopt: "npm:^5.0.0" - npmlog: "npm:^5.0.1" - rimraf: "npm:^3.0.2" - semver: "npm:^7.3.5" - tar: "npm:^6.1.11" - bin: - node-pre-gyp: bin/node-pre-gyp - checksum: 10c0/2b24b93c31beca1c91336fa3b3769fda98e202fb7f9771f0f4062588d36dcc30fcf8118c36aa747fa7f7610d8cf601872bdaaf62ce7822bb08b545d1bbe086cc - languageName: node - linkType: hard - -"@nodelib/fs.scandir@npm:2.1.5": - version: 2.1.5 - resolution: "@nodelib/fs.scandir@npm:2.1.5" - dependencies: - "@nodelib/fs.stat": "npm:2.0.5" - run-parallel: "npm:^1.1.9" - checksum: 10c0/732c3b6d1b1e967440e65f284bd06e5821fedf10a1bea9ed2bb75956ea1f30e08c44d3def9d6a230666574edbaf136f8cfd319c14fd1f87c66e6a44449afb2eb - languageName: node - linkType: hard - -"@nodelib/fs.stat@npm:2.0.5, @nodelib/fs.stat@npm:^2.0.2": - version: 2.0.5 - resolution: "@nodelib/fs.stat@npm:2.0.5" - checksum: 10c0/88dafe5e3e29a388b07264680dc996c17f4bda48d163a9d4f5c1112979f0ce8ec72aa7116122c350b4e7976bc5566dc3ddb579be1ceaacc727872eb4ed93926d - languageName: node - linkType: hard - -"@nodelib/fs.walk@npm:^1.2.3, @nodelib/fs.walk@npm:^1.2.8": - version: 1.2.8 - resolution: "@nodelib/fs.walk@npm:1.2.8" - dependencies: - "@nodelib/fs.scandir": "npm:2.1.5" - fastq: "npm:^1.6.0" - checksum: 10c0/db9de047c3bb9b51f9335a7bb46f4fcfb6829fb628318c12115fbaf7d369bfce71c15b103d1fc3b464812d936220ee9bc1c8f762d032c9f6be9acc99249095b1 - languageName: node - linkType: hard - -"@npmcli/agent@npm:^2.0.0": - version: 2.2.2 - resolution: "@npmcli/agent@npm:2.2.2" - dependencies: - agent-base: "npm:^7.1.0" - http-proxy-agent: "npm:^7.0.0" - https-proxy-agent: "npm:^7.0.1" - lru-cache: "npm:^10.0.1" - socks-proxy-agent: "npm:^8.0.3" - checksum: 10c0/325e0db7b287d4154ecd164c0815c08007abfb07653cc57bceded17bb7fd240998a3cbdbe87d700e30bef494885eccc725ab73b668020811d56623d145b524ae - languageName: node - linkType: hard - -"@npmcli/fs@npm:^3.1.0": - version: 3.1.1 - resolution: "@npmcli/fs@npm:3.1.1" - dependencies: - semver: "npm:^7.3.5" - checksum: 10c0/c37a5b4842bfdece3d14dfdb054f73fe15ed2d3da61b34ff76629fb5b1731647c49166fd2a8bf8b56fcfa51200382385ea8909a3cbecdad612310c114d3f6c99 - languageName: node - linkType: hard - -"@pkgjs/parseargs@npm:^0.11.0": - version: 0.11.0 - resolution: "@pkgjs/parseargs@npm:0.11.0" - checksum: 10c0/5bd7576bb1b38a47a7fc7b51ac9f38748e772beebc56200450c4a817d712232b8f1d3ef70532c80840243c657d491cf6a6be1e3a214cff907645819fdc34aadd - languageName: node - linkType: hard - -"@sinclair/typebox@npm:^0.27.8": - version: 0.27.8 - resolution: "@sinclair/typebox@npm:0.27.8" - checksum: 10c0/ef6351ae073c45c2ac89494dbb3e1f87cc60a93ce4cde797b782812b6f97da0d620ae81973f104b43c9b7eaa789ad20ba4f6a1359f1cc62f63729a55a7d22d4e - languageName: node - linkType: hard - -"@sinonjs/commons@npm:^3.0.0": - version: 3.0.1 - resolution: "@sinonjs/commons@npm:3.0.1" - dependencies: - type-detect: "npm:4.0.8" - checksum: 10c0/1227a7b5bd6c6f9584274db996d7f8cee2c8c350534b9d0141fc662eaf1f292ea0ae3ed19e5e5271c8fd390d27e492ca2803acd31a1978be2cdc6be0da711403 - languageName: node - linkType: hard - -"@sinonjs/fake-timers@npm:^10.0.2": - version: 10.3.0 - resolution: "@sinonjs/fake-timers@npm:10.3.0" - dependencies: - "@sinonjs/commons": "npm:^3.0.0" - checksum: 10c0/2e2fb6cc57f227912814085b7b01fede050cd4746ea8d49a1e44d5a0e56a804663b0340ae2f11af7559ea9bf4d087a11f2f646197a660ea3cb04e19efc04aa63 - languageName: node - linkType: hard - -"@types/babel__core@npm:^7.1.14": - version: 7.20.5 - resolution: "@types/babel__core@npm:7.20.5" - dependencies: - "@babel/parser": "npm:^7.20.7" - "@babel/types": "npm:^7.20.7" - "@types/babel__generator": "npm:*" - "@types/babel__template": "npm:*" - "@types/babel__traverse": "npm:*" - checksum: 10c0/bdee3bb69951e833a4b811b8ee9356b69a61ed5b7a23e1a081ec9249769117fa83aaaf023bb06562a038eb5845155ff663e2d5c75dd95c1d5ccc91db012868ff - languageName: node - linkType: hard - -"@types/babel__generator@npm:*": - version: 7.6.8 - resolution: "@types/babel__generator@npm:7.6.8" - dependencies: - "@babel/types": "npm:^7.0.0" - checksum: 10c0/f0ba105e7d2296bf367d6e055bb22996886c114261e2cb70bf9359556d0076c7a57239d019dee42bb063f565bade5ccb46009bce2044b2952d964bf9a454d6d2 - languageName: node - linkType: hard - -"@types/babel__template@npm:*": - version: 7.4.4 - resolution: "@types/babel__template@npm:7.4.4" - dependencies: - "@babel/parser": "npm:^7.1.0" - "@babel/types": "npm:^7.0.0" - checksum: 10c0/cc84f6c6ab1eab1427e90dd2b76ccee65ce940b778a9a67be2c8c39e1994e6f5bbc8efa309f6cea8dc6754994524cd4d2896558df76d92e7a1f46ecffee7112b - languageName: node - linkType: hard - -"@types/babel__traverse@npm:*, @types/babel__traverse@npm:^7.0.6": - version: 7.20.6 - resolution: "@types/babel__traverse@npm:7.20.6" - dependencies: - "@babel/types": "npm:^7.20.7" - checksum: 10c0/7ba7db61a53e28cac955aa99af280d2600f15a8c056619c05b6fc911cbe02c61aa4f2823299221b23ce0cce00b294c0e5f618ec772aa3f247523c2e48cf7b888 - languageName: node - linkType: hard - -"@types/bcrypt@npm:^5.0.2": - version: 5.0.2 - resolution: "@types/bcrypt@npm:5.0.2" - dependencies: - "@types/node": "npm:*" - checksum: 10c0/dd7f05e183b9b1fc08ec499069febf197ab8e9c720766b5bbb5628395082e248f9a444c60882fe7788361fcadc302e21e055ab9c26a300f100e08791c353e6aa - languageName: node - linkType: hard - -"@types/better-sqlite3@npm:^7.6.12": - version: 7.6.12 - resolution: "@types/better-sqlite3@npm:7.6.12" - dependencies: - "@types/node": "npm:*" - checksum: 10c0/5367de7492e2c697aa20cc4024ba26210971d15f60c01ef691eddfbbfd39ccf9f80d5129fd7fd6c76c98804739325e23d2b156b0eac8f5a7665ba374a08ac1e7 - languageName: node - linkType: hard - -"@types/body-parser@npm:*": - version: 1.19.5 - resolution: "@types/body-parser@npm:1.19.5" - dependencies: - "@types/connect": "npm:*" - "@types/node": "npm:*" - checksum: 10c0/aebeb200f25e8818d8cf39cd0209026750d77c9b85381cdd8deeb50913e4d18a1ebe4b74ca9b0b4d21952511eeaba5e9fbbf739b52731a2061e206ec60d568df - languageName: node - linkType: hard - -"@types/connect@npm:*": - version: 3.4.38 - resolution: "@types/connect@npm:3.4.38" - dependencies: - "@types/node": "npm:*" - checksum: 10c0/2e1cdba2c410f25649e77856505cd60223250fa12dff7a503e492208dbfdd25f62859918f28aba95315251fd1f5e1ffbfca1e25e73037189ab85dd3f8d0a148c - languageName: node - linkType: hard - -"@types/cookiejar@npm:^2.1.5": - version: 2.1.5 - resolution: "@types/cookiejar@npm:2.1.5" - checksum: 10c0/af38c3d84aebb3ccc6e46fb6afeeaac80fb26e63a487dd4db5a8b87e6ad3d4b845ba1116b2ae90d6f886290a36200fa433d8b1f6fe19c47da6b81872ce9a2764 - languageName: node - linkType: hard - -"@types/cors@npm:^2.8.13": - version: 2.8.17 - resolution: "@types/cors@npm:2.8.17" - dependencies: - "@types/node": "npm:*" - checksum: 10c0/457364c28c89f3d9ed34800e1de5c6eaaf344d1bb39af122f013322a50bc606eb2aa6f63de4e41a7a08ba7ef454473926c94a830636723da45bf786df032696d - languageName: node - linkType: hard - -"@types/express-actuator@npm:^1.8.0": - version: 1.8.3 - resolution: "@types/express-actuator@npm:1.8.3" - dependencies: - "@types/express": "npm:*" - checksum: 10c0/e7a5ffae28aa89c636edc75594adbf63bc3a0f86d27d0bdd4567e8ac91d6de8d0b76b85009ab65a9714625cfc443374eab227b11e58cb4450093500123eef0a2 - languageName: node - linkType: hard - -"@types/express-serve-static-core@npm:^4.17.33": - version: 4.19.3 - resolution: "@types/express-serve-static-core@npm:4.19.3" - dependencies: - "@types/node": "npm:*" - "@types/qs": "npm:*" - "@types/range-parser": "npm:*" - "@types/send": "npm:*" - checksum: 10c0/5d2a1fb96a17a8e0e8c59325dfeb6d454bbc5c9b9b6796eec0397ddf9dbd262892040d5da3d72b5d7148f34bb3fcd438faf1b37fcba8c5a03e75fae491ad1edf - languageName: node - linkType: hard - -"@types/express@npm:*, @types/express@npm:^4.17.17": - version: 4.17.21 - resolution: "@types/express@npm:4.17.21" - dependencies: - "@types/body-parser": "npm:*" - "@types/express-serve-static-core": "npm:^4.17.33" - "@types/qs": "npm:*" - "@types/serve-static": "npm:*" - checksum: 10c0/12e562c4571da50c7d239e117e688dc434db1bac8be55613294762f84fd77fbd0658ccd553c7d3ab02408f385bc93980992369dd30e2ecd2c68c358e6af8fabf - languageName: node - linkType: hard - -"@types/graceful-fs@npm:^4.1.3": - version: 4.1.9 - resolution: "@types/graceful-fs@npm:4.1.9" - dependencies: - "@types/node": "npm:*" - checksum: 10c0/235d2fc69741448e853333b7c3d1180a966dd2b8972c8cbcd6b2a0c6cd7f8d582ab2b8e58219dbc62cce8f1b40aa317ff78ea2201cdd8249da5025adebed6f0b - languageName: node - linkType: hard - -"@types/http-errors@npm:*": - version: 2.0.4 - resolution: "@types/http-errors@npm:2.0.4" - checksum: 10c0/494670a57ad4062fee6c575047ad5782506dd35a6b9ed3894cea65830a94367bd84ba302eb3dde331871f6d70ca287bfedb1b2cf658e6132cd2cbd427ab56836 - languageName: node - linkType: hard - -"@types/istanbul-lib-coverage@npm:*, @types/istanbul-lib-coverage@npm:^2.0.0, @types/istanbul-lib-coverage@npm:^2.0.1": - version: 2.0.6 - resolution: "@types/istanbul-lib-coverage@npm:2.0.6" - checksum: 10c0/3948088654f3eeb45363f1db158354fb013b362dba2a5c2c18c559484d5eb9f6fd85b23d66c0a7c2fcfab7308d0a585b14dadaca6cc8bf89ebfdc7f8f5102fb7 - languageName: node - linkType: hard - -"@types/istanbul-lib-report@npm:*": - version: 3.0.3 - resolution: "@types/istanbul-lib-report@npm:3.0.3" - dependencies: - "@types/istanbul-lib-coverage": "npm:*" - checksum: 10c0/247e477bbc1a77248f3c6de5dadaae85ff86ac2d76c5fc6ab1776f54512a745ff2a5f791d22b942e3990ddbd40f3ef5289317c4fca5741bedfaa4f01df89051c - languageName: node - linkType: hard - -"@types/istanbul-reports@npm:^3.0.0": - version: 3.0.4 - resolution: "@types/istanbul-reports@npm:3.0.4" - dependencies: - "@types/istanbul-lib-report": "npm:*" - checksum: 10c0/1647fd402aced5b6edac87274af14ebd6b3a85447ef9ad11853a70fd92a98d35f81a5d3ea9fcb5dbb5834e800c6e35b64475e33fcae6bfa9acc70d61497c54ee - languageName: node - linkType: hard - -"@types/jest@npm:^29.2.3": - version: 29.5.12 - resolution: "@types/jest@npm:29.5.12" - dependencies: - expect: "npm:^29.0.0" - pretty-format: "npm:^29.0.0" - checksum: 10c0/25fc8e4c611fa6c4421e631432e9f0a6865a8cb07c9815ec9ac90d630271cad773b2ee5fe08066f7b95bebd18bb967f8ce05d018ee9ab0430f9dfd1d84665b6f - languageName: node - linkType: hard - -"@types/json-schema@npm:^7.0.9": - version: 7.0.15 - resolution: "@types/json-schema@npm:7.0.15" - checksum: 10c0/a996a745e6c5d60292f36731dd41341339d4eeed8180bb09226e5c8d23759067692b1d88e5d91d72ee83dfc00d3aca8e7bd43ea120516c17922cbcb7c3e252db - languageName: node - linkType: hard - -"@types/methods@npm:^1.1.4": - version: 1.1.4 - resolution: "@types/methods@npm:1.1.4" - checksum: 10c0/a78534d79c300718298bfff92facd07bf38429c66191f640c1db4c9cff1e36f819304298a96f7536b6512bfc398e5c3e6b831405e138cd774b88ad7be78d682a - languageName: node - linkType: hard - -"@types/mime@npm:^1": - version: 1.3.5 - resolution: "@types/mime@npm:1.3.5" - checksum: 10c0/c2ee31cd9b993804df33a694d5aa3fa536511a49f2e06eeab0b484fef59b4483777dbb9e42a4198a0809ffbf698081fdbca1e5c2218b82b91603dfab10a10fbc - languageName: node - linkType: hard - -"@types/node@npm:*": - version: 20.14.5 - resolution: "@types/node@npm:20.14.5" - dependencies: - undici-types: "npm:~5.26.4" - checksum: 10c0/06a8c304b5f7f190d4497807dc67ad09ee7b14ea2996bfdc823553c624698d8cab1ef9d16f8b764f20cb9eb11caa0e832787741e9ef70e1c89d620797ab28436 - languageName: node - linkType: hard - -"@types/node@npm:^17.0.45": - version: 17.0.45 - resolution: "@types/node@npm:17.0.45" - checksum: 10c0/0db377133d709b33a47892581a21a41cd7958f22723a3cc6c71d55ac018121382de42fbfc7970d5ae3e7819dbe5f40e1c6a5174aedf7e7964e9cb8fa72b580b0 - languageName: node - linkType: hard - -"@types/qs@npm:*": - version: 6.9.15 - resolution: "@types/qs@npm:6.9.15" - checksum: 10c0/49c5ff75ca3adb18a1939310042d273c9fc55920861bd8e5100c8a923b3cda90d759e1a95e18334092da1c8f7b820084687770c83a1ccef04fb2c6908117c823 - languageName: node - linkType: hard - -"@types/range-parser@npm:*": - version: 1.2.7 - resolution: "@types/range-parser@npm:1.2.7" - checksum: 10c0/361bb3e964ec5133fa40644a0b942279ed5df1949f21321d77de79f48b728d39253e5ce0408c9c17e4e0fd95ca7899da36841686393b9f7a1e209916e9381a3c - languageName: node - linkType: hard - -"@types/semver@npm:^7.3.12": - version: 7.5.8 - resolution: "@types/semver@npm:7.5.8" - checksum: 10c0/8663ff927234d1c5fcc04b33062cb2b9fcfbe0f5f351ed26c4d1e1581657deebd506b41ff7fdf89e787e3d33ce05854bc01686379b89e9c49b564c4cfa988efa - languageName: node - linkType: hard - -"@types/send@npm:*": - version: 0.17.4 - resolution: "@types/send@npm:0.17.4" - dependencies: - "@types/mime": "npm:^1" - "@types/node": "npm:*" - checksum: 10c0/7f17fa696cb83be0a104b04b424fdedc7eaba1c9a34b06027239aba513b398a0e2b7279778af521f516a397ced417c96960e5f50fcfce40c4bc4509fb1a5883c - languageName: node - linkType: hard - -"@types/serve-static@npm:*": - version: 1.15.7 - resolution: "@types/serve-static@npm:1.15.7" - dependencies: - "@types/http-errors": "npm:*" - "@types/node": "npm:*" - "@types/send": "npm:*" - checksum: 10c0/26ec864d3a626ea627f8b09c122b623499d2221bbf2f470127f4c9ebfe92bd8a6bb5157001372d4c4bd0dd37a1691620217d9dc4df5aa8f779f3fd996b1c60ae - languageName: node - linkType: hard - -"@types/stack-utils@npm:^2.0.0": - version: 2.0.3 - resolution: "@types/stack-utils@npm:2.0.3" - checksum: 10c0/1f4658385ae936330581bcb8aa3a066df03867d90281cdf89cc356d404bd6579be0f11902304e1f775d92df22c6dd761d4451c804b0a4fba973e06211e9bd77c - languageName: node - linkType: hard - -"@types/superagent@npm:*": - version: 8.1.7 - resolution: "@types/superagent@npm:8.1.7" - dependencies: - "@types/cookiejar": "npm:^2.1.5" - "@types/methods": "npm:^1.1.4" - "@types/node": "npm:*" - checksum: 10c0/4676d539f5feaaea9d39d7409c86ae9e15b92a43c28456aff9d9897e47e9fe5ebd3807600c5310f84fe5ebea30f3fe5e2b3b101a87821a478ca79e3a56fd8c9e - languageName: node - linkType: hard - -"@types/supertest@npm:^2.0.12": - version: 2.0.16 - resolution: "@types/supertest@npm:2.0.16" - dependencies: - "@types/superagent": "npm:*" - checksum: 10c0/e1b4a4d788c19cd92a3f2e6d0979fb0f679c49aefae2011895a4d9c35aa960d43463aca8783a0b3382bbf0b4eb7ceaf8752d7dc80b8f5a9644fa14e1b1bdbc90 - languageName: node - linkType: hard - -"@types/triple-beam@npm:^1.3.2": - version: 1.3.5 - resolution: "@types/triple-beam@npm:1.3.5" - checksum: 10c0/d5d7f25da612f6d79266f4f1bb9c1ef8f1684e9f60abab251e1261170631062b656ba26ff22631f2760caeafd372abc41e64867cde27fba54fafb73a35b9056a - languageName: node - linkType: hard - -"@types/uuid@npm:^9.0.0": - version: 9.0.8 - resolution: "@types/uuid@npm:9.0.8" - checksum: 10c0/b411b93054cb1d4361919579ef3508a1f12bf15b5fdd97337d3d351bece6c921b52b6daeef89b62340fd73fd60da407878432a1af777f40648cbe53a01723489 - languageName: node - linkType: hard - -"@types/yargs-parser@npm:*": - version: 21.0.3 - resolution: "@types/yargs-parser@npm:21.0.3" - checksum: 10c0/e71c3bd9d0b73ca82e10bee2064c384ab70f61034bbfb78e74f5206283fc16a6d85267b606b5c22cb2a3338373586786fed595b2009825d6a9115afba36560a0 - languageName: node - linkType: hard - -"@types/yargs@npm:^17.0.8": - version: 17.0.32 - resolution: "@types/yargs@npm:17.0.32" - dependencies: - "@types/yargs-parser": "npm:*" - checksum: 10c0/2095e8aad8a4e66b86147415364266b8d607a3b95b4239623423efd7e29df93ba81bb862784a6e08664f645cc1981b25fd598f532019174cd3e5e1e689e1cccf - languageName: node - linkType: hard - -"@typescript-eslint/eslint-plugin@npm:^5.51.0": - version: 5.62.0 - resolution: "@typescript-eslint/eslint-plugin@npm:5.62.0" - dependencies: - "@eslint-community/regexpp": "npm:^4.4.0" - "@typescript-eslint/scope-manager": "npm:5.62.0" - "@typescript-eslint/type-utils": "npm:5.62.0" - "@typescript-eslint/utils": "npm:5.62.0" - debug: "npm:^4.3.4" - graphemer: "npm:^1.4.0" - ignore: "npm:^5.2.0" - natural-compare-lite: "npm:^1.4.0" - semver: "npm:^7.3.7" - tsutils: "npm:^3.21.0" - peerDependencies: - "@typescript-eslint/parser": ^5.0.0 - eslint: ^6.0.0 || ^7.0.0 || ^8.0.0 - peerDependenciesMeta: - typescript: - optional: true - checksum: 10c0/3f40cb6bab5a2833c3544e4621b9fdacd8ea53420cadc1c63fac3b89cdf5c62be1e6b7bcf56976dede5db4c43830de298ced3db60b5494a3b961ca1b4bff9f2a - languageName: node - linkType: hard - -"@typescript-eslint/parser@npm:^5.51.0": - version: 5.62.0 - resolution: "@typescript-eslint/parser@npm:5.62.0" - dependencies: - "@typescript-eslint/scope-manager": "npm:5.62.0" - "@typescript-eslint/types": "npm:5.62.0" - "@typescript-eslint/typescript-estree": "npm:5.62.0" - debug: "npm:^4.3.4" - peerDependencies: - eslint: ^6.0.0 || ^7.0.0 || ^8.0.0 - peerDependenciesMeta: - typescript: - optional: true - checksum: 10c0/315194b3bf39beb9bd16c190956c46beec64b8371e18d6bb72002108b250983eb1e186a01d34b77eb4045f4941acbb243b16155fbb46881105f65e37dc9e24d4 - languageName: node - linkType: hard - -"@typescript-eslint/scope-manager@npm:5.62.0": - version: 5.62.0 - resolution: "@typescript-eslint/scope-manager@npm:5.62.0" - dependencies: - "@typescript-eslint/types": "npm:5.62.0" - "@typescript-eslint/visitor-keys": "npm:5.62.0" - checksum: 10c0/861253235576c1c5c1772d23cdce1418c2da2618a479a7de4f6114a12a7ca853011a1e530525d0931c355a8fd237b9cd828fac560f85f9623e24054fd024726f - languageName: node - linkType: hard - -"@typescript-eslint/type-utils@npm:5.62.0": - version: 5.62.0 - resolution: "@typescript-eslint/type-utils@npm:5.62.0" - dependencies: - "@typescript-eslint/typescript-estree": "npm:5.62.0" - "@typescript-eslint/utils": "npm:5.62.0" - debug: "npm:^4.3.4" - tsutils: "npm:^3.21.0" - peerDependencies: - eslint: "*" - peerDependenciesMeta: - typescript: - optional: true - checksum: 10c0/93112e34026069a48f0484b98caca1c89d9707842afe14e08e7390af51cdde87378df29d213d3bbd10a7cfe6f91b228031b56218515ce077bdb62ddea9d9f474 - languageName: node - linkType: hard - -"@typescript-eslint/types@npm:5.62.0": - version: 5.62.0 - resolution: "@typescript-eslint/types@npm:5.62.0" - checksum: 10c0/7febd3a7f0701c0b927e094f02e82d8ee2cada2b186fcb938bc2b94ff6fbad88237afc304cbaf33e82797078bbbb1baf91475f6400912f8b64c89be79bfa4ddf - languageName: node - linkType: hard - -"@typescript-eslint/typescript-estree@npm:5.62.0": - version: 5.62.0 - resolution: "@typescript-eslint/typescript-estree@npm:5.62.0" - dependencies: - "@typescript-eslint/types": "npm:5.62.0" - "@typescript-eslint/visitor-keys": "npm:5.62.0" - debug: "npm:^4.3.4" - globby: "npm:^11.1.0" - is-glob: "npm:^4.0.3" - semver: "npm:^7.3.7" - tsutils: "npm:^3.21.0" - peerDependenciesMeta: - typescript: - optional: true - checksum: 10c0/d7984a3e9d56897b2481940ec803cb8e7ead03df8d9cfd9797350be82ff765dfcf3cfec04e7355e1779e948da8f02bc5e11719d07a596eb1cb995c48a95e38cf - languageName: node - linkType: hard - -"@typescript-eslint/utils@npm:5.62.0": - version: 5.62.0 - resolution: "@typescript-eslint/utils@npm:5.62.0" - dependencies: - "@eslint-community/eslint-utils": "npm:^4.2.0" - "@types/json-schema": "npm:^7.0.9" - "@types/semver": "npm:^7.3.12" - "@typescript-eslint/scope-manager": "npm:5.62.0" - "@typescript-eslint/types": "npm:5.62.0" - "@typescript-eslint/typescript-estree": "npm:5.62.0" - eslint-scope: "npm:^5.1.1" - semver: "npm:^7.3.7" - peerDependencies: - eslint: ^6.0.0 || ^7.0.0 || ^8.0.0 - checksum: 10c0/f09b7d9952e4a205eb1ced31d7684dd55cee40bf8c2d78e923aa8a255318d97279825733902742c09d8690f37a50243f4c4d383ab16bd7aefaf9c4b438f785e1 - languageName: node - linkType: hard - -"@typescript-eslint/visitor-keys@npm:5.62.0": - version: 5.62.0 - resolution: "@typescript-eslint/visitor-keys@npm:5.62.0" - dependencies: - "@typescript-eslint/types": "npm:5.62.0" - eslint-visitor-keys: "npm:^3.3.0" - checksum: 10c0/7c3b8e4148e9b94d9b7162a596a1260d7a3efc4e65199693b8025c71c4652b8042501c0bc9f57654c1e2943c26da98c0f77884a746c6ae81389fcb0b513d995d - languageName: node - linkType: hard - -"@ungap/structured-clone@npm:^1.2.0": - version: 1.2.0 - resolution: "@ungap/structured-clone@npm:1.2.0" - checksum: 10c0/8209c937cb39119f44eb63cf90c0b73e7c754209a6411c707be08e50e29ee81356dca1a848a405c8bdeebfe2f5e4f831ad310ae1689eeef65e7445c090c6657d - languageName: node - linkType: hard - -"abbrev@npm:1": - version: 1.1.1 - resolution: "abbrev@npm:1.1.1" - checksum: 10c0/3f762677702acb24f65e813070e306c61fafe25d4b2583f9dfc935131f774863f3addd5741572ed576bd69cabe473c5af18e1e108b829cb7b6b4747884f726e6 - languageName: node - linkType: hard - -"abbrev@npm:^2.0.0": - version: 2.0.0 - resolution: "abbrev@npm:2.0.0" - checksum: 10c0/f742a5a107473946f426c691c08daba61a1d15942616f300b5d32fd735be88fef5cba24201757b6c407fd564555fb48c751cfa33519b2605c8a7aadd22baf372 - languageName: node - linkType: hard - -"accepts@npm:~1.3.8": - version: 1.3.8 - resolution: "accepts@npm:1.3.8" - dependencies: - mime-types: "npm:~2.1.34" - negotiator: "npm:0.6.3" - checksum: 10c0/3a35c5f5586cfb9a21163ca47a5f77ac34fa8ceb5d17d2fa2c0d81f41cbd7f8c6fa52c77e2c039acc0f4d09e71abdc51144246900f6bef5e3c4b333f77d89362 - languageName: node - linkType: hard - -"acorn-jsx@npm:^5.3.2": - version: 5.3.2 - resolution: "acorn-jsx@npm:5.3.2" - peerDependencies: - acorn: ^6.0.0 || ^7.0.0 || ^8.0.0 - checksum: 10c0/4c54868fbef3b8d58927d5e33f0a4de35f59012fe7b12cf9dfbb345fb8f46607709e1c4431be869a23fb63c151033d84c4198fa9f79385cec34fcb1dd53974c1 - languageName: node - linkType: hard - -"acorn@npm:^8.9.0": - version: 8.12.0 - resolution: "acorn@npm:8.12.0" - bin: - acorn: bin/acorn - checksum: 10c0/a19f9dead009d3b430fa3c253710b47778cdaace15b316de6de93a68c355507bc1072a9956372b6c990cbeeb167d4a929249d0faeb8ae4bb6911d68d53299549 - languageName: node - linkType: hard - -"actual-sync@workspace:.": - version: 0.0.0-use.local - resolution: "actual-sync@workspace:." - dependencies: - "@actual-app/crdt": "npm:2.1.0" - "@actual-app/web": "npm:24.12.0" - "@babel/preset-typescript": "npm:^7.20.2" - "@types/bcrypt": "npm:^5.0.2" - "@types/better-sqlite3": "npm:^7.6.12" - "@types/cors": "npm:^2.8.13" - "@types/express": "npm:^4.17.17" - "@types/express-actuator": "npm:^1.8.0" - "@types/jest": "npm:^29.2.3" - "@types/node": "npm:^17.0.45" - "@types/supertest": "npm:^2.0.12" - "@types/uuid": "npm:^9.0.0" - "@typescript-eslint/eslint-plugin": "npm:^5.51.0" - "@typescript-eslint/parser": "npm:^5.51.0" - bcrypt: "npm:^5.1.1" - better-sqlite3: "npm:^11.7.0" - body-parser: "npm:^1.20.3" - cors: "npm:^2.8.5" - date-fns: "npm:^2.30.0" - debug: "npm:^4.3.4" - eslint: "npm:^8.33.0" - eslint-plugin-prettier: "npm:^4.2.1" - express: "npm:4.20.0" - express-actuator: "npm:1.8.4" - express-rate-limit: "npm:^6.7.0" - express-response-size: "npm:^0.0.3" - express-winston: "npm:^4.2.0" - jest: "npm:^29.3.1" - jws: "npm:^4.0.0" - migrate: "npm:^2.0.1" - nordigen-node: "npm:^1.4.0" - openid-client: "npm:^5.4.2" - prettier: "npm:^2.8.3" - supertest: "npm:^6.3.1" - typescript: "npm:^4.9.5" - uuid: "npm:^9.0.0" - winston: "npm:^3.14.2" - languageName: unknown - linkType: soft - -"agent-base@npm:6": - version: 6.0.2 - resolution: "agent-base@npm:6.0.2" - dependencies: - debug: "npm:4" - checksum: 10c0/dc4f757e40b5f3e3d674bc9beb4f1048f4ee83af189bae39be99f57bf1f48dde166a8b0a5342a84b5944ee8e6ed1e5a9d801858f4ad44764e84957122fe46261 - languageName: node - linkType: hard - -"agent-base@npm:^7.0.2, agent-base@npm:^7.1.0, agent-base@npm:^7.1.1": - version: 7.1.1 - resolution: "agent-base@npm:7.1.1" - dependencies: - debug: "npm:^4.3.4" - checksum: 10c0/e59ce7bed9c63bf071a30cc471f2933862044c97fd9958967bfe22521d7a0f601ce4ed5a8c011799d0c726ca70312142ae193bbebb60f576b52be19d4a363b50 - languageName: node - linkType: hard - -"aggregate-error@npm:^3.0.0": - version: 3.1.0 - resolution: "aggregate-error@npm:3.1.0" - dependencies: - clean-stack: "npm:^2.0.0" - indent-string: "npm:^4.0.0" - checksum: 10c0/a42f67faa79e3e6687a4923050e7c9807db3848a037076f791d10e092677d65c1d2d863b7848560699f40fc0502c19f40963fb1cd1fb3d338a7423df8e45e039 - languageName: node - linkType: hard - -"ajv@npm:^6.12.4": - version: 6.12.6 - resolution: "ajv@npm:6.12.6" - dependencies: - fast-deep-equal: "npm:^3.1.1" - fast-json-stable-stringify: "npm:^2.0.0" - json-schema-traverse: "npm:^0.4.1" - uri-js: "npm:^4.2.2" - checksum: 10c0/41e23642cbe545889245b9d2a45854ebba51cda6c778ebced9649420d9205f2efb39cb43dbc41e358409223b1ea43303ae4839db682c848b891e4811da1a5a71 - languageName: node - linkType: hard - -"ansi-escapes@npm:^4.2.1": - version: 4.3.2 - resolution: "ansi-escapes@npm:4.3.2" - dependencies: - type-fest: "npm:^0.21.3" - checksum: 10c0/da917be01871525a3dfcf925ae2977bc59e8c513d4423368645634bf5d4ceba5401574eb705c1e92b79f7292af5a656f78c5725a4b0e1cec97c4b413705c1d50 - languageName: node - linkType: hard - -"ansi-regex@npm:^5.0.1": - version: 5.0.1 - resolution: "ansi-regex@npm:5.0.1" - checksum: 10c0/9a64bb8627b434ba9327b60c027742e5d17ac69277960d041898596271d992d4d52ba7267a63ca10232e29f6107fc8a835f6ce8d719b88c5f8493f8254813737 - languageName: node - linkType: hard - -"ansi-regex@npm:^6.0.1": - version: 6.0.1 - resolution: "ansi-regex@npm:6.0.1" - checksum: 10c0/cbe16dbd2c6b2735d1df7976a7070dd277326434f0212f43abf6d87674095d247968209babdaad31bb00882fa68807256ba9be340eec2f1004de14ca75f52a08 - languageName: node - linkType: hard - -"ansi-styles@npm:^3.2.1": - version: 3.2.1 - resolution: "ansi-styles@npm:3.2.1" - dependencies: - color-convert: "npm:^1.9.0" - checksum: 10c0/ece5a8ef069fcc5298f67e3f4771a663129abd174ea2dfa87923a2be2abf6cd367ef72ac87942da00ce85bd1d651d4cd8595aebdb1b385889b89b205860e977b - languageName: node - linkType: hard - -"ansi-styles@npm:^4.0.0, ansi-styles@npm:^4.1.0": - version: 4.3.0 - resolution: "ansi-styles@npm:4.3.0" - dependencies: - color-convert: "npm:^2.0.1" - checksum: 10c0/895a23929da416f2bd3de7e9cb4eabd340949328ab85ddd6e484a637d8f6820d485f53933446f5291c3b760cbc488beb8e88573dd0f9c7daf83dccc8fe81b041 - languageName: node - linkType: hard - -"ansi-styles@npm:^5.0.0": - version: 5.2.0 - resolution: "ansi-styles@npm:5.2.0" - checksum: 10c0/9c4ca80eb3c2fb7b33841c210d2f20807f40865d27008d7c3f707b7f95cab7d67462a565e2388ac3285b71cb3d9bb2173de8da37c57692a362885ec34d6e27df - languageName: node - linkType: hard - -"ansi-styles@npm:^6.1.0": - version: 6.2.1 - resolution: "ansi-styles@npm:6.2.1" - checksum: 10c0/5d1ec38c123984bcedd996eac680d548f31828bd679a66db2bdf11844634dde55fec3efa9c6bb1d89056a5e79c1ac540c4c784d592ea1d25028a92227d2f2d5c - languageName: node - linkType: hard - -"anymatch@npm:^3.0.3": - version: 3.1.3 - resolution: "anymatch@npm:3.1.3" - dependencies: - normalize-path: "npm:^3.0.0" - picomatch: "npm:^2.0.4" - checksum: 10c0/57b06ae984bc32a0d22592c87384cd88fe4511b1dd7581497831c56d41939c8a001b28e7b853e1450f2bf61992dfcaa8ae2d0d161a0a90c4fb631ef07098fbac - languageName: node - linkType: hard - -"aproba@npm:^1.0.3 || ^2.0.0": - version: 2.0.0 - resolution: "aproba@npm:2.0.0" - checksum: 10c0/d06e26384a8f6245d8c8896e138c0388824e259a329e0c9f196b4fa533c82502a6fd449586e3604950a0c42921832a458bb3aa0aa9f0ba449cfd4f50fd0d09b5 - languageName: node - linkType: hard - -"are-we-there-yet@npm:^2.0.0": - version: 2.0.0 - resolution: "are-we-there-yet@npm:2.0.0" - dependencies: - delegates: "npm:^1.0.0" - readable-stream: "npm:^3.6.0" - checksum: 10c0/375f753c10329153c8d66dc95e8f8b6c7cc2aa66e05cb0960bd69092b10dae22900cacc7d653ad11d26b3ecbdbfe1e8bfb6ccf0265ba8077a7d979970f16b99c - languageName: node - linkType: hard - -"argparse@npm:^1.0.7": - version: 1.0.10 - resolution: "argparse@npm:1.0.10" - dependencies: - sprintf-js: "npm:~1.0.2" - checksum: 10c0/b2972c5c23c63df66bca144dbc65d180efa74f25f8fd9b7d9a0a6c88ae839db32df3d54770dcb6460cf840d232b60695d1a6b1053f599d84e73f7437087712de - languageName: node - linkType: hard - -"argparse@npm:^2.0.1": - version: 2.0.1 - resolution: "argparse@npm:2.0.1" - checksum: 10c0/c5640c2d89045371c7cedd6a70212a04e360fd34d6edeae32f6952c63949e3525ea77dbec0289d8213a99bbaeab5abfa860b5c12cf88a2e6cf8106e90dd27a7e - languageName: node - linkType: hard - -"array-flatten@npm:1.1.1": - version: 1.1.1 - resolution: "array-flatten@npm:1.1.1" - checksum: 10c0/806966c8abb2f858b08f5324d9d18d7737480610f3bd5d3498aaae6eb5efdc501a884ba019c9b4a8f02ff67002058749d05548fd42fa8643f02c9c7f22198b91 - languageName: node - linkType: hard - -"array-union@npm:^2.1.0": - version: 2.1.0 - resolution: "array-union@npm:2.1.0" - checksum: 10c0/429897e68110374f39b771ec47a7161fc6a8fc33e196857c0a396dc75df0b5f65e4d046674db764330b6bb66b39ef48dd7c53b6a2ee75cfb0681e0c1a7033962 - languageName: node - linkType: hard - -"asap@npm:^2.0.0": - version: 2.0.6 - resolution: "asap@npm:2.0.6" - checksum: 10c0/c6d5e39fe1f15e4b87677460bd66b66050cd14c772269cee6688824c1410a08ab20254bb6784f9afb75af9144a9f9a7692d49547f4d19d715aeb7c0318f3136d - languageName: node - linkType: hard - -"async@npm:^3.2.3": - version: 3.2.5 - resolution: "async@npm:3.2.5" - checksum: 10c0/1408287b26c6db67d45cb346e34892cee555b8b59e6c68e6f8c3e495cad5ca13b4f218180e871f3c2ca30df4ab52693b66f2f6ff43644760cab0b2198bda79c1 - languageName: node - linkType: hard - -"asynckit@npm:^0.4.0": - version: 0.4.0 - resolution: "asynckit@npm:0.4.0" - checksum: 10c0/d73e2ddf20c4eb9337e1b3df1a0f6159481050a5de457c55b14ea2e5cb6d90bb69e004c9af54737a5ee0917fcf2c9e25de67777bbe58261847846066ba75bc9d - languageName: node - linkType: hard - -"axios@npm:^1.2.1": - version: 1.7.4 - resolution: "axios@npm:1.7.4" - dependencies: - follow-redirects: "npm:^1.15.6" - form-data: "npm:^4.0.0" - proxy-from-env: "npm:^1.1.0" - checksum: 10c0/5ea1a93140ca1d49db25ef8e1bd8cfc59da6f9220159a944168860ad15a2743ea21c5df2967795acb15cbe81362f5b157fdebbea39d53117ca27658bab9f7f17 - languageName: node - linkType: hard - -"babel-jest@npm:^29.7.0": - version: 29.7.0 - resolution: "babel-jest@npm:29.7.0" - dependencies: - "@jest/transform": "npm:^29.7.0" - "@types/babel__core": "npm:^7.1.14" - babel-plugin-istanbul: "npm:^6.1.1" - babel-preset-jest: "npm:^29.6.3" - chalk: "npm:^4.0.0" - graceful-fs: "npm:^4.2.9" - slash: "npm:^3.0.0" - peerDependencies: - "@babel/core": ^7.8.0 - checksum: 10c0/2eda9c1391e51936ca573dd1aedfee07b14c59b33dbe16ef347873ddd777bcf6e2fc739681e9e9661ab54ef84a3109a03725be2ac32cd2124c07ea4401cbe8c1 - languageName: node - linkType: hard - -"babel-plugin-istanbul@npm:^6.1.1": - version: 6.1.1 - resolution: "babel-plugin-istanbul@npm:6.1.1" - dependencies: - "@babel/helper-plugin-utils": "npm:^7.0.0" - "@istanbuljs/load-nyc-config": "npm:^1.0.0" - "@istanbuljs/schema": "npm:^0.1.2" - istanbul-lib-instrument: "npm:^5.0.4" - test-exclude: "npm:^6.0.0" - checksum: 10c0/1075657feb705e00fd9463b329921856d3775d9867c5054b449317d39153f8fbcebd3e02ebf00432824e647faff3683a9ca0a941325ef1afe9b3c4dd51b24beb - languageName: node - linkType: hard - -"babel-plugin-jest-hoist@npm:^29.6.3": - version: 29.6.3 - resolution: "babel-plugin-jest-hoist@npm:29.6.3" - dependencies: - "@babel/template": "npm:^7.3.3" - "@babel/types": "npm:^7.3.3" - "@types/babel__core": "npm:^7.1.14" - "@types/babel__traverse": "npm:^7.0.6" - checksum: 10c0/7e6451caaf7dce33d010b8aafb970e62f1b0c0b57f4978c37b0d457bbcf0874d75a395a102daf0bae0bd14eafb9f6e9a165ee5e899c0a4f1f3bb2e07b304ed2e - languageName: node - linkType: hard - -"babel-preset-current-node-syntax@npm:^1.0.0": - version: 1.0.1 - resolution: "babel-preset-current-node-syntax@npm:1.0.1" - dependencies: - "@babel/plugin-syntax-async-generators": "npm:^7.8.4" - "@babel/plugin-syntax-bigint": "npm:^7.8.3" - "@babel/plugin-syntax-class-properties": "npm:^7.8.3" - "@babel/plugin-syntax-import-meta": "npm:^7.8.3" - "@babel/plugin-syntax-json-strings": "npm:^7.8.3" - "@babel/plugin-syntax-logical-assignment-operators": "npm:^7.8.3" - "@babel/plugin-syntax-nullish-coalescing-operator": "npm:^7.8.3" - "@babel/plugin-syntax-numeric-separator": "npm:^7.8.3" - "@babel/plugin-syntax-object-rest-spread": "npm:^7.8.3" - "@babel/plugin-syntax-optional-catch-binding": "npm:^7.8.3" - "@babel/plugin-syntax-optional-chaining": "npm:^7.8.3" - "@babel/plugin-syntax-top-level-await": "npm:^7.8.3" - peerDependencies: - "@babel/core": ^7.0.0 - checksum: 10c0/5ba39a3a0e6c37d25e56a4fb843be632dac98d54706d8a0933f9bcb1a07987a96d55c2b5a6c11788a74063fb2534fe68c1f1dbb6c93626850c785e0938495627 - languageName: node - linkType: hard - -"babel-preset-jest@npm:^29.6.3": - version: 29.6.3 - resolution: "babel-preset-jest@npm:29.6.3" - dependencies: - babel-plugin-jest-hoist: "npm:^29.6.3" - babel-preset-current-node-syntax: "npm:^1.0.0" - peerDependencies: - "@babel/core": ^7.0.0 - checksum: 10c0/ec5fd0276b5630b05f0c14bb97cc3815c6b31600c683ebb51372e54dcb776cff790bdeeabd5b8d01ede375a040337ccbf6a3ccd68d3a34219125945e167ad943 - languageName: node - linkType: hard - -"balanced-match@npm:^1.0.0": - version: 1.0.2 - resolution: "balanced-match@npm:1.0.2" - checksum: 10c0/9308baf0a7e4838a82bbfd11e01b1cb0f0cf2893bc1676c27c2a8c0e70cbae1c59120c3268517a8ae7fb6376b4639ef81ca22582611dbee4ed28df945134aaee - languageName: node - linkType: hard - -"base64-js@npm:^1.3.1": - version: 1.5.1 - resolution: "base64-js@npm:1.5.1" - checksum: 10c0/f23823513b63173a001030fae4f2dabe283b99a9d324ade3ad3d148e218134676f1ee8568c877cd79ec1c53158dcf2d2ba527a97c606618928ba99dd930102bf - languageName: node - linkType: hard - -"bcrypt@npm:^5.1.1": - version: 5.1.1 - resolution: "bcrypt@npm:5.1.1" - dependencies: - "@mapbox/node-pre-gyp": "npm:^1.0.11" - node-addon-api: "npm:^5.0.0" - checksum: 10c0/743231158c866bddc46f25eb8e9617fe38bc1a6f5f3052aba35e361d349b7f8fb80e96b45c48a4c23c45c29967ccd11c81cf31166454fc0ab019801c336cab40 - languageName: node - linkType: hard - -"better-sqlite3@npm:^11.7.0": - version: 11.7.0 - resolution: "better-sqlite3@npm:11.7.0" - dependencies: - bindings: "npm:^1.5.0" - node-gyp: "npm:latest" - prebuild-install: "npm:^7.1.1" - checksum: 10c0/66e78fb7e12f55dd78469efec7f6fcf69079e149e974be9ea24befac7c67b8fe0e23a419cae412ac4ea025c73841d8d54bd222eece1c007485e3f6bd56fd1c94 - languageName: node - linkType: hard - -"bindings@npm:^1.5.0": - version: 1.5.0 - resolution: "bindings@npm:1.5.0" - dependencies: - file-uri-to-path: "npm:1.0.0" - checksum: 10c0/3dab2491b4bb24124252a91e656803eac24292473e56554e35bbfe3cc1875332cfa77600c3bac7564049dc95075bf6fcc63a4609920ff2d64d0fe405fcf0d4ba - languageName: node - linkType: hard - -"bl@npm:^4.0.3": - version: 4.1.0 - resolution: "bl@npm:4.1.0" - dependencies: - buffer: "npm:^5.5.0" - inherits: "npm:^2.0.4" - readable-stream: "npm:^3.4.0" - checksum: 10c0/02847e1d2cb089c9dc6958add42e3cdeaf07d13f575973963335ac0fdece563a50ac770ac4c8fa06492d2dd276f6cc3b7f08c7cd9c7a7ad0f8d388b2a28def5f - languageName: node - linkType: hard - -"body-parser@npm:1.20.3, body-parser@npm:^1.20.3": - version: 1.20.3 - resolution: "body-parser@npm:1.20.3" - dependencies: - bytes: "npm:3.1.2" - content-type: "npm:~1.0.5" - debug: "npm:2.6.9" - depd: "npm:2.0.0" - destroy: "npm:1.2.0" - http-errors: "npm:2.0.0" - iconv-lite: "npm:0.4.24" - on-finished: "npm:2.4.1" - qs: "npm:6.13.0" - raw-body: "npm:2.5.2" - type-is: "npm:~1.6.18" - unpipe: "npm:1.0.0" - checksum: 10c0/0a9a93b7518f222885498dcecaad528cf010dd109b071bf471c93def4bfe30958b83e03496eb9c1ad4896db543d999bb62be1a3087294162a88cfa1b42c16310 - languageName: node - linkType: hard - -"brace-expansion@npm:^1.1.7": - version: 1.1.11 - resolution: "brace-expansion@npm:1.1.11" - dependencies: - balanced-match: "npm:^1.0.0" - concat-map: "npm:0.0.1" - checksum: 10c0/695a56cd058096a7cb71fb09d9d6a7070113c7be516699ed361317aca2ec169f618e28b8af352e02ab4233fb54eb0168460a40dc320bab0034b36ab59aaad668 - languageName: node - linkType: hard - -"brace-expansion@npm:^2.0.1": - version: 2.0.1 - resolution: "brace-expansion@npm:2.0.1" - dependencies: - balanced-match: "npm:^1.0.0" - checksum: 10c0/b358f2fe060e2d7a87aa015979ecea07f3c37d4018f8d6deb5bd4c229ad3a0384fe6029bb76cd8be63c81e516ee52d1a0673edbe2023d53a5191732ae3c3e49f - languageName: node - linkType: hard - -"braces@npm:^3.0.3": - version: 3.0.3 - resolution: "braces@npm:3.0.3" - dependencies: - fill-range: "npm:^7.1.1" - checksum: 10c0/7c6dfd30c338d2997ba77500539227b9d1f85e388a5f43220865201e407e076783d0881f2d297b9f80951b4c957fcf0b51c1d2d24227631643c3f7c284b0aa04 - languageName: node - linkType: hard - -"browserslist@npm:^4.22.2": - version: 4.23.1 - resolution: "browserslist@npm:4.23.1" - dependencies: - caniuse-lite: "npm:^1.0.30001629" - electron-to-chromium: "npm:^1.4.796" - node-releases: "npm:^2.0.14" - update-browserslist-db: "npm:^1.0.16" - bin: - browserslist: cli.js - checksum: 10c0/eb47c7ab9d60db25ce2faca70efeb278faa7282a2f62b7f2fa2f92e5f5251cf65144244566c86559419ff4f6d78f59ea50e39911321ad91f3b27788901f1f5e9 - languageName: node - linkType: hard - -"bser@npm:2.1.1": - version: 2.1.1 - resolution: "bser@npm:2.1.1" - dependencies: - node-int64: "npm:^0.4.0" - checksum: 10c0/24d8dfb7b6d457d73f32744e678a60cc553e4ec0e9e1a01cf614b44d85c3c87e188d3cc78ef0442ce5032ee6818de20a0162ba1074725c0d08908f62ea979227 - languageName: node - linkType: hard - -"buffer-equal-constant-time@npm:1.0.1": - version: 1.0.1 - resolution: "buffer-equal-constant-time@npm:1.0.1" - checksum: 10c0/fb2294e64d23c573d0dd1f1e7a466c3e978fe94a4e0f8183937912ca374619773bef8e2aceb854129d2efecbbc515bbd0cc78d2734a3e3031edb0888531bbc8e - languageName: node - linkType: hard - -"buffer-from@npm:^1.0.0": - version: 1.1.2 - resolution: "buffer-from@npm:1.1.2" - checksum: 10c0/124fff9d66d691a86d3b062eff4663fe437a9d9ee4b47b1b9e97f5a5d14f6d5399345db80f796827be7c95e70a8e765dd404b7c3ff3b3324f98e9b0c8826cc34 - languageName: node - linkType: hard - -"buffer@npm:^5.5.0": - version: 5.7.1 - resolution: "buffer@npm:5.7.1" - dependencies: - base64-js: "npm:^1.3.1" - ieee754: "npm:^1.1.13" - checksum: 10c0/27cac81cff434ed2876058d72e7c4789d11ff1120ef32c9de48f59eab58179b66710c488987d295ae89a228f835fc66d088652dffeb8e3ba8659f80eb091d55e - languageName: node - linkType: hard - -"bytes@npm:3.1.2": - version: 3.1.2 - resolution: "bytes@npm:3.1.2" - checksum: 10c0/76d1c43cbd602794ad8ad2ae94095cddeb1de78c5dddaa7005c51af10b0176c69971a6d88e805a90c2b6550d76636e43c40d8427a808b8645ede885de4a0358e - languageName: node - linkType: hard - -"cacache@npm:^18.0.0": - version: 18.0.3 - resolution: "cacache@npm:18.0.3" - dependencies: - "@npmcli/fs": "npm:^3.1.0" - fs-minipass: "npm:^3.0.0" - glob: "npm:^10.2.2" - lru-cache: "npm:^10.0.1" - minipass: "npm:^7.0.3" - minipass-collect: "npm:^2.0.1" - minipass-flush: "npm:^1.0.5" - minipass-pipeline: "npm:^1.2.4" - p-map: "npm:^4.0.0" - ssri: "npm:^10.0.0" - tar: "npm:^6.1.11" - unique-filename: "npm:^3.0.0" - checksum: 10c0/dfda92840bb371fb66b88c087c61a74544363b37a265023223a99965b16a16bbb87661fe4948718d79df6e0cc04e85e62784fbcf1832b2a5e54ff4c46fbb45b7 - languageName: node - linkType: hard - -"call-bind@npm:^1.0.7": - version: 1.0.7 - resolution: "call-bind@npm:1.0.7" - dependencies: - es-define-property: "npm:^1.0.0" - es-errors: "npm:^1.3.0" - function-bind: "npm:^1.1.2" - get-intrinsic: "npm:^1.2.4" - set-function-length: "npm:^1.2.1" - checksum: 10c0/a3ded2e423b8e2a265983dba81c27e125b48eefb2655e7dfab6be597088da3d47c47976c24bc51b8fd9af1061f8f87b4ab78a314f3c77784b2ae2ba535ad8b8d - languageName: node - linkType: hard - -"callsites@npm:^3.0.0": - version: 3.1.0 - resolution: "callsites@npm:3.1.0" - checksum: 10c0/fff92277400eb06c3079f9e74f3af120db9f8ea03bad0e84d9aede54bbe2d44a56cccb5f6cf12211f93f52306df87077ecec5b712794c5a9b5dac6d615a3f301 - languageName: node - linkType: hard - -"camelcase@npm:^5.3.1": - version: 5.3.1 - resolution: "camelcase@npm:5.3.1" - checksum: 10c0/92ff9b443bfe8abb15f2b1513ca182d16126359ad4f955ebc83dc4ddcc4ef3fdd2c078bc223f2673dc223488e75c99b16cc4d056624374b799e6a1555cf61b23 - languageName: node - linkType: hard - -"camelcase@npm:^6.2.0": - version: 6.3.0 - resolution: "camelcase@npm:6.3.0" - checksum: 10c0/0d701658219bd3116d12da3eab31acddb3f9440790c0792e0d398f0a520a6a4058018e546862b6fba89d7ae990efaeb97da71e1913e9ebf5a8b5621a3d55c710 - languageName: node - linkType: hard - -"caniuse-lite@npm:^1.0.30001629": - version: 1.0.30001636 - resolution: "caniuse-lite@npm:1.0.30001636" - checksum: 10c0/e5f965b4da7bae1531fd9f93477d015729ff9e3fa12670ead39a9e6cdc4c43e62c272d47857c5cc332e7b02d697cb3f2f965a1030870ac7476da60c2fc81ee94 - languageName: node - linkType: hard - -"chalk@npm:^2.4.2": - version: 2.4.2 - resolution: "chalk@npm:2.4.2" - dependencies: - ansi-styles: "npm:^3.2.1" - escape-string-regexp: "npm:^1.0.5" - supports-color: "npm:^5.3.0" - checksum: 10c0/e6543f02ec877732e3a2d1c3c3323ddb4d39fbab687c23f526e25bd4c6a9bf3b83a696e8c769d078e04e5754921648f7821b2a2acfd16c550435fd630026e073 - languageName: node - linkType: hard - -"chalk@npm:^4.0.0, chalk@npm:^4.1.2": - version: 4.1.2 - resolution: "chalk@npm:4.1.2" - dependencies: - ansi-styles: "npm:^4.1.0" - supports-color: "npm:^7.1.0" - checksum: 10c0/4a3fef5cc34975c898ffe77141450f679721df9dde00f6c304353fa9c8b571929123b26a0e4617bde5018977eb655b31970c297b91b63ee83bb82aeb04666880 - languageName: node - linkType: hard - -"char-regex@npm:^1.0.2": - version: 1.0.2 - resolution: "char-regex@npm:1.0.2" - checksum: 10c0/57a09a86371331e0be35d9083ba429e86c4f4648ecbe27455dbfb343037c16ee6fdc7f6b61f433a57cc5ded5561d71c56a150e018f40c2ffb7bc93a26dae341e - languageName: node - linkType: hard - -"chownr@npm:^1.1.1": - version: 1.1.4 - resolution: "chownr@npm:1.1.4" - checksum: 10c0/ed57952a84cc0c802af900cf7136de643d3aba2eecb59d29344bc2f3f9bf703a301b9d84cdc71f82c3ffc9ccde831b0d92f5b45f91727d6c9da62f23aef9d9db - languageName: node - linkType: hard - -"chownr@npm:^2.0.0": - version: 2.0.0 - resolution: "chownr@npm:2.0.0" - checksum: 10c0/594754e1303672171cc04e50f6c398ae16128eb134a88f801bf5354fd96f205320f23536a045d9abd8b51024a149696e51231565891d4efdab8846021ecf88e6 - languageName: node - linkType: hard - -"ci-info@npm:^3.2.0": - version: 3.9.0 - resolution: "ci-info@npm:3.9.0" - checksum: 10c0/6f0109e36e111684291d46123d491bc4e7b7a1934c3a20dea28cba89f1d4a03acd892f5f6a81ed3855c38647e285a150e3c9ba062e38943bef57fee6c1554c3a - languageName: node - linkType: hard - -"cjs-module-lexer@npm:^1.0.0": - version: 1.3.1 - resolution: "cjs-module-lexer@npm:1.3.1" - checksum: 10c0/cd98fbf3c7f4272fb0ebf71d08d0c54bc75ce0e30b9d186114e15b4ba791f3d310af65a339eea2a0318599af2818cdd8886d353b43dfab94468f72987397ad16 - languageName: node - linkType: hard - -"clean-stack@npm:^2.0.0": - version: 2.2.0 - resolution: "clean-stack@npm:2.2.0" - checksum: 10c0/1f90262d5f6230a17e27d0c190b09d47ebe7efdd76a03b5a1127863f7b3c9aec4c3e6c8bb3a7bbf81d553d56a1fd35728f5a8ef4c63f867ac8d690109742a8c1 - languageName: node - linkType: hard - -"cliui@npm:^8.0.1": - version: 8.0.1 - resolution: "cliui@npm:8.0.1" - dependencies: - string-width: "npm:^4.2.0" - strip-ansi: "npm:^6.0.1" - wrap-ansi: "npm:^7.0.0" - checksum: 10c0/4bda0f09c340cbb6dfdc1ed508b3ca080f12992c18d68c6be4d9cf51756033d5266e61ec57529e610dacbf4da1c634423b0c1b11037709cc6b09045cbd815df5 - languageName: node - linkType: hard - -"co@npm:^4.6.0": - version: 4.6.0 - resolution: "co@npm:4.6.0" - checksum: 10c0/c0e85ea0ca8bf0a50cbdca82efc5af0301240ca88ebe3644a6ffb8ffe911f34d40f8fbcf8f1d52c5ddd66706abd4d3bfcd64259f1e8e2371d4f47573b0dc8c28 - languageName: node - linkType: hard - -"collect-v8-coverage@npm:^1.0.0": - version: 1.0.2 - resolution: "collect-v8-coverage@npm:1.0.2" - checksum: 10c0/ed7008e2e8b6852c5483b444a3ae6e976e088d4335a85aa0a9db2861c5f1d31bd2d7ff97a60469b3388deeba661a619753afbe201279fb159b4b9548ab8269a1 - languageName: node - linkType: hard - -"color-convert@npm:^1.9.0, color-convert@npm:^1.9.3": - version: 1.9.3 - resolution: "color-convert@npm:1.9.3" - dependencies: - color-name: "npm:1.1.3" - checksum: 10c0/5ad3c534949a8c68fca8fbc6f09068f435f0ad290ab8b2f76841b9e6af7e0bb57b98cb05b0e19fe33f5d91e5a8611ad457e5f69e0a484caad1f7487fd0e8253c - languageName: node - linkType: hard - -"color-convert@npm:^2.0.1": - version: 2.0.1 - resolution: "color-convert@npm:2.0.1" - dependencies: - color-name: "npm:~1.1.4" - checksum: 10c0/37e1150172f2e311fe1b2df62c6293a342ee7380da7b9cfdba67ea539909afbd74da27033208d01d6d5cfc65ee7868a22e18d7e7648e004425441c0f8a15a7d7 - languageName: node - linkType: hard - -"color-name@npm:1.1.3": - version: 1.1.3 - resolution: "color-name@npm:1.1.3" - checksum: 10c0/566a3d42cca25b9b3cd5528cd7754b8e89c0eb646b7f214e8e2eaddb69994ac5f0557d9c175eb5d8f0ad73531140d9c47525085ee752a91a2ab15ab459caf6d6 - languageName: node - linkType: hard - -"color-name@npm:^1.0.0, color-name@npm:~1.1.4": - version: 1.1.4 - resolution: "color-name@npm:1.1.4" - checksum: 10c0/a1a3f914156960902f46f7f56bc62effc6c94e84b2cae157a526b1c1f74b677a47ec602bf68a61abfa2b42d15b7c5651c6dbe72a43af720bc588dff885b10f95 - languageName: node - linkType: hard - -"color-string@npm:^1.6.0": - version: 1.9.1 - resolution: "color-string@npm:1.9.1" - dependencies: - color-name: "npm:^1.0.0" - simple-swizzle: "npm:^0.2.2" - checksum: 10c0/b0bfd74c03b1f837f543898b512f5ea353f71630ccdd0d66f83028d1f0924a7d4272deb278b9aef376cacf1289b522ac3fb175e99895283645a2dc3a33af2404 - languageName: node - linkType: hard - -"color-support@npm:^1.1.2": - version: 1.1.3 - resolution: "color-support@npm:1.1.3" - bin: - color-support: bin.js - checksum: 10c0/8ffeaa270a784dc382f62d9be0a98581db43e11eee301af14734a6d089bd456478b1a8b3e7db7ca7dc5b18a75f828f775c44074020b51c05fc00e6d0992b1cc6 - languageName: node - linkType: hard - -"color@npm:^3.1.3": - version: 3.2.1 - resolution: "color@npm:3.2.1" - dependencies: - color-convert: "npm:^1.9.3" - color-string: "npm:^1.6.0" - checksum: 10c0/39345d55825884c32a88b95127d417a2c24681d8b57069413596d9fcbb721459ef9d9ec24ce3e65527b5373ce171b73e38dbcd9c830a52a6487e7f37bf00e83c - languageName: node - linkType: hard - -"colorspace@npm:1.1.x": - version: 1.1.4 - resolution: "colorspace@npm:1.1.4" - dependencies: - color: "npm:^3.1.3" - text-hex: "npm:1.0.x" - checksum: 10c0/af5f91ff7f8e146b96e439ac20ed79b197210193bde721b47380a75b21751d90fa56390c773bb67c0aedd34ff85091883a437ab56861c779bd507d639ba7e123 - languageName: node - linkType: hard - -"combined-stream@npm:^1.0.8": - version: 1.0.8 - resolution: "combined-stream@npm:1.0.8" - dependencies: - delayed-stream: "npm:~1.0.0" - checksum: 10c0/0dbb829577e1b1e839fa82b40c07ffaf7de8a09b935cadd355a73652ae70a88b4320db322f6634a4ad93424292fa80973ac6480986247f1734a1137debf271d5 - languageName: node - linkType: hard - -"commander@npm:^2.20.3": - version: 2.20.3 - resolution: "commander@npm:2.20.3" - checksum: 10c0/74c781a5248c2402a0a3e966a0a2bba3c054aad144f5c023364be83265e796b20565aa9feff624132ff629aa64e16999fa40a743c10c12f7c61e96a794b99288 - languageName: node - linkType: hard - -"component-emitter@npm:^1.3.0": - version: 1.3.1 - resolution: "component-emitter@npm:1.3.1" - checksum: 10c0/e4900b1b790b5e76b8d71b328da41482118c0f3523a516a41be598dc2785a07fd721098d9bf6e22d89b19f4fa4e1025160dc00317ea111633a3e4f75c2b86032 - languageName: node - linkType: hard - -"concat-map@npm:0.0.1": - version: 0.0.1 - resolution: "concat-map@npm:0.0.1" - checksum: 10c0/c996b1cfdf95b6c90fee4dae37e332c8b6eb7d106430c17d538034c0ad9a1630cb194d2ab37293b1bdd4d779494beee7786d586a50bd9376fd6f7bcc2bd4c98f - languageName: node - linkType: hard - -"console-control-strings@npm:^1.0.0, console-control-strings@npm:^1.1.0": - version: 1.1.0 - resolution: "console-control-strings@npm:1.1.0" - checksum: 10c0/7ab51d30b52d461412cd467721bb82afe695da78fff8f29fe6f6b9cbaac9a2328e27a22a966014df9532100f6dd85370460be8130b9c677891ba36d96a343f50 - languageName: node - linkType: hard - -"content-disposition@npm:0.5.4": - version: 0.5.4 - resolution: "content-disposition@npm:0.5.4" - dependencies: - safe-buffer: "npm:5.2.1" - checksum: 10c0/bac0316ebfeacb8f381b38285dc691c9939bf0a78b0b7c2d5758acadad242d04783cee5337ba7d12a565a19075af1b3c11c728e1e4946de73c6ff7ce45f3f1bb - languageName: node - linkType: hard - -"content-type@npm:~1.0.4, content-type@npm:~1.0.5": - version: 1.0.5 - resolution: "content-type@npm:1.0.5" - checksum: 10c0/b76ebed15c000aee4678c3707e0860cb6abd4e680a598c0a26e17f0bfae723ec9cc2802f0ff1bc6e4d80603719010431d2231018373d4dde10f9ccff9dadf5af - languageName: node - linkType: hard - -"convert-source-map@npm:^2.0.0": - version: 2.0.0 - resolution: "convert-source-map@npm:2.0.0" - checksum: 10c0/8f2f7a27a1a011cc6cc88cc4da2d7d0cfa5ee0369508baae3d98c260bb3ac520691464e5bbe4ae7cdf09860c1d69ecc6f70c63c6e7c7f7e3f18ec08484dc7d9b - languageName: node - linkType: hard - -"cookie-signature@npm:1.0.6": - version: 1.0.6 - resolution: "cookie-signature@npm:1.0.6" - checksum: 10c0/b36fd0d4e3fef8456915fcf7742e58fbfcc12a17a018e0eb9501c9d5ef6893b596466f03b0564b81af29ff2538fd0aa4b9d54fe5ccbfb4c90ea50ad29fe2d221 - languageName: node - linkType: hard - -"cookie@npm:0.6.0": - version: 0.6.0 - resolution: "cookie@npm:0.6.0" - checksum: 10c0/f2318b31af7a31b4ddb4a678d024514df5e705f9be5909a192d7f116cfb6d45cbacf96a473fa733faa95050e7cff26e7832bb3ef94751592f1387b71c8956686 - languageName: node - linkType: hard - -"cookiejar@npm:^2.1.4": - version: 2.1.4 - resolution: "cookiejar@npm:2.1.4" - checksum: 10c0/2dae55611c6e1678f34d93984cbd4bda58f4fe3e5247cc4993f4a305cd19c913bbaf325086ed952e892108115073a747596453d3dc1c34947f47f731818b8ad1 - languageName: node - linkType: hard - -"cors@npm:^2.8.5": - version: 2.8.5 - resolution: "cors@npm:2.8.5" - dependencies: - object-assign: "npm:^4" - vary: "npm:^1" - checksum: 10c0/373702b7999409922da80de4a61938aabba6929aea5b6fd9096fefb9e8342f626c0ebd7507b0e8b0b311380744cc985f27edebc0a26e0ddb784b54e1085de761 - languageName: node - linkType: hard - -"create-jest@npm:^29.7.0": - version: 29.7.0 - resolution: "create-jest@npm:29.7.0" - dependencies: - "@jest/types": "npm:^29.6.3" - chalk: "npm:^4.0.0" - exit: "npm:^0.1.2" - graceful-fs: "npm:^4.2.9" - jest-config: "npm:^29.7.0" - jest-util: "npm:^29.7.0" - prompts: "npm:^2.0.1" - bin: - create-jest: bin/create-jest.js - checksum: 10c0/e7e54c280692470d3398f62a6238fd396327e01c6a0757002833f06d00afc62dd7bfe04ff2b9cd145264460e6b4d1eb8386f2925b7e567f97939843b7b0e812f - languageName: node - linkType: hard - -"cross-spawn@npm:^7.0.0, cross-spawn@npm:^7.0.2, cross-spawn@npm:^7.0.3": - version: 7.0.6 - resolution: "cross-spawn@npm:7.0.6" - dependencies: - path-key: "npm:^3.1.0" - shebang-command: "npm:^2.0.0" - which: "npm:^2.0.1" - checksum: 10c0/053ea8b2135caff68a9e81470e845613e374e7309a47731e81639de3eaeb90c3d01af0e0b44d2ab9d50b43467223b88567dfeb3262db942dc063b9976718ffc1 - languageName: node - linkType: hard - -"date-fns@npm:^2.30.0": - version: 2.30.0 - resolution: "date-fns@npm:2.30.0" - dependencies: - "@babel/runtime": "npm:^7.21.0" - checksum: 10c0/e4b521fbf22bc8c3db332bbfb7b094fd3e7627de0259a9d17c7551e2d2702608a7307a449206065916538e384f37b181565447ce2637ae09828427aed9cb5581 - languageName: node - linkType: hard - -"dateformat@npm:^4.6.3": - version: 4.6.3 - resolution: "dateformat@npm:4.6.3" - checksum: 10c0/e2023b905e8cfe2eb8444fb558562b524807a51cdfe712570f360f873271600b5c94aebffaf11efb285e2c072264a7cf243eadb68f3eba0f8cc85fb86cd25df6 - languageName: node - linkType: hard - -"dayjs@npm:^1.11.3": - version: 1.11.11 - resolution: "dayjs@npm:1.11.11" - checksum: 10c0/0131d10516b9945f05a57e13f4af49a6814de5573a494824e103131a3bbe4cc470b1aefe8e17e51f9a478a22cd116084be1ee5725cedb66ec4c3f9091202dc4b - languageName: node - linkType: hard - -"debug@npm:2.6.9": - version: 2.6.9 - resolution: "debug@npm:2.6.9" - dependencies: - ms: "npm:2.0.0" - checksum: 10c0/121908fb839f7801180b69a7e218a40b5a0b718813b886b7d6bdb82001b931c938e2941d1e4450f33a1b1df1da653f5f7a0440c197f29fbf8a6e9d45ff6ef589 - languageName: node - linkType: hard - -"debug@npm:4, debug@npm:^4.1.0, debug@npm:^4.1.1, debug@npm:^4.3.1, debug@npm:^4.3.2, debug@npm:^4.3.4": - version: 4.3.5 - resolution: "debug@npm:4.3.5" - dependencies: - ms: "npm:2.1.2" - peerDependenciesMeta: - supports-color: - optional: true - checksum: 10c0/082c375a2bdc4f4469c99f325ff458adad62a3fc2c482d59923c260cb08152f34e2659f72b3767db8bb2f21ca81a60a42d1019605a412132d7b9f59363a005cc - languageName: node - linkType: hard - -"decompress-response@npm:^6.0.0": - version: 6.0.0 - resolution: "decompress-response@npm:6.0.0" - dependencies: - mimic-response: "npm:^3.1.0" - checksum: 10c0/bd89d23141b96d80577e70c54fb226b2f40e74a6817652b80a116d7befb8758261ad073a8895648a29cc0a5947021ab66705cb542fa9c143c82022b27c5b175e - languageName: node - linkType: hard - -"dedent@npm:^1.0.0": - version: 1.5.3 - resolution: "dedent@npm:1.5.3" - peerDependencies: - babel-plugin-macros: ^3.1.0 - peerDependenciesMeta: - babel-plugin-macros: - optional: true - checksum: 10c0/d94bde6e6f780be4da4fd760288fcf755ec368872f4ac5218197200d86430aeb8d90a003a840bff1c20221188e3f23adced0119cb811c6873c70d0ac66d12832 - languageName: node - linkType: hard - -"deep-extend@npm:^0.6.0": - version: 0.6.0 - resolution: "deep-extend@npm:0.6.0" - checksum: 10c0/1c6b0abcdb901e13a44c7d699116d3d4279fdb261983122a3783e7273844d5f2537dc2e1c454a23fcf645917f93fbf8d07101c1d03c015a87faa662755212566 - languageName: node - linkType: hard - -"deep-is@npm:^0.1.3": - version: 0.1.4 - resolution: "deep-is@npm:0.1.4" - checksum: 10c0/7f0ee496e0dff14a573dc6127f14c95061b448b87b995fc96c017ce0a1e66af1675e73f1d6064407975bc4ea6ab679497a29fff7b5b9c4e99cb10797c1ad0b4c - languageName: node - linkType: hard - -"deepmerge@npm:^4.2.2": - version: 4.3.1 - resolution: "deepmerge@npm:4.3.1" - checksum: 10c0/e53481aaf1aa2c4082b5342be6b6d8ad9dfe387bc92ce197a66dea08bd4265904a087e75e464f14d1347cf2ac8afe1e4c16b266e0561cc5df29382d3c5f80044 - languageName: node - linkType: hard - -"define-data-property@npm:^1.1.4": - version: 1.1.4 - resolution: "define-data-property@npm:1.1.4" - dependencies: - es-define-property: "npm:^1.0.0" - es-errors: "npm:^1.3.0" - gopd: "npm:^1.0.1" - checksum: 10c0/dea0606d1483eb9db8d930d4eac62ca0fa16738b0b3e07046cddfacf7d8c868bbe13fa0cb263eb91c7d0d527960dc3f2f2471a69ed7816210307f6744fe62e37 - languageName: node - linkType: hard - -"delayed-stream@npm:~1.0.0": - version: 1.0.0 - resolution: "delayed-stream@npm:1.0.0" - checksum: 10c0/d758899da03392e6712f042bec80aa293bbe9e9ff1b2634baae6a360113e708b91326594c8a486d475c69d6259afb7efacdc3537bfcda1c6c648e390ce601b19 - languageName: node - linkType: hard - -"delegates@npm:^1.0.0": - version: 1.0.0 - resolution: "delegates@npm:1.0.0" - checksum: 10c0/ba05874b91148e1db4bf254750c042bf2215febd23a6d3cda2e64896aef79745fbd4b9996488bd3cafb39ce19dbce0fd6e3b6665275638befffe1c9b312b91b5 - languageName: node - linkType: hard - -"depd@npm:2.0.0": - version: 2.0.0 - resolution: "depd@npm:2.0.0" - checksum: 10c0/58bd06ec20e19529b06f7ad07ddab60e504d9e0faca4bd23079fac2d279c3594334d736508dc350e06e510aba5e22e4594483b3a6562ce7c17dd797f4cc4ad2c - languageName: node - linkType: hard - -"destroy@npm:1.2.0": - version: 1.2.0 - resolution: "destroy@npm:1.2.0" - checksum: 10c0/bd7633942f57418f5a3b80d5cb53898127bcf53e24cdf5d5f4396be471417671f0fee48a4ebe9a1e9defbde2a31280011af58a57e090ff822f589b443ed4e643 - languageName: node - linkType: hard - -"detect-libc@npm:^2.0.0": - version: 2.0.3 - resolution: "detect-libc@npm:2.0.3" - checksum: 10c0/88095bda8f90220c95f162bf92cad70bd0e424913e655c20578600e35b91edc261af27531cf160a331e185c0ced93944bc7e09939143225f56312d7fd800fdb7 - languageName: node - linkType: hard - -"detect-newline@npm:^3.0.0": - version: 3.1.0 - resolution: "detect-newline@npm:3.1.0" - checksum: 10c0/c38cfc8eeb9fda09febb44bcd85e467c970d4e3bf526095394e5a4f18bc26dd0cf6b22c69c1fa9969261521c593836db335c2795218f6d781a512aea2fb8209d - languageName: node - linkType: hard - -"dezalgo@npm:^1.0.4": - version: 1.0.4 - resolution: "dezalgo@npm:1.0.4" - dependencies: - asap: "npm:^2.0.0" - wrappy: "npm:1" - checksum: 10c0/8a870ed42eade9a397e6141fe5c025148a59ed52f1f28b1db5de216b4d57f0af7a257070c3af7ce3d5508c1ce9dd5009028a76f4b2cc9370dc56551d2355fad8 - languageName: node - linkType: hard - -"diff-sequences@npm:^29.6.3": - version: 29.6.3 - resolution: "diff-sequences@npm:29.6.3" - checksum: 10c0/32e27ac7dbffdf2fb0eb5a84efd98a9ad084fbabd5ac9abb8757c6770d5320d2acd172830b28c4add29bb873d59420601dfc805ac4064330ce59b1adfd0593b2 - languageName: node - linkType: hard - -"dir-glob@npm:^3.0.1": - version: 3.0.1 - resolution: "dir-glob@npm:3.0.1" - dependencies: - path-type: "npm:^4.0.0" - checksum: 10c0/dcac00920a4d503e38bb64001acb19df4efc14536ada475725e12f52c16777afdee4db827f55f13a908ee7efc0cb282e2e3dbaeeb98c0993dd93d1802d3bf00c - languageName: node - linkType: hard - -"doctrine@npm:^3.0.0": - version: 3.0.0 - resolution: "doctrine@npm:3.0.0" - dependencies: - esutils: "npm:^2.0.2" - checksum: 10c0/c96bdccabe9d62ab6fea9399fdff04a66e6563c1d6fb3a3a063e8d53c3bb136ba63e84250bbf63d00086a769ad53aef92d2bd483f03f837fc97b71cbee6b2520 - languageName: node - linkType: hard - -"dotenv@npm:^10.0.0": - version: 10.0.0 - resolution: "dotenv@npm:10.0.0" - checksum: 10c0/2d8d4ba64bfaff7931402aa5e8cbb8eba0acbc99fe9ae442300199af021079eafa7171ce90e150821a5cb3d74f0057721fbe7ec201a6044b68c8a7615f8c123f - languageName: node - linkType: hard - -"dotenv@npm:^16.0.0": - version: 16.4.5 - resolution: "dotenv@npm:16.4.5" - checksum: 10c0/48d92870076832af0418b13acd6e5a5a3e83bb00df690d9812e94b24aff62b88ade955ac99a05501305b8dc8f1b0ee7638b18493deb6fe93d680e5220936292f - languageName: node - linkType: hard - -"eastasianwidth@npm:^0.2.0": - version: 0.2.0 - resolution: "eastasianwidth@npm:0.2.0" - checksum: 10c0/26f364ebcdb6395f95124fda411f63137a4bfb5d3a06453f7f23dfe52502905bd84e0488172e0f9ec295fdc45f05c23d5d91baf16bd26f0fe9acd777a188dc39 - languageName: node - linkType: hard - -"ecdsa-sig-formatter@npm:1.0.11": - version: 1.0.11 - resolution: "ecdsa-sig-formatter@npm:1.0.11" - dependencies: - safe-buffer: "npm:^5.0.1" - checksum: 10c0/ebfbf19d4b8be938f4dd4a83b8788385da353d63307ede301a9252f9f7f88672e76f2191618fd8edfc2f24679236064176fab0b78131b161ee73daa37125408c - languageName: node - linkType: hard - -"ee-first@npm:1.1.1": - version: 1.1.1 - resolution: "ee-first@npm:1.1.1" - checksum: 10c0/b5bb125ee93161bc16bfe6e56c6b04de5ad2aa44234d8f644813cc95d861a6910903132b05093706de2b706599367c4130eb6d170f6b46895686b95f87d017b7 - languageName: node - linkType: hard - -"electron-to-chromium@npm:^1.4.796": - version: 1.4.805 - resolution: "electron-to-chromium@npm:1.4.805" - checksum: 10c0/90594849ebe1152c1c302183be7bf51642e24626e6d0332f8c56c5ad18d9fb821135e0ed9d0fcf3ec69422d774e48e6c226362be0d8c8efe6b0849225a28d53e - languageName: node - linkType: hard - -"emittery@npm:^0.13.1": - version: 0.13.1 - resolution: "emittery@npm:0.13.1" - checksum: 10c0/1573d0ae29ab34661b6c63251ff8f5facd24ccf6a823f19417ae8ba8c88ea450325788c67f16c99edec8de4b52ce93a10fe441ece389fd156e88ee7dab9bfa35 - languageName: node - linkType: hard - -"emoji-regex@npm:^8.0.0": - version: 8.0.0 - resolution: "emoji-regex@npm:8.0.0" - checksum: 10c0/b6053ad39951c4cf338f9092d7bfba448cdfd46fe6a2a034700b149ac9ffbc137e361cbd3c442297f86bed2e5f7576c1b54cc0a6bf8ef5106cc62f496af35010 - languageName: node - linkType: hard - -"emoji-regex@npm:^9.2.2": - version: 9.2.2 - resolution: "emoji-regex@npm:9.2.2" - checksum: 10c0/af014e759a72064cf66e6e694a7fc6b0ed3d8db680427b021a89727689671cefe9d04151b2cad51dbaf85d5ba790d061cd167f1cf32eb7b281f6368b3c181639 - languageName: node - linkType: hard - -"enabled@npm:2.0.x": - version: 2.0.0 - resolution: "enabled@npm:2.0.0" - checksum: 10c0/3b2c2af9bc7f8b9e291610f2dde4a75cf6ee52a68f4dd585482fbdf9a55d65388940e024e56d40bb03e05ef6671f5f53021fa8b72a20e954d7066ec28166713f - languageName: node - linkType: hard - -"encodeurl@npm:~1.0.2": - version: 1.0.2 - resolution: "encodeurl@npm:1.0.2" - checksum: 10c0/f6c2387379a9e7c1156c1c3d4f9cb7bb11cf16dd4c1682e1f6746512564b053df5781029b6061296832b59fb22f459dbe250386d217c2f6e203601abb2ee0bec - languageName: node - linkType: hard - -"encodeurl@npm:~2.0.0": - version: 2.0.0 - resolution: "encodeurl@npm:2.0.0" - checksum: 10c0/5d317306acb13e6590e28e27924c754163946a2480de11865c991a3a7eed4315cd3fba378b543ca145829569eefe9b899f3d84bb09870f675ae60bc924b01ceb - languageName: node - linkType: hard - -"encoding@npm:^0.1.13": - version: 0.1.13 - resolution: "encoding@npm:0.1.13" - dependencies: - iconv-lite: "npm:^0.6.2" - checksum: 10c0/36d938712ff00fe1f4bac88b43bcffb5930c1efa57bbcdca9d67e1d9d6c57cfb1200fb01efe0f3109b2ce99b231f90779532814a81370a1bd3274a0f58585039 - languageName: node - linkType: hard - -"end-of-stream@npm:^1.1.0, end-of-stream@npm:^1.4.1": - version: 1.4.4 - resolution: "end-of-stream@npm:1.4.4" - dependencies: - once: "npm:^1.4.0" - checksum: 10c0/870b423afb2d54bb8d243c63e07c170409d41e20b47eeef0727547aea5740bd6717aca45597a9f2745525667a6b804c1e7bede41f856818faee5806dd9ff3975 - languageName: node - linkType: hard - -"env-paths@npm:^2.2.0": - version: 2.2.1 - resolution: "env-paths@npm:2.2.1" - checksum: 10c0/285325677bf00e30845e330eec32894f5105529db97496ee3f598478e50f008c5352a41a30e5e72ec9de8a542b5a570b85699cd63bd2bc646dbcb9f311d83bc4 - languageName: node - linkType: hard - -"err-code@npm:^2.0.2": - version: 2.0.3 - resolution: "err-code@npm:2.0.3" - checksum: 10c0/b642f7b4dd4a376e954947550a3065a9ece6733ab8e51ad80db727aaae0817c2e99b02a97a3d6cecc648a97848305e728289cf312d09af395403a90c9d4d8a66 - languageName: node - linkType: hard - -"error-ex@npm:^1.3.1": - version: 1.3.2 - resolution: "error-ex@npm:1.3.2" - dependencies: - is-arrayish: "npm:^0.2.1" - checksum: 10c0/ba827f89369b4c93382cfca5a264d059dfefdaa56ecc5e338ffa58a6471f5ed93b71a20add1d52290a4873d92381174382658c885ac1a2305f7baca363ce9cce - languageName: node - linkType: hard - -"es-define-property@npm:^1.0.0": - version: 1.0.0 - resolution: "es-define-property@npm:1.0.0" - dependencies: - get-intrinsic: "npm:^1.2.4" - checksum: 10c0/6bf3191feb7ea2ebda48b577f69bdfac7a2b3c9bcf97307f55fd6ef1bbca0b49f0c219a935aca506c993d8c5d8bddd937766cb760cd5e5a1071351f2df9f9aa4 - languageName: node - linkType: hard - -"es-errors@npm:^1.3.0": - version: 1.3.0 - resolution: "es-errors@npm:1.3.0" - checksum: 10c0/0a61325670072f98d8ae3b914edab3559b6caa980f08054a3b872052640d91da01d38df55df797fcc916389d77fc92b8d5906cf028f4db46d7e3003abecbca85 - languageName: node - linkType: hard - -"escalade@npm:^3.1.1, escalade@npm:^3.1.2": - version: 3.1.2 - resolution: "escalade@npm:3.1.2" - checksum: 10c0/6b4adafecd0682f3aa1cd1106b8fff30e492c7015b178bc81b2d2f75106dabea6c6d6e8508fc491bd58e597c74abb0e8e2368f943ecb9393d4162e3c2f3cf287 - languageName: node - linkType: hard - -"escape-html@npm:~1.0.3": - version: 1.0.3 - resolution: "escape-html@npm:1.0.3" - checksum: 10c0/524c739d776b36c3d29fa08a22e03e8824e3b2fd57500e5e44ecf3cc4707c34c60f9ca0781c0e33d191f2991161504c295e98f68c78fe7baa6e57081ec6ac0a3 - languageName: node - linkType: hard - -"escape-string-regexp@npm:^1.0.5": - version: 1.0.5 - resolution: "escape-string-regexp@npm:1.0.5" - checksum: 10c0/a968ad453dd0c2724e14a4f20e177aaf32bb384ab41b674a8454afe9a41c5e6fe8903323e0a1052f56289d04bd600f81278edf140b0fcc02f5cac98d0f5b5371 - languageName: node - linkType: hard - -"escape-string-regexp@npm:^2.0.0": - version: 2.0.0 - resolution: "escape-string-regexp@npm:2.0.0" - checksum: 10c0/2530479fe8db57eace5e8646c9c2a9c80fa279614986d16dcc6bcaceb63ae77f05a851ba6c43756d816c61d7f4534baf56e3c705e3e0d884818a46808811c507 - languageName: node - linkType: hard - -"escape-string-regexp@npm:^4.0.0": - version: 4.0.0 - resolution: "escape-string-regexp@npm:4.0.0" - checksum: 10c0/9497d4dd307d845bd7f75180d8188bb17ea8c151c1edbf6b6717c100e104d629dc2dfb687686181b0f4b7d732c7dfdc4d5e7a8ff72de1b0ca283a75bbb3a9cd9 - languageName: node - linkType: hard - -"eslint-plugin-prettier@npm:^4.2.1": - version: 4.2.1 - resolution: "eslint-plugin-prettier@npm:4.2.1" - dependencies: - prettier-linter-helpers: "npm:^1.0.0" - peerDependencies: - eslint: ">=7.28.0" - prettier: ">=2.0.0" - peerDependenciesMeta: - eslint-config-prettier: - optional: true - checksum: 10c0/c5e7316baeab9d96ac39c279f16686e837277e5c67a8006c6588bcff317edffdc1532fb580441eb598bc6770f6444006756b68a6575dff1cd85ebe227252d0b7 - languageName: node - linkType: hard - -"eslint-scope@npm:^5.1.1": - version: 5.1.1 - resolution: "eslint-scope@npm:5.1.1" - dependencies: - esrecurse: "npm:^4.3.0" - estraverse: "npm:^4.1.1" - checksum: 10c0/d30ef9dc1c1cbdece34db1539a4933fe3f9b14e1ffb27ecc85987902ee663ad7c9473bbd49a9a03195a373741e62e2f807c4938992e019b511993d163450e70a - languageName: node - linkType: hard - -"eslint-scope@npm:^7.2.2": - version: 7.2.2 - resolution: "eslint-scope@npm:7.2.2" - dependencies: - esrecurse: "npm:^4.3.0" - estraverse: "npm:^5.2.0" - checksum: 10c0/613c267aea34b5a6d6c00514e8545ef1f1433108097e857225fed40d397dd6b1809dffd11c2fde23b37ca53d7bf935fe04d2a18e6fc932b31837b6ad67e1c116 - languageName: node - linkType: hard - -"eslint-visitor-keys@npm:^3.3.0, eslint-visitor-keys@npm:^3.4.1, eslint-visitor-keys@npm:^3.4.3": - version: 3.4.3 - resolution: "eslint-visitor-keys@npm:3.4.3" - checksum: 10c0/92708e882c0a5ffd88c23c0b404ac1628cf20104a108c745f240a13c332a11aac54f49a22d5762efbffc18ecbc9a580d1b7ad034bf5f3cc3307e5cbff2ec9820 - languageName: node - linkType: hard - -"eslint@npm:^8.33.0": - version: 8.57.0 - resolution: "eslint@npm:8.57.0" - dependencies: - "@eslint-community/eslint-utils": "npm:^4.2.0" - "@eslint-community/regexpp": "npm:^4.6.1" - "@eslint/eslintrc": "npm:^2.1.4" - "@eslint/js": "npm:8.57.0" - "@humanwhocodes/config-array": "npm:^0.11.14" - "@humanwhocodes/module-importer": "npm:^1.0.1" - "@nodelib/fs.walk": "npm:^1.2.8" - "@ungap/structured-clone": "npm:^1.2.0" - ajv: "npm:^6.12.4" - chalk: "npm:^4.0.0" - cross-spawn: "npm:^7.0.2" - debug: "npm:^4.3.2" - doctrine: "npm:^3.0.0" - escape-string-regexp: "npm:^4.0.0" - eslint-scope: "npm:^7.2.2" - eslint-visitor-keys: "npm:^3.4.3" - espree: "npm:^9.6.1" - esquery: "npm:^1.4.2" - esutils: "npm:^2.0.2" - fast-deep-equal: "npm:^3.1.3" - file-entry-cache: "npm:^6.0.1" - find-up: "npm:^5.0.0" - glob-parent: "npm:^6.0.2" - globals: "npm:^13.19.0" - graphemer: "npm:^1.4.0" - ignore: "npm:^5.2.0" - imurmurhash: "npm:^0.1.4" - is-glob: "npm:^4.0.0" - is-path-inside: "npm:^3.0.3" - js-yaml: "npm:^4.1.0" - json-stable-stringify-without-jsonify: "npm:^1.0.1" - levn: "npm:^0.4.1" - lodash.merge: "npm:^4.6.2" - minimatch: "npm:^3.1.2" - natural-compare: "npm:^1.4.0" - optionator: "npm:^0.9.3" - strip-ansi: "npm:^6.0.1" - text-table: "npm:^0.2.0" - bin: - eslint: bin/eslint.js - checksum: 10c0/00bb96fd2471039a312435a6776fe1fd557c056755eaa2b96093ef3a8508c92c8775d5f754768be6b1dddd09fdd3379ddb231eeb9b6c579ee17ea7d68000a529 - languageName: node - linkType: hard - -"espree@npm:^9.6.0, espree@npm:^9.6.1": - version: 9.6.1 - resolution: "espree@npm:9.6.1" - dependencies: - acorn: "npm:^8.9.0" - acorn-jsx: "npm:^5.3.2" - eslint-visitor-keys: "npm:^3.4.1" - checksum: 10c0/1a2e9b4699b715347f62330bcc76aee224390c28bb02b31a3752e9d07549c473f5f986720483c6469cf3cfb3c9d05df612ffc69eb1ee94b54b739e67de9bb460 - languageName: node - linkType: hard - -"esprima@npm:^4.0.0": - version: 4.0.1 - resolution: "esprima@npm:4.0.1" - bin: - esparse: ./bin/esparse.js - esvalidate: ./bin/esvalidate.js - checksum: 10c0/ad4bab9ead0808cf56501750fd9d3fb276f6b105f987707d059005d57e182d18a7c9ec7f3a01794ebddcca676773e42ca48a32d67a250c9d35e009ca613caba3 - languageName: node - linkType: hard - -"esquery@npm:^1.4.2": - version: 1.5.0 - resolution: "esquery@npm:1.5.0" - dependencies: - estraverse: "npm:^5.1.0" - checksum: 10c0/a084bd049d954cc88ac69df30534043fb2aee5555b56246493f42f27d1e168f00d9e5d4192e46f10290d312dc30dc7d58994d61a609c579c1219d636996f9213 - languageName: node - linkType: hard - -"esrecurse@npm:^4.3.0": - version: 4.3.0 - resolution: "esrecurse@npm:4.3.0" - dependencies: - estraverse: "npm:^5.2.0" - checksum: 10c0/81a37116d1408ded88ada45b9fb16dbd26fba3aadc369ce50fcaf82a0bac12772ebd7b24cd7b91fc66786bf2c1ac7b5f196bc990a473efff972f5cb338877cf5 - languageName: node - linkType: hard - -"estraverse@npm:^4.1.1": - version: 4.3.0 - resolution: "estraverse@npm:4.3.0" - checksum: 10c0/9cb46463ef8a8a4905d3708a652d60122a0c20bb58dec7e0e12ab0e7235123d74214fc0141d743c381813e1b992767e2708194f6f6e0f9fd00c1b4e0887b8b6d - languageName: node - linkType: hard - -"estraverse@npm:^5.1.0, estraverse@npm:^5.2.0": - version: 5.3.0 - resolution: "estraverse@npm:5.3.0" - checksum: 10c0/1ff9447b96263dec95d6d67431c5e0771eb9776427421260a3e2f0fdd5d6bd4f8e37a7338f5ad2880c9f143450c9b1e4fc2069060724570a49cf9cf0312bd107 - languageName: node - linkType: hard - -"esutils@npm:^2.0.2": - version: 2.0.3 - resolution: "esutils@npm:2.0.3" - checksum: 10c0/9a2fe69a41bfdade834ba7c42de4723c97ec776e40656919c62cbd13607c45e127a003f05f724a1ea55e5029a4cf2de444b13009f2af71271e42d93a637137c7 - languageName: node - linkType: hard - -"etag@npm:~1.8.1": - version: 1.8.1 - resolution: "etag@npm:1.8.1" - checksum: 10c0/12be11ef62fb9817314d790089a0a49fae4e1b50594135dcb8076312b7d7e470884b5100d249b28c18581b7fd52f8b485689ffae22a11ed9ec17377a33a08f84 - languageName: node - linkType: hard - -"execa@npm:^5.0.0": - version: 5.1.1 - resolution: "execa@npm:5.1.1" - dependencies: - cross-spawn: "npm:^7.0.3" - get-stream: "npm:^6.0.0" - human-signals: "npm:^2.1.0" - is-stream: "npm:^2.0.0" - merge-stream: "npm:^2.0.0" - npm-run-path: "npm:^4.0.1" - onetime: "npm:^5.1.2" - signal-exit: "npm:^3.0.3" - strip-final-newline: "npm:^2.0.0" - checksum: 10c0/c8e615235e8de4c5addf2fa4c3da3e3aa59ce975a3e83533b4f6a71750fb816a2e79610dc5f1799b6e28976c9ae86747a36a606655bf8cb414a74d8d507b304f - languageName: node - linkType: hard - -"exit@npm:^0.1.2": - version: 0.1.2 - resolution: "exit@npm:0.1.2" - checksum: 10c0/71d2ad9b36bc25bb8b104b17e830b40a08989be7f7d100b13269aaae7c3784c3e6e1e88a797e9e87523993a25ba27c8958959a554535370672cfb4d824af8989 - languageName: node - linkType: hard - -"expand-template@npm:^2.0.3": - version: 2.0.3 - resolution: "expand-template@npm:2.0.3" - checksum: 10c0/1c9e7afe9acadf9d373301d27f6a47b34e89b3391b1ef38b7471d381812537ef2457e620ae7f819d2642ce9c43b189b3583813ec395e2938319abe356a9b2f51 - languageName: node - linkType: hard - -"expect@npm:^29.0.0, expect@npm:^29.7.0": - version: 29.7.0 - resolution: "expect@npm:29.7.0" - dependencies: - "@jest/expect-utils": "npm:^29.7.0" - jest-get-type: "npm:^29.6.3" - jest-matcher-utils: "npm:^29.7.0" - jest-message-util: "npm:^29.7.0" - jest-util: "npm:^29.7.0" - checksum: 10c0/2eddeace66e68b8d8ee5f7be57f3014b19770caaf6815c7a08d131821da527fb8c8cb7b3dcd7c883d2d3d8d184206a4268984618032d1e4b16dc8d6596475d41 - languageName: node - linkType: hard - -"exponential-backoff@npm:^3.1.1": - version: 3.1.1 - resolution: "exponential-backoff@npm:3.1.1" - checksum: 10c0/160456d2d647e6019640bd07111634d8c353038d9fa40176afb7cd49b0548bdae83b56d05e907c2cce2300b81cae35d800ef92fefb9d0208e190fa3b7d6bb579 - languageName: node - linkType: hard - -"express-actuator@npm:1.8.4": - version: 1.8.4 - resolution: "express-actuator@npm:1.8.4" - dependencies: - dayjs: "npm:^1.11.3" - properties-reader: "npm:^2.2.0" - checksum: 10c0/216264ba977b34d59de95721924354d7b4090b62801f225eac7f861fd8e40251e215826d7b462e57b86d02143eabdff2384b50d17d207e54a61b5aaaa726ad83 - languageName: node - linkType: hard - -"express-rate-limit@npm:^6.7.0": - version: 6.11.2 - resolution: "express-rate-limit@npm:6.11.2" - peerDependencies: - express: ^4 || ^5 - checksum: 10c0/1f92107fca92423b6311ca26cb79a3d5d79ac5f5eb81791c382c2323e331da0e993249225615003e2db91b617d69ba0d428ef60d212bfabb7a83244645f10e0a - languageName: node - linkType: hard - -"express-response-size@npm:^0.0.3": - version: 0.0.3 - resolution: "express-response-size@npm:0.0.3" - dependencies: - on-headers: "npm:1.0.1" - checksum: 10c0/9203d192c7d7b48b4ea8710aa90d78522020d469963f9e83355a140eb1b13e40c03caa752b8f9d0b6bb401fe89958c5a043f28278ff514c90af8dc7d51409bc7 - languageName: node - linkType: hard - -"express-winston@npm:^4.2.0": - version: 4.2.0 - resolution: "express-winston@npm:4.2.0" - dependencies: - chalk: "npm:^2.4.2" - lodash: "npm:^4.17.21" - peerDependencies: - winston: ">=3.x <4" - checksum: 10c0/8f80993e7d7696b22a12c68ffb72ea0cd3ad980d8394073fd972162cdca116b8f506974dd504987e350cddfdbf55402871762dcb9c69f2f7feaef2df6d93ef09 - languageName: node - linkType: hard - -"express@npm:4.20.0": - version: 4.20.0 - resolution: "express@npm:4.20.0" - dependencies: - accepts: "npm:~1.3.8" - array-flatten: "npm:1.1.1" - body-parser: "npm:1.20.3" - content-disposition: "npm:0.5.4" - content-type: "npm:~1.0.4" - cookie: "npm:0.6.0" - cookie-signature: "npm:1.0.6" - debug: "npm:2.6.9" - depd: "npm:2.0.0" - encodeurl: "npm:~2.0.0" - escape-html: "npm:~1.0.3" - etag: "npm:~1.8.1" - finalhandler: "npm:1.2.0" - fresh: "npm:0.5.2" - http-errors: "npm:2.0.0" - merge-descriptors: "npm:1.0.3" - methods: "npm:~1.1.2" - on-finished: "npm:2.4.1" - parseurl: "npm:~1.3.3" - path-to-regexp: "npm:0.1.10" - proxy-addr: "npm:~2.0.7" - qs: "npm:6.11.0" - range-parser: "npm:~1.2.1" - safe-buffer: "npm:5.2.1" - send: "npm:0.19.0" - serve-static: "npm:1.16.0" - setprototypeof: "npm:1.2.0" - statuses: "npm:2.0.1" - type-is: "npm:~1.6.18" - utils-merge: "npm:1.0.1" - vary: "npm:~1.1.2" - checksum: 10c0/626e440e9feffa3f82ebce5e7dc0ad7a74fa96079994f30048cce450f4855a258abbcabf021f691aeb72154867f0d28440a8498c62888805faf667a829fb65aa - languageName: node - linkType: hard - -"fast-deep-equal@npm:^3.1.1, fast-deep-equal@npm:^3.1.3": - version: 3.1.3 - resolution: "fast-deep-equal@npm:3.1.3" - checksum: 10c0/40dedc862eb8992c54579c66d914635afbec43350afbbe991235fdcb4e3a8d5af1b23ae7e79bef7d4882d0ecee06c3197488026998fb19f72dc95acff1d1b1d0 - languageName: node - linkType: hard - -"fast-diff@npm:^1.1.2": - version: 1.3.0 - resolution: "fast-diff@npm:1.3.0" - checksum: 10c0/5c19af237edb5d5effda008c891a18a585f74bf12953be57923f17a3a4d0979565fc64dbc73b9e20926b9d895f5b690c618cbb969af0cf022e3222471220ad29 - languageName: node - linkType: hard - -"fast-glob@npm:^3.2.9": - version: 3.3.2 - resolution: "fast-glob@npm:3.3.2" - dependencies: - "@nodelib/fs.stat": "npm:^2.0.2" - "@nodelib/fs.walk": "npm:^1.2.3" - glob-parent: "npm:^5.1.2" - merge2: "npm:^1.3.0" - micromatch: "npm:^4.0.4" - checksum: 10c0/42baad7b9cd40b63e42039132bde27ca2cb3a4950d0a0f9abe4639ea1aa9d3e3b40f98b1fe31cbc0cc17b664c9ea7447d911a152fa34ec5b72977b125a6fc845 - languageName: node - linkType: hard - -"fast-json-stable-stringify@npm:^2.0.0, fast-json-stable-stringify@npm:^2.1.0": - version: 2.1.0 - resolution: "fast-json-stable-stringify@npm:2.1.0" - checksum: 10c0/7f081eb0b8a64e0057b3bb03f974b3ef00135fbf36c1c710895cd9300f13c94ba809bb3a81cf4e1b03f6e5285610a61abbd7602d0652de423144dfee5a389c9b - languageName: node - linkType: hard - -"fast-levenshtein@npm:^2.0.6": - version: 2.0.6 - resolution: "fast-levenshtein@npm:2.0.6" - checksum: 10c0/111972b37338bcb88f7d9e2c5907862c280ebf4234433b95bc611e518d192ccb2d38119c4ac86e26b668d75f7f3894f4ff5c4982899afced7ca78633b08287c4 - languageName: node - linkType: hard - -"fast-safe-stringify@npm:^2.1.1": - version: 2.1.1 - resolution: "fast-safe-stringify@npm:2.1.1" - checksum: 10c0/d90ec1c963394919828872f21edaa3ad6f1dddd288d2bd4e977027afff09f5db40f94e39536d4646f7e01761d704d72d51dce5af1b93717f3489ef808f5f4e4d - languageName: node - linkType: hard - -"fastq@npm:^1.6.0": - version: 1.17.1 - resolution: "fastq@npm:1.17.1" - dependencies: - reusify: "npm:^1.0.4" - checksum: 10c0/1095f16cea45fb3beff558bb3afa74ca7a9250f5a670b65db7ed585f92b4b48381445cd328b3d87323da81e43232b5d5978a8201bde84e0cd514310f1ea6da34 - languageName: node - linkType: hard - -"fb-watchman@npm:^2.0.0": - version: 2.0.2 - resolution: "fb-watchman@npm:2.0.2" - dependencies: - bser: "npm:2.1.1" - checksum: 10c0/feae89ac148adb8f6ae8ccd87632e62b13563e6fb114cacb5265c51f585b17e2e268084519fb2edd133872f1d47a18e6bfd7e5e08625c0d41b93149694187581 - languageName: node - linkType: hard - -"fecha@npm:^4.2.0": - version: 4.2.3 - resolution: "fecha@npm:4.2.3" - checksum: 10c0/0e895965959cf6a22bb7b00f0bf546f2783836310f510ddf63f463e1518d4c96dec61ab33fdfd8e79a71b4856a7c865478ce2ee8498d560fe125947703c9b1cf - languageName: node - linkType: hard - -"file-entry-cache@npm:^6.0.1": - version: 6.0.1 - resolution: "file-entry-cache@npm:6.0.1" - dependencies: - flat-cache: "npm:^3.0.4" - checksum: 10c0/58473e8a82794d01b38e5e435f6feaf648e3f36fdb3a56e98f417f4efae71ad1c0d4ebd8a9a7c50c3ad085820a93fc7494ad721e0e4ebc1da3573f4e1c3c7cdd - languageName: node - linkType: hard - -"file-uri-to-path@npm:1.0.0": - version: 1.0.0 - resolution: "file-uri-to-path@npm:1.0.0" - checksum: 10c0/3b545e3a341d322d368e880e1c204ef55f1d45cdea65f7efc6c6ce9e0c4d22d802d5629320eb779d006fe59624ac17b0e848d83cc5af7cd101f206cb704f5519 - languageName: node - linkType: hard - -"fill-range@npm:^7.1.1": - version: 7.1.1 - resolution: "fill-range@npm:7.1.1" - dependencies: - to-regex-range: "npm:^5.0.1" - checksum: 10c0/b75b691bbe065472f38824f694c2f7449d7f5004aa950426a2c28f0306c60db9b880c0b0e4ed819997ffb882d1da02cfcfc819bddc94d71627f5269682edf018 - languageName: node - linkType: hard - -"finalhandler@npm:1.2.0": - version: 1.2.0 - resolution: "finalhandler@npm:1.2.0" - dependencies: - debug: "npm:2.6.9" - encodeurl: "npm:~1.0.2" - escape-html: "npm:~1.0.3" - on-finished: "npm:2.4.1" - parseurl: "npm:~1.3.3" - statuses: "npm:2.0.1" - unpipe: "npm:~1.0.0" - checksum: 10c0/64b7e5ff2ad1fcb14931cd012651631b721ce657da24aedb5650ddde9378bf8e95daa451da43398123f5de161a81e79ff5affe4f9f2a6d2df4a813d6d3e254b7 - languageName: node - linkType: hard - -"find-up@npm:^4.0.0, find-up@npm:^4.1.0": - version: 4.1.0 - resolution: "find-up@npm:4.1.0" - dependencies: - locate-path: "npm:^5.0.0" - path-exists: "npm:^4.0.0" - checksum: 10c0/0406ee89ebeefa2d507feb07ec366bebd8a6167ae74aa4e34fb4c4abd06cf782a3ce26ae4194d70706f72182841733f00551c209fe575cb00bd92104056e78c1 - languageName: node - linkType: hard - -"find-up@npm:^5.0.0": - version: 5.0.0 - resolution: "find-up@npm:5.0.0" - dependencies: - locate-path: "npm:^6.0.0" - path-exists: "npm:^4.0.0" - checksum: 10c0/062c5a83a9c02f53cdd6d175a37ecf8f87ea5bbff1fdfb828f04bfa021441bc7583e8ebc0872a4c1baab96221fb8a8a275a19809fb93fbc40bd69ec35634069a - languageName: node - linkType: hard - -"flat-cache@npm:^3.0.4": - version: 3.2.0 - resolution: "flat-cache@npm:3.2.0" - dependencies: - flatted: "npm:^3.2.9" - keyv: "npm:^4.5.3" - rimraf: "npm:^3.0.2" - checksum: 10c0/b76f611bd5f5d68f7ae632e3ae503e678d205cf97a17c6ab5b12f6ca61188b5f1f7464503efae6dc18683ed8f0b41460beb48ac4b9ac63fe6201296a91ba2f75 - languageName: node - linkType: hard - -"flatted@npm:^3.2.9": - version: 3.3.1 - resolution: "flatted@npm:3.3.1" - checksum: 10c0/324166b125ee07d4ca9bcf3a5f98d915d5db4f39d711fba640a3178b959919aae1f7cfd8aabcfef5826ed8aa8a2aa14cc85b2d7d18ff638ddf4ae3df39573eaf - languageName: node - linkType: hard - -"fn.name@npm:1.x.x": - version: 1.1.0 - resolution: "fn.name@npm:1.1.0" - checksum: 10c0/8ad62aa2d4f0b2a76d09dba36cfec61c540c13a0fd72e5d94164e430f987a7ce6a743112bbeb14877c810ef500d1f73d7f56e76d029d2e3413f20d79e3460a9a - languageName: node - linkType: hard - -"follow-redirects@npm:^1.15.6": - version: 1.15.6 - resolution: "follow-redirects@npm:1.15.6" - peerDependenciesMeta: - debug: - optional: true - checksum: 10c0/9ff767f0d7be6aa6870c82ac79cf0368cd73e01bbc00e9eb1c2a16fbb198ec105e3c9b6628bb98e9f3ac66fe29a957b9645bcb9a490bb7aa0d35f908b6b85071 - languageName: node - linkType: hard - -"foreground-child@npm:^3.1.0": - version: 3.2.1 - resolution: "foreground-child@npm:3.2.1" - dependencies: - cross-spawn: "npm:^7.0.0" - signal-exit: "npm:^4.0.1" - checksum: 10c0/9a53a33dbd87090e9576bef65fb4a71de60f6863a8062a7b11bc1cbe3cc86d428677d7c0b9ef61cdac11007ac580006f78bd5638618d564cfd5e6fd713d6878f - languageName: node - linkType: hard - -"form-data@npm:^4.0.0": - version: 4.0.0 - resolution: "form-data@npm:4.0.0" - dependencies: - asynckit: "npm:^0.4.0" - combined-stream: "npm:^1.0.8" - mime-types: "npm:^2.1.12" - checksum: 10c0/cb6f3ac49180be03ff07ba3ff125f9eba2ff0b277fb33c7fc47569fc5e616882c5b1c69b9904c4c4187e97dd0419dd03b134174756f296dec62041e6527e2c6e - languageName: node - linkType: hard - -"formidable@npm:^2.1.2": - version: 2.1.2 - resolution: "formidable@npm:2.1.2" - dependencies: - dezalgo: "npm:^1.0.4" - hexoid: "npm:^1.0.0" - once: "npm:^1.4.0" - qs: "npm:^6.11.0" - checksum: 10c0/efba03d11127098daa6ef54c3c0fad25693973eb902fa88ccaaa203baebe8c74d12ba0fe1e113eccf79b9172510fa337e4e107330b124fb3a8c74697b4aa2ce3 - languageName: node - linkType: hard - -"forwarded@npm:0.2.0": - version: 0.2.0 - resolution: "forwarded@npm:0.2.0" - checksum: 10c0/9b67c3fac86acdbc9ae47ba1ddd5f2f81526fa4c8226863ede5600a3f7c7416ef451f6f1e240a3cc32d0fd79fcfe6beb08fd0da454f360032bde70bf80afbb33 - languageName: node - linkType: hard - -"fresh@npm:0.5.2": - version: 0.5.2 - resolution: "fresh@npm:0.5.2" - checksum: 10c0/c6d27f3ed86cc5b601404822f31c900dd165ba63fff8152a3ef714e2012e7535027063bc67ded4cb5b3a49fa596495d46cacd9f47d6328459cf570f08b7d9e5a - languageName: node - linkType: hard - -"fs-constants@npm:^1.0.0": - version: 1.0.0 - resolution: "fs-constants@npm:1.0.0" - checksum: 10c0/a0cde99085f0872f4d244e83e03a46aa387b74f5a5af750896c6b05e9077fac00e9932fdf5aef84f2f16634cd473c63037d7a512576da7d5c2b9163d1909f3a8 - languageName: node - linkType: hard - -"fs-minipass@npm:^2.0.0": - version: 2.1.0 - resolution: "fs-minipass@npm:2.1.0" - dependencies: - minipass: "npm:^3.0.0" - checksum: 10c0/703d16522b8282d7299337539c3ed6edddd1afe82435e4f5b76e34a79cd74e488a8a0e26a636afc2440e1a23b03878e2122e3a2cfe375a5cf63c37d92b86a004 - languageName: node - linkType: hard - -"fs-minipass@npm:^3.0.0": - version: 3.0.3 - resolution: "fs-minipass@npm:3.0.3" - dependencies: - minipass: "npm:^7.0.3" - checksum: 10c0/63e80da2ff9b621e2cb1596abcb9207f1cf82b968b116ccd7b959e3323144cce7fb141462200971c38bbf2ecca51695069db45265705bed09a7cd93ae5b89f94 - languageName: node - linkType: hard - -"fs.realpath@npm:^1.0.0": - version: 1.0.0 - resolution: "fs.realpath@npm:1.0.0" - checksum: 10c0/444cf1291d997165dfd4c0d58b69f0e4782bfd9149fd72faa4fe299e68e0e93d6db941660b37dd29153bf7186672ececa3b50b7e7249477b03fdf850f287c948 - languageName: node - linkType: hard - -"fsevents@npm:^2.3.2": - version: 2.3.3 - resolution: "fsevents@npm:2.3.3" - dependencies: - node-gyp: "npm:latest" - checksum: 10c0/a1f0c44595123ed717febbc478aa952e47adfc28e2092be66b8ab1635147254ca6cfe1df792a8997f22716d4cbafc73309899ff7bfac2ac3ad8cf2e4ecc3ec60 - conditions: os=darwin - languageName: node - linkType: hard - -"fsevents@patch:fsevents@npm%3A^2.3.2#optional!builtin<compat/fsevents>": - version: 2.3.3 - resolution: "fsevents@patch:fsevents@npm%3A2.3.3#optional!builtin<compat/fsevents>::version=2.3.3&hash=df0bf1" - dependencies: - node-gyp: "npm:latest" - conditions: os=darwin - languageName: node - linkType: hard - -"function-bind@npm:^1.1.2": - version: 1.1.2 - resolution: "function-bind@npm:1.1.2" - checksum: 10c0/d8680ee1e5fcd4c197e4ac33b2b4dce03c71f4d91717292785703db200f5c21f977c568d28061226f9b5900cbcd2c84463646134fd5337e7925e0942bc3f46d5 - languageName: node - linkType: hard - -"gauge@npm:^3.0.0": - version: 3.0.2 - resolution: "gauge@npm:3.0.2" - dependencies: - aproba: "npm:^1.0.3 || ^2.0.0" - color-support: "npm:^1.1.2" - console-control-strings: "npm:^1.0.0" - has-unicode: "npm:^2.0.1" - object-assign: "npm:^4.1.1" - signal-exit: "npm:^3.0.0" - string-width: "npm:^4.2.3" - strip-ansi: "npm:^6.0.1" - wide-align: "npm:^1.1.2" - checksum: 10c0/75230ccaf216471e31025c7d5fcea1629596ca20792de50c596eb18ffb14d8404f927cd55535aab2eeecd18d1e11bd6f23ec3c2e9878d2dda1dc74bccc34b913 - languageName: node - linkType: hard - -"gensync@npm:^1.0.0-beta.2": - version: 1.0.0-beta.2 - resolution: "gensync@npm:1.0.0-beta.2" - checksum: 10c0/782aba6cba65b1bb5af3b095d96249d20edbe8df32dbf4696fd49be2583faf676173bf4809386588828e4dd76a3354fcbeb577bab1c833ccd9fc4577f26103f8 - languageName: node - linkType: hard - -"get-caller-file@npm:^2.0.5": - version: 2.0.5 - resolution: "get-caller-file@npm:2.0.5" - checksum: 10c0/c6c7b60271931fa752aeb92f2b47e355eac1af3a2673f47c9589e8f8a41adc74d45551c1bc57b5e66a80609f10ffb72b6f575e4370d61cc3f7f3aaff01757cde - languageName: node - linkType: hard - -"get-intrinsic@npm:^1.1.3, get-intrinsic@npm:^1.2.4": - version: 1.2.4 - resolution: "get-intrinsic@npm:1.2.4" - dependencies: - es-errors: "npm:^1.3.0" - function-bind: "npm:^1.1.2" - has-proto: "npm:^1.0.1" - has-symbols: "npm:^1.0.3" - hasown: "npm:^2.0.0" - checksum: 10c0/0a9b82c16696ed6da5e39b1267104475c47e3a9bdbe8b509dfe1710946e38a87be70d759f4bb3cda042d76a41ef47fe769660f3b7c0d1f68750299344ffb15b7 - languageName: node - linkType: hard - -"get-package-type@npm:^0.1.0": - version: 0.1.0 - resolution: "get-package-type@npm:0.1.0" - checksum: 10c0/e34cdf447fdf1902a1f6d5af737eaadf606d2ee3518287abde8910e04159368c268568174b2e71102b87b26c2020486f126bfca9c4fb1ceb986ff99b52ecd1be - languageName: node - linkType: hard - -"get-stream@npm:^6.0.0": - version: 6.0.1 - resolution: "get-stream@npm:6.0.1" - checksum: 10c0/49825d57d3fd6964228e6200a58169464b8e8970489b3acdc24906c782fb7f01f9f56f8e6653c4a50713771d6658f7cfe051e5eb8c12e334138c9c918b296341 - languageName: node - linkType: hard - -"github-from-package@npm:0.0.0": - version: 0.0.0 - resolution: "github-from-package@npm:0.0.0" - checksum: 10c0/737ee3f52d0a27e26332cde85b533c21fcdc0b09fb716c3f8e522cfaa9c600d4a631dec9fcde179ec9d47cca89017b7848ed4d6ae6b6b78f936c06825b1fcc12 - languageName: node - linkType: hard - -"glob-parent@npm:^5.1.2": - version: 5.1.2 - resolution: "glob-parent@npm:5.1.2" - dependencies: - is-glob: "npm:^4.0.1" - checksum: 10c0/cab87638e2112bee3f839ef5f6e0765057163d39c66be8ec1602f3823da4692297ad4e972de876ea17c44d652978638d2fd583c6713d0eb6591706825020c9ee - languageName: node - linkType: hard - -"glob-parent@npm:^6.0.2": - version: 6.0.2 - resolution: "glob-parent@npm:6.0.2" - dependencies: - is-glob: "npm:^4.0.3" - checksum: 10c0/317034d88654730230b3f43bb7ad4f7c90257a426e872ea0bf157473ac61c99bf5d205fad8f0185f989be8d2fa6d3c7dce1645d99d545b6ea9089c39f838e7f8 - languageName: node - linkType: hard - -"glob@npm:^10.2.2, glob@npm:^10.3.10": - version: 10.4.1 - resolution: "glob@npm:10.4.1" - dependencies: - foreground-child: "npm:^3.1.0" - jackspeak: "npm:^3.1.2" - minimatch: "npm:^9.0.4" - minipass: "npm:^7.1.2" - path-scurry: "npm:^1.11.1" - bin: - glob: dist/esm/bin.mjs - checksum: 10c0/77f2900ed98b9cc2a0e1901ee5e476d664dae3cd0f1b662b8bfd4ccf00d0edc31a11595807706a274ca10e1e251411bbf2e8e976c82bed0d879a9b89343ed379 - languageName: node - linkType: hard - -"glob@npm:^7.1.3, glob@npm:^7.1.4": - version: 7.2.3 - resolution: "glob@npm:7.2.3" - dependencies: - fs.realpath: "npm:^1.0.0" - inflight: "npm:^1.0.4" - inherits: "npm:2" - minimatch: "npm:^3.1.1" - once: "npm:^1.3.0" - path-is-absolute: "npm:^1.0.0" - checksum: 10c0/65676153e2b0c9095100fe7f25a778bf45608eeb32c6048cf307f579649bcc30353277b3b898a3792602c65764e5baa4f643714dfbdfd64ea271d210c7a425fe - languageName: node - linkType: hard - -"globals@npm:^11.1.0": - version: 11.12.0 - resolution: "globals@npm:11.12.0" - checksum: 10c0/758f9f258e7b19226bd8d4af5d3b0dcf7038780fb23d82e6f98932c44e239f884847f1766e8fa9cc5635ccb3204f7fa7314d4408dd4002a5e8ea827b4018f0a1 - languageName: node - linkType: hard - -"globals@npm:^13.19.0": - version: 13.24.0 - resolution: "globals@npm:13.24.0" - dependencies: - type-fest: "npm:^0.20.2" - checksum: 10c0/d3c11aeea898eb83d5ec7a99508600fbe8f83d2cf00cbb77f873dbf2bcb39428eff1b538e4915c993d8a3b3473fa71eeebfe22c9bb3a3003d1e26b1f2c8a42cd - languageName: node - linkType: hard - -"globby@npm:^11.1.0": - version: 11.1.0 - resolution: "globby@npm:11.1.0" - dependencies: - array-union: "npm:^2.1.0" - dir-glob: "npm:^3.0.1" - fast-glob: "npm:^3.2.9" - ignore: "npm:^5.2.0" - merge2: "npm:^1.4.1" - slash: "npm:^3.0.0" - checksum: 10c0/b39511b4afe4bd8a7aead3a27c4ade2b9968649abab0a6c28b1a90141b96ca68ca5db1302f7c7bd29eab66bf51e13916b8e0a3d0ac08f75e1e84a39b35691189 - languageName: node - linkType: hard - -"google-protobuf@npm:^3.12.0-rc.1": - version: 3.21.2 - resolution: "google-protobuf@npm:3.21.2" - checksum: 10c0/df20b41aad9eba4d842d69c717a4d73ac6d321084c12f524ad5eb79a47ad185323bd1b477c19565a15fd08b6eef29e475c8ac281dbc6fe547b81d8b6b99974f5 - languageName: node - linkType: hard - -"gopd@npm:^1.0.1": - version: 1.0.1 - resolution: "gopd@npm:1.0.1" - dependencies: - get-intrinsic: "npm:^1.1.3" - checksum: 10c0/505c05487f7944c552cee72087bf1567debb470d4355b1335f2c262d218ebbff805cd3715448fe29b4b380bae6912561d0467233e4165830efd28da241418c63 - languageName: node - linkType: hard - -"graceful-fs@npm:^4.2.6, graceful-fs@npm:^4.2.9": - version: 4.2.11 - resolution: "graceful-fs@npm:4.2.11" - checksum: 10c0/386d011a553e02bc594ac2ca0bd6d9e4c22d7fa8cfbfc448a6d148c59ea881b092db9dbe3547ae4b88e55f1b01f7c4a2ecc53b310c042793e63aa44cf6c257f2 - languageName: node - linkType: hard - -"graphemer@npm:^1.4.0": - version: 1.4.0 - resolution: "graphemer@npm:1.4.0" - checksum: 10c0/e951259d8cd2e0d196c72ec711add7115d42eb9a8146c8eeda5b8d3ac91e5dd816b9cd68920726d9fd4490368e7ed86e9c423f40db87e2d8dfafa00fa17c3a31 - languageName: node - linkType: hard - -"has-flag@npm:^3.0.0": - version: 3.0.0 - resolution: "has-flag@npm:3.0.0" - checksum: 10c0/1c6c83b14b8b1b3c25b0727b8ba3e3b647f99e9e6e13eb7322107261de07a4c1be56fc0d45678fc376e09772a3a1642ccdaf8fc69bdf123b6c086598397ce473 - languageName: node - linkType: hard - -"has-flag@npm:^4.0.0": - version: 4.0.0 - resolution: "has-flag@npm:4.0.0" - checksum: 10c0/2e789c61b7888d66993e14e8331449e525ef42aac53c627cc53d1c3334e768bcb6abdc4f5f0de1478a25beec6f0bd62c7549058b7ac53e924040d4f301f02fd1 - languageName: node - linkType: hard - -"has-property-descriptors@npm:^1.0.2": - version: 1.0.2 - resolution: "has-property-descriptors@npm:1.0.2" - dependencies: - es-define-property: "npm:^1.0.0" - checksum: 10c0/253c1f59e80bb476cf0dde8ff5284505d90c3bdb762983c3514d36414290475fe3fd6f574929d84de2a8eec00d35cf07cb6776205ff32efd7c50719125f00236 - languageName: node - linkType: hard - -"has-proto@npm:^1.0.1": - version: 1.0.3 - resolution: "has-proto@npm:1.0.3" - checksum: 10c0/35a6989f81e9f8022c2f4027f8b48a552de714938765d019dbea6bb547bd49ce5010a3c7c32ec6ddac6e48fc546166a3583b128f5a7add8b058a6d8b4afec205 - languageName: node - linkType: hard - -"has-symbols@npm:^1.0.3": - version: 1.0.3 - resolution: "has-symbols@npm:1.0.3" - checksum: 10c0/e6922b4345a3f37069cdfe8600febbca791c94988c01af3394d86ca3360b4b93928bbf395859158f88099cb10b19d98e3bbab7c9ff2c1bd09cf665ee90afa2c3 - languageName: node - linkType: hard - -"has-unicode@npm:^2.0.1": - version: 2.0.1 - resolution: "has-unicode@npm:2.0.1" - checksum: 10c0/ebdb2f4895c26bb08a8a100b62d362e49b2190bcfd84b76bc4be1a3bd4d254ec52d0dd9f2fbcc093fc5eb878b20c52146f9dfd33e2686ed28982187be593b47c - languageName: node - linkType: hard - -"hasown@npm:^2.0.0": - version: 2.0.2 - resolution: "hasown@npm:2.0.2" - dependencies: - function-bind: "npm:^1.1.2" - checksum: 10c0/3769d434703b8ac66b209a4cca0737519925bbdb61dd887f93a16372b14694c63ff4e797686d87c90f08168e81082248b9b028bad60d4da9e0d1148766f56eb9 - languageName: node - linkType: hard - -"hexoid@npm:^1.0.0": - version: 1.0.0 - resolution: "hexoid@npm:1.0.0" - checksum: 10c0/9c45e8ba676b9eb88455631ebceec4c829a8374a583410dc735472ab9808bf11339fcd074633c3fa30e420901b894d8a92ffd5e2e21eddd41149546e05a91f69 - languageName: node - linkType: hard - -"html-escaper@npm:^2.0.0": - version: 2.0.2 - resolution: "html-escaper@npm:2.0.2" - checksum: 10c0/208e8a12de1a6569edbb14544f4567e6ce8ecc30b9394fcaa4e7bb1e60c12a7c9a1ed27e31290817157e8626f3a4f29e76c8747030822eb84a6abb15c255f0a0 - languageName: node - linkType: hard - -"http-cache-semantics@npm:^4.1.1": - version: 4.1.1 - resolution: "http-cache-semantics@npm:4.1.1" - checksum: 10c0/ce1319b8a382eb3cbb4a37c19f6bfe14e5bb5be3d09079e885e8c513ab2d3cd9214902f8a31c9dc4e37022633ceabfc2d697405deeaf1b8f3552bb4ed996fdfc - languageName: node - linkType: hard - -"http-errors@npm:2.0.0": - version: 2.0.0 - resolution: "http-errors@npm:2.0.0" - dependencies: - depd: "npm:2.0.0" - inherits: "npm:2.0.4" - setprototypeof: "npm:1.2.0" - statuses: "npm:2.0.1" - toidentifier: "npm:1.0.1" - checksum: 10c0/fc6f2715fe188d091274b5ffc8b3657bd85c63e969daa68ccb77afb05b071a4b62841acb7a21e417b5539014dff2ebf9550f0b14a9ff126f2734a7c1387f8e19 - languageName: node - linkType: hard - -"http-proxy-agent@npm:^7.0.0": - version: 7.0.2 - resolution: "http-proxy-agent@npm:7.0.2" - dependencies: - agent-base: "npm:^7.1.0" - debug: "npm:^4.3.4" - checksum: 10c0/4207b06a4580fb85dd6dff521f0abf6db517489e70863dca1a0291daa7f2d3d2d6015a57bd702af068ea5cf9f1f6ff72314f5f5b4228d299c0904135d2aef921 - languageName: node - linkType: hard - -"https-proxy-agent@npm:^5.0.0": - version: 5.0.1 - resolution: "https-proxy-agent@npm:5.0.1" - dependencies: - agent-base: "npm:6" - debug: "npm:4" - checksum: 10c0/6dd639f03434003577c62b27cafdb864784ef19b2de430d8ae2a1d45e31c4fd60719e5637b44db1a88a046934307da7089e03d6089ec3ddacc1189d8de8897d1 - languageName: node - linkType: hard - -"https-proxy-agent@npm:^7.0.1": - version: 7.0.4 - resolution: "https-proxy-agent@npm:7.0.4" - dependencies: - agent-base: "npm:^7.0.2" - debug: "npm:4" - checksum: 10c0/bc4f7c38da32a5fc622450b6cb49a24ff596f9bd48dcedb52d2da3fa1c1a80e100fb506bd59b326c012f21c863c69b275c23de1a01d0b84db396822fdf25e52b - languageName: node - linkType: hard - -"human-signals@npm:^2.1.0": - version: 2.1.0 - resolution: "human-signals@npm:2.1.0" - checksum: 10c0/695edb3edfcfe9c8b52a76926cd31b36978782062c0ed9b1192b36bebc75c4c87c82e178dfcb0ed0fc27ca59d434198aac0bd0be18f5781ded775604db22304a - languageName: node - linkType: hard - -"iconv-lite@npm:0.4.24": - version: 0.4.24 - resolution: "iconv-lite@npm:0.4.24" - dependencies: - safer-buffer: "npm:>= 2.1.2 < 3" - checksum: 10c0/c6886a24cc00f2a059767440ec1bc00d334a89f250db8e0f7feb4961c8727118457e27c495ba94d082e51d3baca378726cd110aaf7ded8b9bbfd6a44760cf1d4 - languageName: node - linkType: hard - -"iconv-lite@npm:^0.6.2": - version: 0.6.3 - resolution: "iconv-lite@npm:0.6.3" - dependencies: - safer-buffer: "npm:>= 2.1.2 < 3.0.0" - checksum: 10c0/98102bc66b33fcf5ac044099d1257ba0b7ad5e3ccd3221f34dd508ab4070edff183276221684e1e0555b145fce0850c9f7d2b60a9fcac50fbb4ea0d6e845a3b1 - languageName: node - linkType: hard - -"ieee754@npm:^1.1.13": - version: 1.2.1 - resolution: "ieee754@npm:1.2.1" - checksum: 10c0/b0782ef5e0935b9f12883a2e2aa37baa75da6e66ce6515c168697b42160807d9330de9a32ec1ed73149aea02e0d822e572bca6f1e22bdcbd2149e13b050b17bb - languageName: node - linkType: hard - -"ignore@npm:^5.2.0": - version: 5.3.1 - resolution: "ignore@npm:5.3.1" - checksum: 10c0/703f7f45ffb2a27fb2c5a8db0c32e7dee66b33a225d28e8db4e1be6474795f606686a6e3bcc50e1aa12f2042db4c9d4a7d60af3250511de74620fbed052ea4cd - languageName: node - linkType: hard - -"import-fresh@npm:^3.2.1": - version: 3.3.0 - resolution: "import-fresh@npm:3.3.0" - dependencies: - parent-module: "npm:^1.0.0" - resolve-from: "npm:^4.0.0" - checksum: 10c0/7f882953aa6b740d1f0e384d0547158bc86efbf2eea0f1483b8900a6f65c5a5123c2cf09b0d542cc419d0b98a759ecaeb394237e97ea427f2da221dc3cd80cc3 - languageName: node - linkType: hard - -"import-local@npm:^3.0.2": - version: 3.1.0 - resolution: "import-local@npm:3.1.0" - dependencies: - pkg-dir: "npm:^4.2.0" - resolve-cwd: "npm:^3.0.0" - bin: - import-local-fixture: fixtures/cli.js - checksum: 10c0/c67ecea72f775fe8684ca3d057e54bdb2ae28c14bf261d2607c269c18ea0da7b730924c06262eca9aed4b8ab31e31d65bc60b50e7296c85908a56e2f7d41ecd2 - languageName: node - linkType: hard - -"imurmurhash@npm:^0.1.4": - version: 0.1.4 - resolution: "imurmurhash@npm:0.1.4" - checksum: 10c0/8b51313850dd33605c6c9d3fd9638b714f4c4c40250cff658209f30d40da60f78992fb2df5dabee4acf589a6a82bbc79ad5486550754bd9ec4e3fc0d4a57d6a6 - languageName: node - linkType: hard - -"indent-string@npm:^4.0.0": - version: 4.0.0 - resolution: "indent-string@npm:4.0.0" - checksum: 10c0/1e1904ddb0cb3d6cce7cd09e27a90184908b7a5d5c21b92e232c93579d314f0b83c246ffb035493d0504b1e9147ba2c9b21df0030f48673fba0496ecd698161f - languageName: node - linkType: hard - -"inflight@npm:^1.0.4": - version: 1.0.6 - resolution: "inflight@npm:1.0.6" - dependencies: - once: "npm:^1.3.0" - wrappy: "npm:1" - checksum: 10c0/7faca22584600a9dc5b9fca2cd5feb7135ac8c935449837b315676b4c90aa4f391ec4f42240178244b5a34e8bede1948627fda392ca3191522fc46b34e985ab2 - languageName: node - linkType: hard - -"inherits@npm:2, inherits@npm:2.0.4, inherits@npm:^2.0.3, inherits@npm:^2.0.4": - version: 2.0.4 - resolution: "inherits@npm:2.0.4" - checksum: 10c0/4e531f648b29039fb7426fb94075e6545faa1eb9fe83c29f0b6d9e7263aceb4289d2d4557db0d428188eeb449cc7c5e77b0a0b2c4e248ff2a65933a0dee49ef2 - languageName: node - linkType: hard - -"ini@npm:~1.3.0": - version: 1.3.8 - resolution: "ini@npm:1.3.8" - checksum: 10c0/ec93838d2328b619532e4f1ff05df7909760b6f66d9c9e2ded11e5c1897d6f2f9980c54dd638f88654b00919ce31e827040631eab0a3969e4d1abefa0719516a - languageName: node - linkType: hard - -"ip-address@npm:^9.0.5": - version: 9.0.5 - resolution: "ip-address@npm:9.0.5" - dependencies: - jsbn: "npm:1.1.0" - sprintf-js: "npm:^1.1.3" - checksum: 10c0/331cd07fafcb3b24100613e4b53e1a2b4feab11e671e655d46dc09ee233da5011284d09ca40c4ecbdfe1d0004f462958675c224a804259f2f78d2465a87824bc - languageName: node - linkType: hard - -"ipaddr.js@npm:1.9.1": - version: 1.9.1 - resolution: "ipaddr.js@npm:1.9.1" - checksum: 10c0/0486e775047971d3fdb5fb4f063829bac45af299ae0b82dcf3afa2145338e08290563a2a70f34b732d795ecc8311902e541a8530eeb30d75860a78ff4e94ce2a - languageName: node - linkType: hard - -"is-arrayish@npm:^0.2.1": - version: 0.2.1 - resolution: "is-arrayish@npm:0.2.1" - checksum: 10c0/e7fb686a739068bb70f860b39b67afc62acc62e36bb61c5f965768abce1873b379c563e61dd2adad96ebb7edf6651111b385e490cf508378959b0ed4cac4e729 - languageName: node - linkType: hard - -"is-arrayish@npm:^0.3.1": - version: 0.3.2 - resolution: "is-arrayish@npm:0.3.2" - checksum: 10c0/f59b43dc1d129edb6f0e282595e56477f98c40278a2acdc8b0a5c57097c9eff8fe55470493df5775478cf32a4dc8eaf6d3a749f07ceee5bc263a78b2434f6a54 - languageName: node - linkType: hard - -"is-core-module@npm:^2.13.0": - version: 2.13.1 - resolution: "is-core-module@npm:2.13.1" - dependencies: - hasown: "npm:^2.0.0" - checksum: 10c0/2cba9903aaa52718f11c4896dabc189bab980870aae86a62dc0d5cedb546896770ee946fb14c84b7adf0735f5eaea4277243f1b95f5cefa90054f92fbcac2518 - languageName: node - linkType: hard - -"is-extglob@npm:^2.1.1": - version: 2.1.1 - resolution: "is-extglob@npm:2.1.1" - checksum: 10c0/5487da35691fbc339700bbb2730430b07777a3c21b9ebaecb3072512dfd7b4ba78ac2381a87e8d78d20ea08affb3f1971b4af629173a6bf435ff8a4c47747912 - languageName: node - linkType: hard - -"is-fullwidth-code-point@npm:^3.0.0": - version: 3.0.0 - resolution: "is-fullwidth-code-point@npm:3.0.0" - checksum: 10c0/bb11d825e049f38e04c06373a8d72782eee0205bda9d908cc550ccb3c59b99d750ff9537982e01733c1c94a58e35400661f57042158ff5e8f3e90cf936daf0fc - languageName: node - linkType: hard - -"is-generator-fn@npm:^2.0.0": - version: 2.1.0 - resolution: "is-generator-fn@npm:2.1.0" - checksum: 10c0/2957cab387997a466cd0bf5c1b6047bd21ecb32bdcfd8996b15747aa01002c1c88731802f1b3d34ac99f4f6874b626418bd118658cf39380fe5fff32a3af9c4d - languageName: node - linkType: hard - -"is-glob@npm:^4.0.0, is-glob@npm:^4.0.1, is-glob@npm:^4.0.3": - version: 4.0.3 - resolution: "is-glob@npm:4.0.3" - dependencies: - is-extglob: "npm:^2.1.1" - checksum: 10c0/17fb4014e22be3bbecea9b2e3a76e9e34ff645466be702f1693e8f1ee1adac84710d0be0bd9f967d6354036fd51ab7c2741d954d6e91dae6bb69714de92c197a - languageName: node - linkType: hard - -"is-lambda@npm:^1.0.1": - version: 1.0.1 - resolution: "is-lambda@npm:1.0.1" - checksum: 10c0/85fee098ae62ba6f1e24cf22678805473c7afd0fb3978a3aa260e354cb7bcb3a5806cf0a98403188465efedec41ab4348e8e4e79305d409601323855b3839d4d - languageName: node - linkType: hard - -"is-number@npm:^7.0.0": - version: 7.0.0 - resolution: "is-number@npm:7.0.0" - checksum: 10c0/b4686d0d3053146095ccd45346461bc8e53b80aeb7671cc52a4de02dbbf7dc0d1d2a986e2fe4ae206984b4d34ef37e8b795ebc4f4295c978373e6575e295d811 - languageName: node - linkType: hard - -"is-path-inside@npm:^3.0.3": - version: 3.0.3 - resolution: "is-path-inside@npm:3.0.3" - checksum: 10c0/cf7d4ac35fb96bab6a1d2c3598fe5ebb29aafb52c0aaa482b5a3ed9d8ba3edc11631e3ec2637660c44b3ce0e61a08d54946e8af30dec0b60a7c27296c68ffd05 - languageName: node - linkType: hard - -"is-stream@npm:^2.0.0": - version: 2.0.1 - resolution: "is-stream@npm:2.0.1" - checksum: 10c0/7c284241313fc6efc329b8d7f08e16c0efeb6baab1b4cd0ba579eb78e5af1aa5da11e68559896a2067cd6c526bd29241dda4eb1225e627d5aa1a89a76d4635a5 - languageName: node - linkType: hard - -"isexe@npm:^2.0.0": - version: 2.0.0 - resolution: "isexe@npm:2.0.0" - checksum: 10c0/228cfa503fadc2c31596ab06ed6aa82c9976eec2bfd83397e7eaf06d0ccf42cd1dfd6743bf9aeb01aebd4156d009994c5f76ea898d2832c1fe342da923ca457d - languageName: node - linkType: hard - -"isexe@npm:^3.1.1": - version: 3.1.1 - resolution: "isexe@npm:3.1.1" - checksum: 10c0/9ec257654093443eb0a528a9c8cbba9c0ca7616ccb40abd6dde7202734d96bb86e4ac0d764f0f8cd965856aacbff2f4ce23e730dc19dfb41e3b0d865ca6fdcc7 - languageName: node - linkType: hard - -"istanbul-lib-coverage@npm:^3.0.0, istanbul-lib-coverage@npm:^3.2.0": - version: 3.2.2 - resolution: "istanbul-lib-coverage@npm:3.2.2" - checksum: 10c0/6c7ff2106769e5f592ded1fb418f9f73b4411fd5a084387a5410538332b6567cd1763ff6b6cadca9b9eb2c443cce2f7ea7d7f1b8d315f9ce58539793b1e0922b - languageName: node - linkType: hard - -"istanbul-lib-instrument@npm:^5.0.4": - version: 5.2.1 - resolution: "istanbul-lib-instrument@npm:5.2.1" - dependencies: - "@babel/core": "npm:^7.12.3" - "@babel/parser": "npm:^7.14.7" - "@istanbuljs/schema": "npm:^0.1.2" - istanbul-lib-coverage: "npm:^3.2.0" - semver: "npm:^6.3.0" - checksum: 10c0/8a1bdf3e377dcc0d33ec32fe2b6ecacdb1e4358fd0eb923d4326bb11c67622c0ceb99600a680f3dad5d29c66fc1991306081e339b4d43d0b8a2ab2e1d910a6ee - languageName: node - linkType: hard - -"istanbul-lib-instrument@npm:^6.0.0": - version: 6.0.2 - resolution: "istanbul-lib-instrument@npm:6.0.2" - dependencies: - "@babel/core": "npm:^7.23.9" - "@babel/parser": "npm:^7.23.9" - "@istanbuljs/schema": "npm:^0.1.3" - istanbul-lib-coverage: "npm:^3.2.0" - semver: "npm:^7.5.4" - checksum: 10c0/405c6ac037bf8c7ee7495980b0cd5544b2c53078c10534d0c9ceeb92a9ea7dcf8510f58ccfce31336458a8fa6ccef27b570bbb602abaa8c1650f5496a807477c - languageName: node - linkType: hard - -"istanbul-lib-report@npm:^3.0.0": - version: 3.0.1 - resolution: "istanbul-lib-report@npm:3.0.1" - dependencies: - istanbul-lib-coverage: "npm:^3.0.0" - make-dir: "npm:^4.0.0" - supports-color: "npm:^7.1.0" - checksum: 10c0/84323afb14392de8b6a5714bd7e9af845cfbd56cfe71ed276cda2f5f1201aea673c7111901227ee33e68e4364e288d73861eb2ed48f6679d1e69a43b6d9b3ba7 - languageName: node - linkType: hard - -"istanbul-lib-source-maps@npm:^4.0.0": - version: 4.0.1 - resolution: "istanbul-lib-source-maps@npm:4.0.1" - dependencies: - debug: "npm:^4.1.1" - istanbul-lib-coverage: "npm:^3.0.0" - source-map: "npm:^0.6.1" - checksum: 10c0/19e4cc405016f2c906dff271a76715b3e881fa9faeb3f09a86cb99b8512b3a5ed19cadfe0b54c17ca0e54c1142c9c6de9330d65506e35873994e06634eebeb66 - languageName: node - linkType: hard - -"istanbul-reports@npm:^3.1.3": - version: 3.1.7 - resolution: "istanbul-reports@npm:3.1.7" - dependencies: - html-escaper: "npm:^2.0.0" - istanbul-lib-report: "npm:^3.0.0" - checksum: 10c0/a379fadf9cf8dc5dfe25568115721d4a7eb82fbd50b005a6672aff9c6989b20cc9312d7865814e0859cd8df58cbf664482e1d3604be0afde1f7fc3ccc1394a51 - languageName: node - linkType: hard - -"jackspeak@npm:^3.1.2": - version: 3.4.0 - resolution: "jackspeak@npm:3.4.0" - dependencies: - "@isaacs/cliui": "npm:^8.0.2" - "@pkgjs/parseargs": "npm:^0.11.0" - dependenciesMeta: - "@pkgjs/parseargs": - optional: true - checksum: 10c0/7e42d1ea411b4d57d43ea8a6afbca9224382804359cb72626d0fc45bb8db1de5ad0248283c3db45fe73e77210750d4fcc7c2b4fe5d24fda94aaa24d658295c5f - languageName: node - linkType: hard - -"jest-changed-files@npm:^29.7.0": - version: 29.7.0 - resolution: "jest-changed-files@npm:29.7.0" - dependencies: - execa: "npm:^5.0.0" - jest-util: "npm:^29.7.0" - p-limit: "npm:^3.1.0" - checksum: 10c0/e071384d9e2f6bb462231ac53f29bff86f0e12394c1b49ccafbad225ce2ab7da226279a8a94f421949920bef9be7ef574fd86aee22e8adfa149be73554ab828b - languageName: node - linkType: hard - -"jest-circus@npm:^29.7.0": - version: 29.7.0 - resolution: "jest-circus@npm:29.7.0" - dependencies: - "@jest/environment": "npm:^29.7.0" - "@jest/expect": "npm:^29.7.0" - "@jest/test-result": "npm:^29.7.0" - "@jest/types": "npm:^29.6.3" - "@types/node": "npm:*" - chalk: "npm:^4.0.0" - co: "npm:^4.6.0" - dedent: "npm:^1.0.0" - is-generator-fn: "npm:^2.0.0" - jest-each: "npm:^29.7.0" - jest-matcher-utils: "npm:^29.7.0" - jest-message-util: "npm:^29.7.0" - jest-runtime: "npm:^29.7.0" - jest-snapshot: "npm:^29.7.0" - jest-util: "npm:^29.7.0" - p-limit: "npm:^3.1.0" - pretty-format: "npm:^29.7.0" - pure-rand: "npm:^6.0.0" - slash: "npm:^3.0.0" - stack-utils: "npm:^2.0.3" - checksum: 10c0/8d15344cf7a9f14e926f0deed64ed190c7a4fa1ed1acfcd81e4cc094d3cc5bf7902ebb7b874edc98ada4185688f90c91e1747e0dfd7ac12463b097968ae74b5e - languageName: node - linkType: hard - -"jest-cli@npm:^29.7.0": - version: 29.7.0 - resolution: "jest-cli@npm:29.7.0" - dependencies: - "@jest/core": "npm:^29.7.0" - "@jest/test-result": "npm:^29.7.0" - "@jest/types": "npm:^29.6.3" - chalk: "npm:^4.0.0" - create-jest: "npm:^29.7.0" - exit: "npm:^0.1.2" - import-local: "npm:^3.0.2" - jest-config: "npm:^29.7.0" - jest-util: "npm:^29.7.0" - jest-validate: "npm:^29.7.0" - yargs: "npm:^17.3.1" - peerDependencies: - node-notifier: ^8.0.1 || ^9.0.0 || ^10.0.0 - peerDependenciesMeta: - node-notifier: - optional: true - bin: - jest: bin/jest.js - checksum: 10c0/a658fd55050d4075d65c1066364595962ead7661711495cfa1dfeecf3d6d0a8ffec532f3dbd8afbb3e172dd5fd2fb2e813c5e10256e7cf2fea766314942fb43a - languageName: node - linkType: hard - -"jest-config@npm:^29.7.0": - version: 29.7.0 - resolution: "jest-config@npm:29.7.0" - dependencies: - "@babel/core": "npm:^7.11.6" - "@jest/test-sequencer": "npm:^29.7.0" - "@jest/types": "npm:^29.6.3" - babel-jest: "npm:^29.7.0" - chalk: "npm:^4.0.0" - ci-info: "npm:^3.2.0" - deepmerge: "npm:^4.2.2" - glob: "npm:^7.1.3" - graceful-fs: "npm:^4.2.9" - jest-circus: "npm:^29.7.0" - jest-environment-node: "npm:^29.7.0" - jest-get-type: "npm:^29.6.3" - jest-regex-util: "npm:^29.6.3" - jest-resolve: "npm:^29.7.0" - jest-runner: "npm:^29.7.0" - jest-util: "npm:^29.7.0" - jest-validate: "npm:^29.7.0" - micromatch: "npm:^4.0.4" - parse-json: "npm:^5.2.0" - pretty-format: "npm:^29.7.0" - slash: "npm:^3.0.0" - strip-json-comments: "npm:^3.1.1" - peerDependencies: - "@types/node": "*" - ts-node: ">=9.0.0" - peerDependenciesMeta: - "@types/node": - optional: true - ts-node: - optional: true - checksum: 10c0/bab23c2eda1fff06e0d104b00d6adfb1d1aabb7128441899c9bff2247bd26710b050a5364281ce8d52b46b499153bf7e3ee88b19831a8f3451f1477a0246a0f1 - languageName: node - linkType: hard - -"jest-diff@npm:^29.7.0": - version: 29.7.0 - resolution: "jest-diff@npm:29.7.0" - dependencies: - chalk: "npm:^4.0.0" - diff-sequences: "npm:^29.6.3" - jest-get-type: "npm:^29.6.3" - pretty-format: "npm:^29.7.0" - checksum: 10c0/89a4a7f182590f56f526443dde69acefb1f2f0c9e59253c61d319569856c4931eae66b8a3790c443f529267a0ddba5ba80431c585deed81827032b2b2a1fc999 - languageName: node - linkType: hard - -"jest-docblock@npm:^29.7.0": - version: 29.7.0 - resolution: "jest-docblock@npm:29.7.0" - dependencies: - detect-newline: "npm:^3.0.0" - checksum: 10c0/d932a8272345cf6b6142bb70a2bb63e0856cc0093f082821577ea5bdf4643916a98744dfc992189d2b1417c38a11fa42466f6111526bc1fb81366f56410f3be9 - languageName: node - linkType: hard - -"jest-each@npm:^29.7.0": - version: 29.7.0 - resolution: "jest-each@npm:29.7.0" - dependencies: - "@jest/types": "npm:^29.6.3" - chalk: "npm:^4.0.0" - jest-get-type: "npm:^29.6.3" - jest-util: "npm:^29.7.0" - pretty-format: "npm:^29.7.0" - checksum: 10c0/f7f9a90ebee80cc688e825feceb2613627826ac41ea76a366fa58e669c3b2403d364c7c0a74d862d469b103c843154f8456d3b1c02b487509a12afa8b59edbb4 - languageName: node - linkType: hard - -"jest-environment-node@npm:^29.7.0": - version: 29.7.0 - resolution: "jest-environment-node@npm:29.7.0" - dependencies: - "@jest/environment": "npm:^29.7.0" - "@jest/fake-timers": "npm:^29.7.0" - "@jest/types": "npm:^29.6.3" - "@types/node": "npm:*" - jest-mock: "npm:^29.7.0" - jest-util: "npm:^29.7.0" - checksum: 10c0/61f04fec077f8b1b5c1a633e3612fc0c9aa79a0ab7b05600683428f1e01a4d35346c474bde6f439f9fcc1a4aa9a2861ff852d079a43ab64b02105d1004b2592b - languageName: node - linkType: hard - -"jest-get-type@npm:^29.6.3": - version: 29.6.3 - resolution: "jest-get-type@npm:29.6.3" - checksum: 10c0/552e7a97a983d3c2d4e412a44eb7de0430ff773dd99f7500962c268d6dfbfa431d7d08f919c9d960530e5f7f78eb47f267ad9b318265e5092b3ff9ede0db7c2b - languageName: node - linkType: hard - -"jest-haste-map@npm:^29.7.0": - version: 29.7.0 - resolution: "jest-haste-map@npm:29.7.0" - dependencies: - "@jest/types": "npm:^29.6.3" - "@types/graceful-fs": "npm:^4.1.3" - "@types/node": "npm:*" - anymatch: "npm:^3.0.3" - fb-watchman: "npm:^2.0.0" - fsevents: "npm:^2.3.2" - graceful-fs: "npm:^4.2.9" - jest-regex-util: "npm:^29.6.3" - jest-util: "npm:^29.7.0" - jest-worker: "npm:^29.7.0" - micromatch: "npm:^4.0.4" - walker: "npm:^1.0.8" - dependenciesMeta: - fsevents: - optional: true - checksum: 10c0/2683a8f29793c75a4728787662972fedd9267704c8f7ef9d84f2beed9a977f1cf5e998c07b6f36ba5603f53cb010c911fe8cd0ac9886e073fe28ca66beefd30c - languageName: node - linkType: hard - -"jest-leak-detector@npm:^29.7.0": - version: 29.7.0 - resolution: "jest-leak-detector@npm:29.7.0" - dependencies: - jest-get-type: "npm:^29.6.3" - pretty-format: "npm:^29.7.0" - checksum: 10c0/71bb9f77fc489acb842a5c7be030f2b9acb18574dc9fb98b3100fc57d422b1abc55f08040884bd6e6dbf455047a62f7eaff12aa4058f7cbdc11558718ca6a395 - languageName: node - linkType: hard - -"jest-matcher-utils@npm:^29.7.0": - version: 29.7.0 - resolution: "jest-matcher-utils@npm:29.7.0" - dependencies: - chalk: "npm:^4.0.0" - jest-diff: "npm:^29.7.0" - jest-get-type: "npm:^29.6.3" - pretty-format: "npm:^29.7.0" - checksum: 10c0/0d0e70b28fa5c7d4dce701dc1f46ae0922102aadc24ed45d594dd9b7ae0a8a6ef8b216718d1ab79e451291217e05d4d49a82666e1a3cc2b428b75cd9c933244e - languageName: node - linkType: hard - -"jest-message-util@npm:^29.7.0": - version: 29.7.0 - resolution: "jest-message-util@npm:29.7.0" - dependencies: - "@babel/code-frame": "npm:^7.12.13" - "@jest/types": "npm:^29.6.3" - "@types/stack-utils": "npm:^2.0.0" - chalk: "npm:^4.0.0" - graceful-fs: "npm:^4.2.9" - micromatch: "npm:^4.0.4" - pretty-format: "npm:^29.7.0" - slash: "npm:^3.0.0" - stack-utils: "npm:^2.0.3" - checksum: 10c0/850ae35477f59f3e6f27efac5215f706296e2104af39232bb14e5403e067992afb5c015e87a9243ec4d9df38525ef1ca663af9f2f4766aa116f127247008bd22 - languageName: node - linkType: hard - -"jest-mock@npm:^29.7.0": - version: 29.7.0 - resolution: "jest-mock@npm:29.7.0" - dependencies: - "@jest/types": "npm:^29.6.3" - "@types/node": "npm:*" - jest-util: "npm:^29.7.0" - checksum: 10c0/7b9f8349ee87695a309fe15c46a74ab04c853369e5c40952d68061d9dc3159a0f0ed73e215f81b07ee97a9faaf10aebe5877a9d6255068a0977eae6a9ff1d5ac - languageName: node - linkType: hard - -"jest-pnp-resolver@npm:^1.2.2": - version: 1.2.3 - resolution: "jest-pnp-resolver@npm:1.2.3" - peerDependencies: - jest-resolve: "*" - peerDependenciesMeta: - jest-resolve: - optional: true - checksum: 10c0/86eec0c78449a2de733a6d3e316d49461af6a858070e113c97f75fb742a48c2396ea94150cbca44159ffd4a959f743a47a8b37a792ef6fdad2cf0a5cba973fac - languageName: node - linkType: hard - -"jest-regex-util@npm:^29.6.3": - version: 29.6.3 - resolution: "jest-regex-util@npm:29.6.3" - checksum: 10c0/4e33fb16c4f42111159cafe26397118dcfc4cf08bc178a67149fb05f45546a91928b820894572679d62559839d0992e21080a1527faad65daaae8743a5705a3b - languageName: node - linkType: hard - -"jest-resolve-dependencies@npm:^29.7.0": - version: 29.7.0 - resolution: "jest-resolve-dependencies@npm:29.7.0" - dependencies: - jest-regex-util: "npm:^29.6.3" - jest-snapshot: "npm:^29.7.0" - checksum: 10c0/b6e9ad8ae5b6049474118ea6441dfddd385b6d1fc471db0136f7c8fbcfe97137a9665e4f837a9f49f15a29a1deb95a14439b7aec812f3f99d08f228464930f0d - languageName: node - linkType: hard - -"jest-resolve@npm:^29.7.0": - version: 29.7.0 - resolution: "jest-resolve@npm:29.7.0" - dependencies: - chalk: "npm:^4.0.0" - graceful-fs: "npm:^4.2.9" - jest-haste-map: "npm:^29.7.0" - jest-pnp-resolver: "npm:^1.2.2" - jest-util: "npm:^29.7.0" - jest-validate: "npm:^29.7.0" - resolve: "npm:^1.20.0" - resolve.exports: "npm:^2.0.0" - slash: "npm:^3.0.0" - checksum: 10c0/59da5c9c5b50563e959a45e09e2eace783d7f9ac0b5dcc6375dea4c0db938d2ebda97124c8161310082760e8ebbeff9f6b177c15ca2f57fb424f637a5d2adb47 - languageName: node - linkType: hard - -"jest-runner@npm:^29.7.0": - version: 29.7.0 - resolution: "jest-runner@npm:29.7.0" - dependencies: - "@jest/console": "npm:^29.7.0" - "@jest/environment": "npm:^29.7.0" - "@jest/test-result": "npm:^29.7.0" - "@jest/transform": "npm:^29.7.0" - "@jest/types": "npm:^29.6.3" - "@types/node": "npm:*" - chalk: "npm:^4.0.0" - emittery: "npm:^0.13.1" - graceful-fs: "npm:^4.2.9" - jest-docblock: "npm:^29.7.0" - jest-environment-node: "npm:^29.7.0" - jest-haste-map: "npm:^29.7.0" - jest-leak-detector: "npm:^29.7.0" - jest-message-util: "npm:^29.7.0" - jest-resolve: "npm:^29.7.0" - jest-runtime: "npm:^29.7.0" - jest-util: "npm:^29.7.0" - jest-watcher: "npm:^29.7.0" - jest-worker: "npm:^29.7.0" - p-limit: "npm:^3.1.0" - source-map-support: "npm:0.5.13" - checksum: 10c0/2194b4531068d939f14c8d3274fe5938b77fa73126aedf9c09ec9dec57d13f22c72a3b5af01ac04f5c1cf2e28d0ac0b4a54212a61b05f10b5d6b47f2a1097bb4 - languageName: node - linkType: hard - -"jest-runtime@npm:^29.7.0": - version: 29.7.0 - resolution: "jest-runtime@npm:29.7.0" - dependencies: - "@jest/environment": "npm:^29.7.0" - "@jest/fake-timers": "npm:^29.7.0" - "@jest/globals": "npm:^29.7.0" - "@jest/source-map": "npm:^29.6.3" - "@jest/test-result": "npm:^29.7.0" - "@jest/transform": "npm:^29.7.0" - "@jest/types": "npm:^29.6.3" - "@types/node": "npm:*" - chalk: "npm:^4.0.0" - cjs-module-lexer: "npm:^1.0.0" - collect-v8-coverage: "npm:^1.0.0" - glob: "npm:^7.1.3" - graceful-fs: "npm:^4.2.9" - jest-haste-map: "npm:^29.7.0" - jest-message-util: "npm:^29.7.0" - jest-mock: "npm:^29.7.0" - jest-regex-util: "npm:^29.6.3" - jest-resolve: "npm:^29.7.0" - jest-snapshot: "npm:^29.7.0" - jest-util: "npm:^29.7.0" - slash: "npm:^3.0.0" - strip-bom: "npm:^4.0.0" - checksum: 10c0/7cd89a1deda0bda7d0941835434e44f9d6b7bd50b5c5d9b0fc9a6c990b2d4d2cab59685ab3cb2850ed4cc37059f6de903af5a50565d7f7f1192a77d3fd6dd2a6 - languageName: node - linkType: hard - -"jest-snapshot@npm:^29.7.0": - version: 29.7.0 - resolution: "jest-snapshot@npm:29.7.0" - dependencies: - "@babel/core": "npm:^7.11.6" - "@babel/generator": "npm:^7.7.2" - "@babel/plugin-syntax-jsx": "npm:^7.7.2" - "@babel/plugin-syntax-typescript": "npm:^7.7.2" - "@babel/types": "npm:^7.3.3" - "@jest/expect-utils": "npm:^29.7.0" - "@jest/transform": "npm:^29.7.0" - "@jest/types": "npm:^29.6.3" - babel-preset-current-node-syntax: "npm:^1.0.0" - chalk: "npm:^4.0.0" - expect: "npm:^29.7.0" - graceful-fs: "npm:^4.2.9" - jest-diff: "npm:^29.7.0" - jest-get-type: "npm:^29.6.3" - jest-matcher-utils: "npm:^29.7.0" - jest-message-util: "npm:^29.7.0" - jest-util: "npm:^29.7.0" - natural-compare: "npm:^1.4.0" - pretty-format: "npm:^29.7.0" - semver: "npm:^7.5.3" - checksum: 10c0/6e9003c94ec58172b4a62864a91c0146513207bedf4e0a06e1e2ac70a4484088a2683e3a0538d8ea913bcfd53dc54a9b98a98cdfa562e7fe1d1339aeae1da570 - languageName: node - linkType: hard - -"jest-util@npm:^29.7.0": - version: 29.7.0 - resolution: "jest-util@npm:29.7.0" - dependencies: - "@jest/types": "npm:^29.6.3" - "@types/node": "npm:*" - chalk: "npm:^4.0.0" - ci-info: "npm:^3.2.0" - graceful-fs: "npm:^4.2.9" - picomatch: "npm:^2.2.3" - checksum: 10c0/bc55a8f49fdbb8f51baf31d2a4f312fb66c9db1483b82f602c9c990e659cdd7ec529c8e916d5a89452ecbcfae4949b21b40a7a59d4ffc0cd813a973ab08c8150 - languageName: node - linkType: hard - -"jest-validate@npm:^29.7.0": - version: 29.7.0 - resolution: "jest-validate@npm:29.7.0" - dependencies: - "@jest/types": "npm:^29.6.3" - camelcase: "npm:^6.2.0" - chalk: "npm:^4.0.0" - jest-get-type: "npm:^29.6.3" - leven: "npm:^3.1.0" - pretty-format: "npm:^29.7.0" - checksum: 10c0/a20b930480c1ed68778c739f4739dce39423131bc070cd2505ddede762a5570a256212e9c2401b7ae9ba4d7b7c0803f03c5b8f1561c62348213aba18d9dbece2 - languageName: node - linkType: hard - -"jest-watcher@npm:^29.7.0": - version: 29.7.0 - resolution: "jest-watcher@npm:29.7.0" - dependencies: - "@jest/test-result": "npm:^29.7.0" - "@jest/types": "npm:^29.6.3" - "@types/node": "npm:*" - ansi-escapes: "npm:^4.2.1" - chalk: "npm:^4.0.0" - emittery: "npm:^0.13.1" - jest-util: "npm:^29.7.0" - string-length: "npm:^4.0.1" - checksum: 10c0/ec6c75030562fc8f8c727cb8f3b94e75d831fc718785abfc196e1f2a2ebc9a2e38744a15147170039628a853d77a3b695561ce850375ede3a4ee6037a2574567 - languageName: node - linkType: hard - -"jest-worker@npm:^29.7.0": - version: 29.7.0 - resolution: "jest-worker@npm:29.7.0" - dependencies: - "@types/node": "npm:*" - jest-util: "npm:^29.7.0" - merge-stream: "npm:^2.0.0" - supports-color: "npm:^8.0.0" - checksum: 10c0/5570a3a005b16f46c131968b8a5b56d291f9bbb85ff4217e31c80bd8a02e7de799e59a54b95ca28d5c302f248b54cbffde2d177c2f0f52ffcee7504c6eabf660 - languageName: node - linkType: hard - -"jest@npm:^29.3.1": - version: 29.7.0 - resolution: "jest@npm:29.7.0" - dependencies: - "@jest/core": "npm:^29.7.0" - "@jest/types": "npm:^29.6.3" - import-local: "npm:^3.0.2" - jest-cli: "npm:^29.7.0" - peerDependencies: - node-notifier: ^8.0.1 || ^9.0.0 || ^10.0.0 - peerDependenciesMeta: - node-notifier: - optional: true - bin: - jest: bin/jest.js - checksum: 10c0/f40eb8171cf147c617cc6ada49d062fbb03b4da666cb8d39cdbfb739a7d75eea4c3ca150fb072d0d273dce0c753db4d0467d54906ad0293f59c54f9db4a09d8b - languageName: node - linkType: hard - -"jose@npm:^4.15.5": - version: 4.15.9 - resolution: "jose@npm:4.15.9" - checksum: 10c0/4ed4ddf4a029db04bd167f2215f65d7245e4dc5f36d7ac3c0126aab38d66309a9e692f52df88975d99429e357e5fd8bab340ff20baab544d17684dd1d940a0f4 - languageName: node - linkType: hard - -"js-tokens@npm:^4.0.0": - version: 4.0.0 - resolution: "js-tokens@npm:4.0.0" - checksum: 10c0/e248708d377aa058eacf2037b07ded847790e6de892bbad3dac0abba2e759cb9f121b00099a65195616badcb6eca8d14d975cb3e89eb1cfda644756402c8aeed - languageName: node - linkType: hard - -"js-yaml@npm:^3.13.1": - version: 3.14.1 - resolution: "js-yaml@npm:3.14.1" - dependencies: - argparse: "npm:^1.0.7" - esprima: "npm:^4.0.0" - bin: - js-yaml: bin/js-yaml.js - checksum: 10c0/6746baaaeac312c4db8e75fa22331d9a04cccb7792d126ed8ce6a0bbcfef0cedaddd0c5098fade53db067c09fe00aa1c957674b4765610a8b06a5a189e46433b - languageName: node - linkType: hard - -"js-yaml@npm:^4.1.0": - version: 4.1.0 - resolution: "js-yaml@npm:4.1.0" - dependencies: - argparse: "npm:^2.0.1" - bin: - js-yaml: bin/js-yaml.js - checksum: 10c0/184a24b4eaacfce40ad9074c64fd42ac83cf74d8c8cd137718d456ced75051229e5061b8633c3366b8aada17945a7a356b337828c19da92b51ae62126575018f - languageName: node - linkType: hard - -"jsbn@npm:1.1.0": - version: 1.1.0 - resolution: "jsbn@npm:1.1.0" - checksum: 10c0/4f907fb78d7b712e11dea8c165fe0921f81a657d3443dde75359ed52eb2b5d33ce6773d97985a089f09a65edd80b11cb75c767b57ba47391fee4c969f7215c96 - languageName: node - linkType: hard - -"jsesc@npm:^2.5.1": - version: 2.5.2 - resolution: "jsesc@npm:2.5.2" - bin: - jsesc: bin/jsesc - checksum: 10c0/dbf59312e0ebf2b4405ef413ec2b25abb5f8f4d9bc5fb8d9f90381622ebca5f2af6a6aa9a8578f65903f9e33990a6dc798edd0ce5586894bf0e9e31803a1de88 - languageName: node - linkType: hard - -"json-buffer@npm:3.0.1": - version: 3.0.1 - resolution: "json-buffer@npm:3.0.1" - checksum: 10c0/0d1c91569d9588e7eef2b49b59851f297f3ab93c7b35c7c221e288099322be6b562767d11e4821da500f3219542b9afd2e54c5dc573107c1126ed1080f8e96d7 - languageName: node - linkType: hard - -"json-parse-even-better-errors@npm:^2.3.0": - version: 2.3.1 - resolution: "json-parse-even-better-errors@npm:2.3.1" - checksum: 10c0/140932564c8f0b88455432e0f33c4cb4086b8868e37524e07e723f4eaedb9425bdc2bafd71bd1d9765bd15fd1e2d126972bc83990f55c467168c228c24d665f3 - languageName: node - linkType: hard - -"json-schema-traverse@npm:^0.4.1": - version: 0.4.1 - resolution: "json-schema-traverse@npm:0.4.1" - checksum: 10c0/108fa90d4cc6f08243aedc6da16c408daf81793bf903e9fd5ab21983cda433d5d2da49e40711da016289465ec2e62e0324dcdfbc06275a607fe3233fde4942ce - languageName: node - linkType: hard - -"json-stable-stringify-without-jsonify@npm:^1.0.1": - version: 1.0.1 - resolution: "json-stable-stringify-without-jsonify@npm:1.0.1" - checksum: 10c0/cb168b61fd4de83e58d09aaa6425ef71001bae30d260e2c57e7d09a5fd82223e2f22a042dedaab8db23b7d9ae46854b08bb1f91675a8be11c5cffebef5fb66a5 - languageName: node - linkType: hard - -"json5@npm:^2.2.3": - version: 2.2.3 - resolution: "json5@npm:2.2.3" - bin: - json5: lib/cli.js - checksum: 10c0/5a04eed94810fa55c5ea138b2f7a5c12b97c3750bc63d11e511dcecbfef758003861522a070c2272764ee0f4e3e323862f386945aeb5b85b87ee43f084ba586c - languageName: node - linkType: hard - -"jwa@npm:^2.0.0": - version: 2.0.0 - resolution: "jwa@npm:2.0.0" - dependencies: - buffer-equal-constant-time: "npm:1.0.1" - ecdsa-sig-formatter: "npm:1.0.11" - safe-buffer: "npm:^5.0.1" - checksum: 10c0/6baab823b93c038ba1d2a9e531984dcadbc04e9eb98d171f4901b7a40d2be15961a359335de1671d78cb6d987f07cbe5d350d8143255977a889160c4d90fcc3c - languageName: node - linkType: hard - -"jws@npm:^4.0.0": - version: 4.0.0 - resolution: "jws@npm:4.0.0" - dependencies: - jwa: "npm:^2.0.0" - safe-buffer: "npm:^5.0.1" - checksum: 10c0/f1ca77ea5451e8dc5ee219cb7053b8a4f1254a79cb22417a2e1043c1eb8a569ae118c68f24d72a589e8a3dd1824697f47d6bd4fb4bebb93a3bdf53545e721661 - languageName: node - linkType: hard - -"keyv@npm:^4.5.3": - version: 4.5.4 - resolution: "keyv@npm:4.5.4" - dependencies: - json-buffer: "npm:3.0.1" - checksum: 10c0/aa52f3c5e18e16bb6324876bb8b59dd02acf782a4b789c7b2ae21107fab95fab3890ed448d4f8dba80ce05391eeac4bfabb4f02a20221342982f806fa2cf271e - languageName: node - linkType: hard - -"kleur@npm:^3.0.3": - version: 3.0.3 - resolution: "kleur@npm:3.0.3" - checksum: 10c0/cd3a0b8878e7d6d3799e54340efe3591ca787d9f95f109f28129bdd2915e37807bf8918bb295ab86afb8c82196beec5a1adcaf29042ce3f2bd932b038fe3aa4b - languageName: node - linkType: hard - -"kuler@npm:^2.0.0": - version: 2.0.0 - resolution: "kuler@npm:2.0.0" - checksum: 10c0/0a4e99d92ca373f8f74d1dc37931909c4d0d82aebc94cf2ba265771160fc12c8df34eaaac80805efbda367e2795cb1f1dd4c3d404b6b1cf38aec94035b503d2d - languageName: node - linkType: hard - -"leven@npm:^3.1.0": - version: 3.1.0 - resolution: "leven@npm:3.1.0" - checksum: 10c0/cd778ba3fbab0f4d0500b7e87d1f6e1f041507c56fdcd47e8256a3012c98aaee371d4c15e0a76e0386107af2d42e2b7466160a2d80688aaa03e66e49949f42df - languageName: node - linkType: hard - -"levn@npm:^0.4.1": - version: 0.4.1 - resolution: "levn@npm:0.4.1" - dependencies: - prelude-ls: "npm:^1.2.1" - type-check: "npm:~0.4.0" - checksum: 10c0/effb03cad7c89dfa5bd4f6989364bfc79994c2042ec5966cb9b95990e2edee5cd8969ddf42616a0373ac49fac1403437deaf6e9050fbbaa3546093a59b9ac94e - languageName: node - linkType: hard - -"lines-and-columns@npm:^1.1.6": - version: 1.2.4 - resolution: "lines-and-columns@npm:1.2.4" - checksum: 10c0/3da6ee62d4cd9f03f5dc90b4df2540fb85b352081bee77fe4bbcd12c9000ead7f35e0a38b8d09a9bb99b13223446dd8689ff3c4959807620726d788701a83d2d - languageName: node - linkType: hard - -"locate-path@npm:^5.0.0": - version: 5.0.0 - resolution: "locate-path@npm:5.0.0" - dependencies: - p-locate: "npm:^4.1.0" - checksum: 10c0/33a1c5247e87e022f9713e6213a744557a3e9ec32c5d0b5efb10aa3a38177615bf90221a5592674857039c1a0fd2063b82f285702d37b792d973e9e72ace6c59 - languageName: node - linkType: hard - -"locate-path@npm:^6.0.0": - version: 6.0.0 - resolution: "locate-path@npm:6.0.0" - dependencies: - p-locate: "npm:^5.0.0" - checksum: 10c0/d3972ab70dfe58ce620e64265f90162d247e87159b6126b01314dd67be43d50e96a50b517bce2d9452a79409c7614054c277b5232377de50416564a77ac7aad3 - languageName: node - linkType: hard - -"lodash.merge@npm:^4.6.2": - version: 4.6.2 - resolution: "lodash.merge@npm:4.6.2" - checksum: 10c0/402fa16a1edd7538de5b5903a90228aa48eb5533986ba7fa26606a49db2572bf414ff73a2c9f5d5fd36b31c46a5d5c7e1527749c07cbcf965ccff5fbdf32c506 - languageName: node - linkType: hard - -"lodash@npm:^4.17.21": - version: 4.17.21 - resolution: "lodash@npm:4.17.21" - checksum: 10c0/d8cbea072bb08655bb4c989da418994b073a608dffa608b09ac04b43a791b12aeae7cd7ad919aa4c925f33b48490b5cfe6c1f71d827956071dae2e7bb3a6b74c - languageName: node - linkType: hard - -"logform@npm:^2.6.0, logform@npm:^2.6.1": - version: 2.6.1 - resolution: "logform@npm:2.6.1" - dependencies: - "@colors/colors": "npm:1.6.0" - "@types/triple-beam": "npm:^1.3.2" - fecha: "npm:^4.2.0" - ms: "npm:^2.1.1" - safe-stable-stringify: "npm:^2.3.1" - triple-beam: "npm:^1.3.0" - checksum: 10c0/c20019336b1da8c08adea67dd7de2b0effdc6e35289c0156722924b571df94ba9f900ef55620c56bceb07cae7cc46057c9859accdee37a131251ba34d6789bce - languageName: node - linkType: hard - -"lru-cache@npm:^10.0.1, lru-cache@npm:^10.2.0": - version: 10.2.2 - resolution: "lru-cache@npm:10.2.2" - checksum: 10c0/402d31094335851220d0b00985084288136136992979d0e015f0f1697e15d1c86052d7d53ae86b614e5b058425606efffc6969a31a091085d7a2b80a8a1e26d6 - languageName: node - linkType: hard - -"lru-cache@npm:^5.1.1": - version: 5.1.1 - resolution: "lru-cache@npm:5.1.1" - dependencies: - yallist: "npm:^3.0.2" - checksum: 10c0/89b2ef2ef45f543011e38737b8a8622a2f8998cddf0e5437174ef8f1f70a8b9d14a918ab3e232cb3ba343b7abddffa667f0b59075b2b80e6b4d63c3de6127482 - languageName: node - linkType: hard - -"lru-cache@npm:^6.0.0": - version: 6.0.0 - resolution: "lru-cache@npm:6.0.0" - dependencies: - yallist: "npm:^4.0.0" - checksum: 10c0/cb53e582785c48187d7a188d3379c181b5ca2a9c78d2bce3e7dee36f32761d1c42983da3fe12b55cb74e1779fa94cdc2e5367c028a9b35317184ede0c07a30a9 - languageName: node - linkType: hard - -"make-dir@npm:^3.1.0": - version: 3.1.0 - resolution: "make-dir@npm:3.1.0" - dependencies: - semver: "npm:^6.0.0" - checksum: 10c0/56aaafefc49c2dfef02c5c95f9b196c4eb6988040cf2c712185c7fe5c99b4091591a7fc4d4eafaaefa70ff763a26f6ab8c3ff60b9e75ea19876f49b18667ecaa - languageName: node - linkType: hard - -"make-dir@npm:^4.0.0": - version: 4.0.0 - resolution: "make-dir@npm:4.0.0" - dependencies: - semver: "npm:^7.5.3" - checksum: 10c0/69b98a6c0b8e5c4fe9acb61608a9fbcfca1756d910f51e5dbe7a9e5cfb74fca9b8a0c8a0ffdf1294a740826c1ab4871d5bf3f62f72a3049e5eac6541ddffed68 - languageName: node - linkType: hard - -"make-fetch-happen@npm:^13.0.0": - version: 13.0.1 - resolution: "make-fetch-happen@npm:13.0.1" - dependencies: - "@npmcli/agent": "npm:^2.0.0" - cacache: "npm:^18.0.0" - http-cache-semantics: "npm:^4.1.1" - is-lambda: "npm:^1.0.1" - minipass: "npm:^7.0.2" - minipass-fetch: "npm:^3.0.0" - minipass-flush: "npm:^1.0.5" - minipass-pipeline: "npm:^1.2.4" - negotiator: "npm:^0.6.3" - proc-log: "npm:^4.2.0" - promise-retry: "npm:^2.0.1" - ssri: "npm:^10.0.0" - checksum: 10c0/df5f4dbb6d98153b751bccf4dc4cc500de85a96a9331db9805596c46aa9f99d9555983954e6c1266d9f981ae37a9e4647f42b9a4bb5466f867f4012e582c9e7e - languageName: node - linkType: hard - -"makeerror@npm:1.0.12": - version: 1.0.12 - resolution: "makeerror@npm:1.0.12" - dependencies: - tmpl: "npm:1.0.5" - checksum: 10c0/b0e6e599780ce6bab49cc413eba822f7d1f0dfebd1c103eaa3785c59e43e22c59018323cf9e1708f0ef5329e94a745d163fcbb6bff8e4c6742f9be9e86f3500c - languageName: node - linkType: hard - -"media-typer@npm:0.3.0": - version: 0.3.0 - resolution: "media-typer@npm:0.3.0" - checksum: 10c0/d160f31246907e79fed398470285f21bafb45a62869dc469b1c8877f3f064f5eabc4bcc122f9479b8b605bc5c76187d7871cf84c4ee3ecd3e487da1993279928 - languageName: node - linkType: hard - -"merge-descriptors@npm:1.0.3": - version: 1.0.3 - resolution: "merge-descriptors@npm:1.0.3" - checksum: 10c0/866b7094afd9293b5ea5dcd82d71f80e51514bed33b4c4e9f516795dc366612a4cbb4dc94356e943a8a6914889a914530badff27f397191b9b75cda20b6bae93 - languageName: node - linkType: hard - -"merge-stream@npm:^2.0.0": - version: 2.0.0 - resolution: "merge-stream@npm:2.0.0" - checksum: 10c0/867fdbb30a6d58b011449b8885601ec1690c3e41c759ecd5a9d609094f7aed0096c37823ff4a7190ef0b8f22cc86beb7049196ff68c016e3b3c671d0dac91ce5 - languageName: node - linkType: hard - -"merge2@npm:^1.3.0, merge2@npm:^1.4.1": - version: 1.4.1 - resolution: "merge2@npm:1.4.1" - checksum: 10c0/254a8a4605b58f450308fc474c82ac9a094848081bf4c06778200207820e5193726dc563a0d2c16468810516a5c97d9d3ea0ca6585d23c58ccfff2403e8dbbeb - languageName: node - linkType: hard - -"methods@npm:^1.1.2, methods@npm:~1.1.2": - version: 1.1.2 - resolution: "methods@npm:1.1.2" - checksum: 10c0/bdf7cc72ff0a33e3eede03708c08983c4d7a173f91348b4b1e4f47d4cdbf734433ad971e7d1e8c77247d9e5cd8adb81ea4c67b0a2db526b758b2233d7814b8b2 - languageName: node - linkType: hard - -"micromatch@npm:^4.0.4": - version: 4.0.8 - resolution: "micromatch@npm:4.0.8" - dependencies: - braces: "npm:^3.0.3" - picomatch: "npm:^2.3.1" - checksum: 10c0/166fa6eb926b9553f32ef81f5f531d27b4ce7da60e5baf8c021d043b27a388fb95e46a8038d5045877881e673f8134122b59624d5cecbd16eb50a42e7a6b5ca8 - languageName: node - linkType: hard - -"migrate@npm:^2.0.1": - version: 2.1.0 - resolution: "migrate@npm:2.1.0" - dependencies: - chalk: "npm:^4.1.2" - commander: "npm:^2.20.3" - dateformat: "npm:^4.6.3" - dotenv: "npm:^16.0.0" - inherits: "npm:^2.0.3" - minimatch: "npm:^9.0.1" - mkdirp: "npm:^3.0.1" - slug: "npm:^8.2.2" - bin: - migrate: bin/migrate - migrate-create: bin/migrate-create - migrate-down: bin/migrate-down - migrate-init: bin/migrate-init - migrate-list: bin/migrate-list - migrate-up: bin/migrate-up - checksum: 10c0/15a3ccd14e95f6c1eed87860860ec3195910e96fa23702867ad6ddbfa802a8c93ea94672192f1c64f868cf441449ac9aaaed629a89fb09d7e139c9502ae9b5f8 - languageName: node - linkType: hard - -"mime-db@npm:1.52.0": - version: 1.52.0 - resolution: "mime-db@npm:1.52.0" - checksum: 10c0/0557a01deebf45ac5f5777fe7740b2a5c309c6d62d40ceab4e23da9f821899ce7a900b7ac8157d4548ddbb7beffe9abc621250e6d182b0397ec7f10c7b91a5aa - languageName: node - linkType: hard - -"mime-types@npm:^2.1.12, mime-types@npm:~2.1.24, mime-types@npm:~2.1.34": - version: 2.1.35 - resolution: "mime-types@npm:2.1.35" - dependencies: - mime-db: "npm:1.52.0" - checksum: 10c0/82fb07ec56d8ff1fc999a84f2f217aa46cb6ed1033fefaabd5785b9a974ed225c90dc72fff460259e66b95b73648596dbcc50d51ed69cdf464af2d237d3149b2 - languageName: node - linkType: hard - -"mime@npm:1.6.0": - version: 1.6.0 - resolution: "mime@npm:1.6.0" - bin: - mime: cli.js - checksum: 10c0/b92cd0adc44888c7135a185bfd0dddc42c32606401c72896a842ae15da71eb88858f17669af41e498b463cd7eb998f7b48939a25b08374c7924a9c8a6f8a81b0 - languageName: node - linkType: hard - -"mime@npm:2.6.0": - version: 2.6.0 - resolution: "mime@npm:2.6.0" - bin: - mime: cli.js - checksum: 10c0/a7f2589900d9c16e3bdf7672d16a6274df903da958c1643c9c45771f0478f3846dcb1097f31eb9178452570271361e2149310931ec705c037210fc69639c8e6c - languageName: node - linkType: hard - -"mimic-fn@npm:^2.1.0": - version: 2.1.0 - resolution: "mimic-fn@npm:2.1.0" - checksum: 10c0/b26f5479d7ec6cc2bce275a08f146cf78f5e7b661b18114e2506dd91ec7ec47e7a25bf4360e5438094db0560bcc868079fb3b1fb3892b833c1ecbf63f80c95a4 - languageName: node - linkType: hard - -"mimic-response@npm:^3.1.0": - version: 3.1.0 - resolution: "mimic-response@npm:3.1.0" - checksum: 10c0/0d6f07ce6e03e9e4445bee655202153bdb8a98d67ee8dc965ac140900d7a2688343e6b4c9a72cfc9ef2f7944dfd76eef4ab2482eb7b293a68b84916bac735362 - languageName: node - linkType: hard - -"minimatch@npm:^3.0.4, minimatch@npm:^3.0.5, minimatch@npm:^3.1.1, minimatch@npm:^3.1.2": - version: 3.1.2 - resolution: "minimatch@npm:3.1.2" - dependencies: - brace-expansion: "npm:^1.1.7" - checksum: 10c0/0262810a8fc2e72cca45d6fd86bd349eee435eb95ac6aa45c9ea2180e7ee875ef44c32b55b5973ceabe95ea12682f6e3725cbb63d7a2d1da3ae1163c8b210311 - languageName: node - linkType: hard - -"minimatch@npm:^9.0.1, minimatch@npm:^9.0.4": - version: 9.0.4 - resolution: "minimatch@npm:9.0.4" - dependencies: - brace-expansion: "npm:^2.0.1" - checksum: 10c0/2c16f21f50e64922864e560ff97c587d15fd491f65d92a677a344e970fe62aafdbeafe648965fa96d33c061b4d0eabfe0213466203dd793367e7f28658cf6414 - languageName: node - linkType: hard - -"minimist@npm:^1.2.0, minimist@npm:^1.2.3": - version: 1.2.8 - resolution: "minimist@npm:1.2.8" - checksum: 10c0/19d3fcdca050087b84c2029841a093691a91259a47def2f18222f41e7645a0b7c44ef4b40e88a1e58a40c84d2ef0ee6047c55594d298146d0eb3f6b737c20ce6 - languageName: node - linkType: hard - -"minipass-collect@npm:^2.0.1": - version: 2.0.1 - resolution: "minipass-collect@npm:2.0.1" - dependencies: - minipass: "npm:^7.0.3" - checksum: 10c0/5167e73f62bb74cc5019594709c77e6a742051a647fe9499abf03c71dca75515b7959d67a764bdc4f8b361cf897fbf25e2d9869ee039203ed45240f48b9aa06e - languageName: node - linkType: hard - -"minipass-fetch@npm:^3.0.0": - version: 3.0.5 - resolution: "minipass-fetch@npm:3.0.5" - dependencies: - encoding: "npm:^0.1.13" - minipass: "npm:^7.0.3" - minipass-sized: "npm:^1.0.3" - minizlib: "npm:^2.1.2" - dependenciesMeta: - encoding: - optional: true - checksum: 10c0/9d702d57f556274286fdd97e406fc38a2f5c8d15e158b498d7393b1105974b21249289ec571fa2b51e038a4872bfc82710111cf75fae98c662f3d6f95e72152b - languageName: node - linkType: hard - -"minipass-flush@npm:^1.0.5": - version: 1.0.5 - resolution: "minipass-flush@npm:1.0.5" - dependencies: - minipass: "npm:^3.0.0" - checksum: 10c0/2a51b63feb799d2bb34669205eee7c0eaf9dce01883261a5b77410c9408aa447e478efd191b4de6fc1101e796ff5892f8443ef20d9544385819093dbb32d36bd - languageName: node - linkType: hard - -"minipass-pipeline@npm:^1.2.4": - version: 1.2.4 - resolution: "minipass-pipeline@npm:1.2.4" - dependencies: - minipass: "npm:^3.0.0" - checksum: 10c0/cbda57cea20b140b797505dc2cac71581a70b3247b84480c1fed5ca5ba46c25ecc25f68bfc9e6dcb1a6e9017dab5c7ada5eab73ad4f0a49d84e35093e0c643f2 - languageName: node - linkType: hard - -"minipass-sized@npm:^1.0.3": - version: 1.0.3 - resolution: "minipass-sized@npm:1.0.3" - dependencies: - minipass: "npm:^3.0.0" - checksum: 10c0/298f124753efdc745cfe0f2bdfdd81ba25b9f4e753ca4a2066eb17c821f25d48acea607dfc997633ee5bf7b6dfffb4eee4f2051eb168663f0b99fad2fa4829cb - languageName: node - linkType: hard - -"minipass@npm:^3.0.0": - version: 3.3.6 - resolution: "minipass@npm:3.3.6" - dependencies: - yallist: "npm:^4.0.0" - checksum: 10c0/a114746943afa1dbbca8249e706d1d38b85ed1298b530f5808ce51f8e9e941962e2a5ad2e00eae7dd21d8a4aae6586a66d4216d1a259385e9d0358f0c1eba16c - languageName: node - linkType: hard - -"minipass@npm:^5.0.0": - version: 5.0.0 - resolution: "minipass@npm:5.0.0" - checksum: 10c0/a91d8043f691796a8ac88df039da19933ef0f633e3d7f0d35dcd5373af49131cf2399bfc355f41515dc495e3990369c3858cd319e5c2722b4753c90bf3152462 - languageName: node - linkType: hard - -"minipass@npm:^5.0.0 || ^6.0.2 || ^7.0.0, minipass@npm:^7.0.2, minipass@npm:^7.0.3, minipass@npm:^7.1.2": - version: 7.1.2 - resolution: "minipass@npm:7.1.2" - checksum: 10c0/b0fd20bb9fb56e5fa9a8bfac539e8915ae07430a619e4b86ff71f5fc757ef3924b23b2c4230393af1eda647ed3d75739e4e0acb250a6b1eb277cf7f8fe449557 - languageName: node - linkType: hard - -"minizlib@npm:^2.1.1, minizlib@npm:^2.1.2": - version: 2.1.2 - resolution: "minizlib@npm:2.1.2" - dependencies: - minipass: "npm:^3.0.0" - yallist: "npm:^4.0.0" - checksum: 10c0/64fae024e1a7d0346a1102bb670085b17b7f95bf6cfdf5b128772ec8faf9ea211464ea4add406a3a6384a7d87a0cd1a96263692134323477b4fb43659a6cab78 - languageName: node - linkType: hard - -"mkdirp-classic@npm:^0.5.2, mkdirp-classic@npm:^0.5.3": - version: 0.5.3 - resolution: "mkdirp-classic@npm:0.5.3" - checksum: 10c0/95371d831d196960ddc3833cc6907e6b8f67ac5501a6582f47dfae5eb0f092e9f8ce88e0d83afcae95d6e2b61a01741ba03714eeafb6f7a6e9dcc158ac85b168 - languageName: node - linkType: hard - -"mkdirp@npm:^1.0.3, mkdirp@npm:^1.0.4": - version: 1.0.4 - resolution: "mkdirp@npm:1.0.4" - bin: - mkdirp: bin/cmd.js - checksum: 10c0/46ea0f3ffa8bc6a5bc0c7081ffc3907777f0ed6516888d40a518c5111f8366d97d2678911ad1a6882bf592fa9de6c784fea32e1687bb94e1f4944170af48a5cf - languageName: node - linkType: hard - -"mkdirp@npm:^3.0.1": - version: 3.0.1 - resolution: "mkdirp@npm:3.0.1" - bin: - mkdirp: dist/cjs/src/bin.js - checksum: 10c0/9f2b975e9246351f5e3a40dcfac99fcd0baa31fbfab615fe059fb11e51f10e4803c63de1f384c54d656e4db31d000e4767e9ef076a22e12a641357602e31d57d - languageName: node - linkType: hard - -"ms@npm:2.0.0": - version: 2.0.0 - resolution: "ms@npm:2.0.0" - checksum: 10c0/f8fda810b39fd7255bbdc451c46286e549794fcc700dc9cd1d25658bbc4dc2563a5de6fe7c60f798a16a60c6ceb53f033cb353f493f0cf63e5199b702943159d - languageName: node - linkType: hard - -"ms@npm:2.1.2": - version: 2.1.2 - resolution: "ms@npm:2.1.2" - checksum: 10c0/a437714e2f90dbf881b5191d35a6db792efbca5badf112f87b9e1c712aace4b4b9b742dd6537f3edf90fd6f684de897cec230abde57e87883766712ddda297cc - languageName: node - linkType: hard - -"ms@npm:2.1.3, ms@npm:^2.1.1": - version: 2.1.3 - resolution: "ms@npm:2.1.3" - checksum: 10c0/d924b57e7312b3b63ad21fc5b3dc0af5e78d61a1fc7cfb5457edaf26326bf62be5307cc87ffb6862ef1c2b33b0233cdb5d4f01c4c958cc0d660948b65a287a48 - languageName: node - linkType: hard - -"murmurhash@npm:^2.0.1": - version: 2.0.1 - resolution: "murmurhash@npm:2.0.1" - checksum: 10c0/f6c7cb12d6ebc9c1cfd232fe9406089e1ceb128d24245e852866ba28967271925d915140f77fef7c92ee29b13165f4537ce80a85c3d0550b1b5cdb9f8bcaa19f - languageName: node - linkType: hard - -"napi-build-utils@npm:^1.0.1": - version: 1.0.2 - resolution: "napi-build-utils@npm:1.0.2" - checksum: 10c0/37fd2cd0ff2ad20073ce78d83fd718a740d568b225924e753ae51cb69d68f330c80544d487e5e5bd18e28702ed2ca469c2424ad948becd1862c1b0209542b2e9 - languageName: node - linkType: hard - -"natural-compare-lite@npm:^1.4.0": - version: 1.4.0 - resolution: "natural-compare-lite@npm:1.4.0" - checksum: 10c0/f6cef26f5044515754802c0fc475d81426f3b90fe88c20fabe08771ce1f736ce46e0397c10acb569a4dd0acb84c7f1ee70676122f95d5bfdd747af3a6c6bbaa8 - languageName: node - linkType: hard - -"natural-compare@npm:^1.4.0": - version: 1.4.0 - resolution: "natural-compare@npm:1.4.0" - checksum: 10c0/f5f9a7974bfb28a91afafa254b197f0f22c684d4a1731763dda960d2c8e375b36c7d690e0d9dc8fba774c537af14a7e979129bca23d88d052fbeb9466955e447 - languageName: node - linkType: hard - -"negotiator@npm:0.6.3, negotiator@npm:^0.6.3": - version: 0.6.3 - resolution: "negotiator@npm:0.6.3" - checksum: 10c0/3ec9fd413e7bf071c937ae60d572bc67155262068ed522cf4b3be5edbe6ddf67d095ec03a3a14ebf8fc8e95f8e1d61be4869db0dbb0de696f6b837358bd43fc2 - languageName: node - linkType: hard - -"node-abi@npm:^3.3.0": - version: 3.65.0 - resolution: "node-abi@npm:3.65.0" - dependencies: - semver: "npm:^7.3.5" - checksum: 10c0/112672015d8f27d6be2f18d64569f28f5d6a15a94cc510da513c69c3e3ab5df6dac196ef13ff115a8fadb69b554974c47ef89b4f6350a2b02de2bca5c23db1e5 - languageName: node - linkType: hard - -"node-addon-api@npm:^5.0.0": - version: 5.1.0 - resolution: "node-addon-api@npm:5.1.0" - dependencies: - node-gyp: "npm:latest" - checksum: 10c0/0eb269786124ba6fad9df8007a149e03c199b3e5a3038125dfb3e747c2d5113d406a4e33f4de1ea600aa2339be1f137d55eba1a73ee34e5fff06c52a5c296d1d - languageName: node - linkType: hard - -"node-fetch@npm:^2.6.7": - version: 2.7.0 - resolution: "node-fetch@npm:2.7.0" - dependencies: - whatwg-url: "npm:^5.0.0" - peerDependencies: - encoding: ^0.1.0 - peerDependenciesMeta: - encoding: - optional: true - checksum: 10c0/b55786b6028208e6fbe594ccccc213cab67a72899c9234eb59dba51062a299ea853210fcf526998eaa2867b0963ad72338824450905679ff0fa304b8c5093ae8 - languageName: node - linkType: hard - -"node-gyp@npm:latest": - version: 10.1.0 - resolution: "node-gyp@npm:10.1.0" - dependencies: - env-paths: "npm:^2.2.0" - exponential-backoff: "npm:^3.1.1" - glob: "npm:^10.3.10" - graceful-fs: "npm:^4.2.6" - make-fetch-happen: "npm:^13.0.0" - nopt: "npm:^7.0.0" - proc-log: "npm:^3.0.0" - semver: "npm:^7.3.5" - tar: "npm:^6.1.2" - which: "npm:^4.0.0" - bin: - node-gyp: bin/node-gyp.js - checksum: 10c0/9cc821111ca244a01fb7f054db7523ab0a0cd837f665267eb962eb87695d71fb1e681f9e21464cc2fd7c05530dc4c81b810bca1a88f7d7186909b74477491a3c - languageName: node - linkType: hard - -"node-int64@npm:^0.4.0": - version: 0.4.0 - resolution: "node-int64@npm:0.4.0" - checksum: 10c0/a6a4d8369e2f2720e9c645255ffde909c0fbd41c92ea92a5607fc17055955daac99c1ff589d421eee12a0d24e99f7bfc2aabfeb1a4c14742f6c099a51863f31a - languageName: node - linkType: hard - -"node-releases@npm:^2.0.14": - version: 2.0.14 - resolution: "node-releases@npm:2.0.14" - checksum: 10c0/199fc93773ae70ec9969bc6d5ac5b2bbd6eb986ed1907d751f411fef3ede0e4bfdb45ceb43711f8078bea237b6036db8b1bf208f6ff2b70c7d615afd157f3ab9 - languageName: node - linkType: hard - -"nopt@npm:^5.0.0": - version: 5.0.0 - resolution: "nopt@npm:5.0.0" - dependencies: - abbrev: "npm:1" - bin: - nopt: bin/nopt.js - checksum: 10c0/fc5c4f07155cb455bf5fc3dd149fac421c1a40fd83c6bfe83aa82b52f02c17c5e88301321318adaa27611c8a6811423d51d29deaceab5fa158b585a61a551061 - languageName: node - linkType: hard - -"nopt@npm:^7.0.0": - version: 7.2.1 - resolution: "nopt@npm:7.2.1" - dependencies: - abbrev: "npm:^2.0.0" - bin: - nopt: bin/nopt.js - checksum: 10c0/a069c7c736767121242037a22a788863accfa932ab285a1eb569eb8cd534b09d17206f68c37f096ae785647435e0c5a5a0a67b42ec743e481a455e5ae6a6df81 - languageName: node - linkType: hard - -"nordigen-node@npm:^1.4.0": - version: 1.4.0 - resolution: "nordigen-node@npm:1.4.0" - dependencies: - axios: "npm:^1.2.1" - dotenv: "npm:^10.0.0" - checksum: 10c0/a04ec90480e4e65b2169d909ac9ea3044f764d59283162420d287a6b229808754dc78c758637724d63c87aa77f2237bc47543f521b0b4057ed3980d6db137e1a - languageName: node - linkType: hard - -"normalize-path@npm:^3.0.0": - version: 3.0.0 - resolution: "normalize-path@npm:3.0.0" - checksum: 10c0/e008c8142bcc335b5e38cf0d63cfd39d6cf2d97480af9abdbe9a439221fd4d749763bab492a8ee708ce7a194bb00c9da6d0a115018672310850489137b3da046 - languageName: node - linkType: hard - -"npm-run-path@npm:^4.0.1": - version: 4.0.1 - resolution: "npm-run-path@npm:4.0.1" - dependencies: - path-key: "npm:^3.0.0" - checksum: 10c0/6f9353a95288f8455cf64cbeb707b28826a7f29690244c1e4bb61ec573256e021b6ad6651b394eb1ccfd00d6ec50147253aba2c5fe58a57ceb111fad62c519ac - languageName: node - linkType: hard - -"npmlog@npm:^5.0.1": - version: 5.0.1 - resolution: "npmlog@npm:5.0.1" - dependencies: - are-we-there-yet: "npm:^2.0.0" - console-control-strings: "npm:^1.1.0" - gauge: "npm:^3.0.0" - set-blocking: "npm:^2.0.0" - checksum: 10c0/489ba519031013001135c463406f55491a17fc7da295c18a04937fe3a4d523fd65e88dd418a28b967ab743d913fdeba1e29838ce0ad8c75557057c481f7d49fa - languageName: node - linkType: hard - -"object-assign@npm:^4, object-assign@npm:^4.1.1": - version: 4.1.1 - resolution: "object-assign@npm:4.1.1" - checksum: 10c0/1f4df9945120325d041ccf7b86f31e8bcc14e73d29171e37a7903050e96b81323784ec59f93f102ec635bcf6fa8034ba3ea0a8c7e69fa202b87ae3b6cec5a414 - languageName: node - linkType: hard - -"object-hash@npm:^2.2.0": - version: 2.2.0 - resolution: "object-hash@npm:2.2.0" - checksum: 10c0/1527de843926c5442ed61f8bdddfc7dc181b6497f725b0e89fcf50a55d9c803088763ed447cac85a5aa65345f1e99c2469ba679a54349ef3c4c0aeaa396a3eb9 - languageName: node - linkType: hard - -"object-inspect@npm:^1.13.1": - version: 1.13.1 - resolution: "object-inspect@npm:1.13.1" - checksum: 10c0/fad603f408e345c82e946abdf4bfd774260a5ed3e5997a0b057c44153ac32c7271ff19e3a5ae39c858da683ba045ccac2f65245c12763ce4e8594f818f4a648d - languageName: node - linkType: hard - -"oidc-token-hash@npm:^5.0.3": - version: 5.0.3 - resolution: "oidc-token-hash@npm:5.0.3" - checksum: 10c0/d0dc0551406f09577874155cc83cf69c39e4b826293d50bb6c37936698aeca17d4bcee356ab910c859e53e83f2728a2acbd041020165191353b29de51fbca615 - languageName: node - linkType: hard - -"on-finished@npm:2.4.1": - version: 2.4.1 - resolution: "on-finished@npm:2.4.1" - dependencies: - ee-first: "npm:1.1.1" - checksum: 10c0/46fb11b9063782f2d9968863d9cbba33d77aa13c17f895f56129c274318b86500b22af3a160fe9995aa41317efcd22941b6eba747f718ced08d9a73afdb087b4 - languageName: node - linkType: hard - -"on-headers@npm:1.0.1": - version: 1.0.1 - resolution: "on-headers@npm:1.0.1" - checksum: 10c0/060229267db33d0f56b03f59f4a1edf50130bf51498599b1f4b1a1bcf364bd8a9e05c3a984f0d0d384e2cdcdacdf176379a17e67bf5041e84c8c5f4a1938ec82 - languageName: node - linkType: hard - -"once@npm:^1.3.0, once@npm:^1.3.1, once@npm:^1.4.0": - version: 1.4.0 - resolution: "once@npm:1.4.0" - dependencies: - wrappy: "npm:1" - checksum: 10c0/5d48aca287dfefabd756621c5dfce5c91a549a93e9fdb7b8246bc4c4790aa2ec17b34a260530474635147aeb631a2dcc8b32c613df0675f96041cbb8244517d0 - languageName: node - linkType: hard - -"one-time@npm:^1.0.0": - version: 1.0.0 - resolution: "one-time@npm:1.0.0" - dependencies: - fn.name: "npm:1.x.x" - checksum: 10c0/6e4887b331edbb954f4e915831cbec0a7b9956c36f4feb5f6de98c448ac02ff881fd8d9b55a6b1b55030af184c6b648f340a76eb211812f4ad8c9b4b8692fdaa - languageName: node - linkType: hard - -"onetime@npm:^5.1.2": - version: 5.1.2 - resolution: "onetime@npm:5.1.2" - dependencies: - mimic-fn: "npm:^2.1.0" - checksum: 10c0/ffcef6fbb2692c3c40749f31ea2e22677a876daea92959b8a80b521d95cca7a668c884d8b2045d1d8ee7d56796aa405c405462af112a1477594cc63531baeb8f - languageName: node - linkType: hard - -"openid-client@npm:^5.4.2": - version: 5.6.5 - resolution: "openid-client@npm:5.6.5" - dependencies: - jose: "npm:^4.15.5" - lru-cache: "npm:^6.0.0" - object-hash: "npm:^2.2.0" - oidc-token-hash: "npm:^5.0.3" - checksum: 10c0/4308dcd37a9ffb1efc2ede0bc556ae42ccc2569e71baa52a03ddfa44407bf403d4534286f6f571381c5eaa1845c609ed699a5eb0d350acfb8c3bacb72c2a6890 - languageName: node - linkType: hard - -"optionator@npm:^0.9.3": - version: 0.9.4 - resolution: "optionator@npm:0.9.4" - dependencies: - deep-is: "npm:^0.1.3" - fast-levenshtein: "npm:^2.0.6" - levn: "npm:^0.4.1" - prelude-ls: "npm:^1.2.1" - type-check: "npm:^0.4.0" - word-wrap: "npm:^1.2.5" - checksum: 10c0/4afb687a059ee65b61df74dfe87d8d6815cd6883cb8b3d5883a910df72d0f5d029821f37025e4bccf4048873dbdb09acc6d303d27b8f76b1a80dd5a7d5334675 - languageName: node - linkType: hard - -"p-limit@npm:^2.2.0": - version: 2.3.0 - resolution: "p-limit@npm:2.3.0" - dependencies: - p-try: "npm:^2.0.0" - checksum: 10c0/8da01ac53efe6a627080fafc127c873da40c18d87b3f5d5492d465bb85ec7207e153948df6b9cbaeb130be70152f874229b8242ee2be84c0794082510af97f12 - languageName: node - linkType: hard - -"p-limit@npm:^3.0.2, p-limit@npm:^3.1.0": - version: 3.1.0 - resolution: "p-limit@npm:3.1.0" - dependencies: - yocto-queue: "npm:^0.1.0" - checksum: 10c0/9db675949dbdc9c3763c89e748d0ef8bdad0afbb24d49ceaf4c46c02c77d30db4e0652ed36d0a0a7a95154335fab810d95c86153105bb73b3a90448e2bb14e1a - languageName: node - linkType: hard - -"p-locate@npm:^4.1.0": - version: 4.1.0 - resolution: "p-locate@npm:4.1.0" - dependencies: - p-limit: "npm:^2.2.0" - checksum: 10c0/1b476ad69ad7f6059744f343b26d51ce091508935c1dbb80c4e0a2f397ffce0ca3a1f9f5cd3c7ce19d7929a09719d5c65fe70d8ee289c3f267cd36f2881813e9 - languageName: node - linkType: hard - -"p-locate@npm:^5.0.0": - version: 5.0.0 - resolution: "p-locate@npm:5.0.0" - dependencies: - p-limit: "npm:^3.0.2" - checksum: 10c0/2290d627ab7903b8b70d11d384fee714b797f6040d9278932754a6860845c4d3190603a0772a663c8cb5a7b21d1b16acb3a6487ebcafa9773094edc3dfe6009a - languageName: node - linkType: hard - -"p-map@npm:^4.0.0": - version: 4.0.0 - resolution: "p-map@npm:4.0.0" - dependencies: - aggregate-error: "npm:^3.0.0" - checksum: 10c0/592c05bd6262c466ce269ff172bb8de7c6975afca9b50c975135b974e9bdaafbfe80e61aaaf5be6d1200ba08b30ead04b88cfa7e25ff1e3b93ab28c9f62a2c75 - languageName: node - linkType: hard - -"p-try@npm:^2.0.0": - version: 2.2.0 - resolution: "p-try@npm:2.2.0" - checksum: 10c0/c36c19907734c904b16994e6535b02c36c2224d433e01a2f1ab777237f4d86e6289fd5fd464850491e940379d4606ed850c03e0f9ab600b0ebddb511312e177f - languageName: node - linkType: hard - -"parent-module@npm:^1.0.0": - version: 1.0.1 - resolution: "parent-module@npm:1.0.1" - dependencies: - callsites: "npm:^3.0.0" - checksum: 10c0/c63d6e80000d4babd11978e0d3fee386ca7752a02b035fd2435960ffaa7219dc42146f07069fb65e6e8bf1caef89daf9af7535a39bddf354d78bf50d8294f556 - languageName: node - linkType: hard - -"parse-json@npm:^5.2.0": - version: 5.2.0 - resolution: "parse-json@npm:5.2.0" - dependencies: - "@babel/code-frame": "npm:^7.0.0" - error-ex: "npm:^1.3.1" - json-parse-even-better-errors: "npm:^2.3.0" - lines-and-columns: "npm:^1.1.6" - checksum: 10c0/77947f2253005be7a12d858aedbafa09c9ae39eb4863adf330f7b416ca4f4a08132e453e08de2db46459256fb66afaac5ee758b44fe6541b7cdaf9d252e59585 - languageName: node - linkType: hard - -"parseurl@npm:~1.3.3": - version: 1.3.3 - resolution: "parseurl@npm:1.3.3" - checksum: 10c0/90dd4760d6f6174adb9f20cf0965ae12e23879b5f5464f38e92fce8073354341e4b3b76fa3d878351efe7d01e617121955284cfd002ab087fba1a0726ec0b4f5 - languageName: node - linkType: hard - -"path-exists@npm:^4.0.0": - version: 4.0.0 - resolution: "path-exists@npm:4.0.0" - checksum: 10c0/8c0bd3f5238188197dc78dced15207a4716c51cc4e3624c44fc97acf69558f5ebb9a2afff486fe1b4ee148e0c133e96c5e11a9aa5c48a3006e3467da070e5e1b - languageName: node - linkType: hard - -"path-is-absolute@npm:^1.0.0": - version: 1.0.1 - resolution: "path-is-absolute@npm:1.0.1" - checksum: 10c0/127da03c82172a2a50099cddbf02510c1791fc2cc5f7713ddb613a56838db1e8168b121a920079d052e0936c23005562059756d653b7c544c53185efe53be078 - languageName: node - linkType: hard - -"path-key@npm:^3.0.0, path-key@npm:^3.1.0": - version: 3.1.1 - resolution: "path-key@npm:3.1.1" - checksum: 10c0/748c43efd5a569c039d7a00a03b58eecd1d75f3999f5a28303d75f521288df4823bc057d8784eb72358b2895a05f29a070bc9f1f17d28226cc4e62494cc58c4c - languageName: node - linkType: hard - -"path-parse@npm:^1.0.7": - version: 1.0.7 - resolution: "path-parse@npm:1.0.7" - checksum: 10c0/11ce261f9d294cc7a58d6a574b7f1b935842355ec66fba3c3fd79e0f036462eaf07d0aa95bb74ff432f9afef97ce1926c720988c6a7451d8a584930ae7de86e1 - languageName: node - linkType: hard - -"path-scurry@npm:^1.11.1": - version: 1.11.1 - resolution: "path-scurry@npm:1.11.1" - dependencies: - lru-cache: "npm:^10.2.0" - minipass: "npm:^5.0.0 || ^6.0.2 || ^7.0.0" - checksum: 10c0/32a13711a2a505616ae1cc1b5076801e453e7aae6ac40ab55b388bb91b9d0547a52f5aaceff710ea400205f18691120d4431e520afbe4266b836fadede15872d - languageName: node - linkType: hard - -"path-to-regexp@npm:0.1.10": - version: 0.1.10 - resolution: "path-to-regexp@npm:0.1.10" - checksum: 10c0/34196775b9113ca6df88e94c8d83ba82c0e1a2063dd33bfe2803a980da8d49b91db8104f49d5191b44ea780d46b8670ce2b7f4a5e349b0c48c6779b653f1afe4 - languageName: node - linkType: hard - -"path-type@npm:^4.0.0": - version: 4.0.0 - resolution: "path-type@npm:4.0.0" - checksum: 10c0/666f6973f332f27581371efaf303fd6c272cc43c2057b37aa99e3643158c7e4b2626549555d88626e99ea9e046f82f32e41bbde5f1508547e9a11b149b52387c - languageName: node - linkType: hard - -"picocolors@npm:^1.0.0, picocolors@npm:^1.0.1": - version: 1.0.1 - resolution: "picocolors@npm:1.0.1" - checksum: 10c0/c63cdad2bf812ef0d66c8db29583802355d4ca67b9285d846f390cc15c2f6ccb94e8cb7eb6a6e97fc5990a6d3ad4ae42d86c84d3146e667c739a4234ed50d400 - languageName: node - linkType: hard - -"picomatch@npm:^2.0.4, picomatch@npm:^2.2.3, picomatch@npm:^2.3.1": - version: 2.3.1 - resolution: "picomatch@npm:2.3.1" - checksum: 10c0/26c02b8d06f03206fc2ab8d16f19960f2ff9e81a658f831ecb656d8f17d9edc799e8364b1f4a7873e89d9702dff96204be0fa26fe4181f6843f040f819dac4be - languageName: node - linkType: hard - -"pirates@npm:^4.0.4": - version: 4.0.6 - resolution: "pirates@npm:4.0.6" - checksum: 10c0/00d5fa51f8dded94d7429700fb91a0c1ead00ae2c7fd27089f0c5b63e6eca36197fe46384631872690a66f390c5e27198e99006ab77ae472692ab9c2ca903f36 - languageName: node - linkType: hard - -"pkg-dir@npm:^4.2.0": - version: 4.2.0 - resolution: "pkg-dir@npm:4.2.0" - dependencies: - find-up: "npm:^4.0.0" - checksum: 10c0/c56bda7769e04907a88423feb320babaed0711af8c436ce3e56763ab1021ba107c7b0cafb11cde7529f669cfc22bffcaebffb573645cbd63842ea9fb17cd7728 - languageName: node - linkType: hard - -"prebuild-install@npm:^7.1.1": - version: 7.1.2 - resolution: "prebuild-install@npm:7.1.2" - dependencies: - detect-libc: "npm:^2.0.0" - expand-template: "npm:^2.0.3" - github-from-package: "npm:0.0.0" - minimist: "npm:^1.2.3" - mkdirp-classic: "npm:^0.5.3" - napi-build-utils: "npm:^1.0.1" - node-abi: "npm:^3.3.0" - pump: "npm:^3.0.0" - rc: "npm:^1.2.7" - simple-get: "npm:^4.0.0" - tar-fs: "npm:^2.0.0" - tunnel-agent: "npm:^0.6.0" - bin: - prebuild-install: bin.js - checksum: 10c0/e64868ba9ef2068fd7264f5b03e5298a901e02a450acdb1f56258d88c09dea601eefdb3d1dfdff8513fdd230a92961712be0676192626a3b4d01ba154d48bdd3 - languageName: node - linkType: hard - -"prelude-ls@npm:^1.2.1": - version: 1.2.1 - resolution: "prelude-ls@npm:1.2.1" - checksum: 10c0/b00d617431e7886c520a6f498a2e14c75ec58f6d93ba48c3b639cf241b54232d90daa05d83a9e9b9fef6baa63cb7e1e4602c2372fea5bc169668401eb127d0cd - languageName: node - linkType: hard - -"prettier-linter-helpers@npm:^1.0.0": - version: 1.0.0 - resolution: "prettier-linter-helpers@npm:1.0.0" - dependencies: - fast-diff: "npm:^1.1.2" - checksum: 10c0/81e0027d731b7b3697ccd2129470ed9913ecb111e4ec175a12f0fcfab0096516373bf0af2fef132af50cafb0a905b74ff57996d615f59512bb9ac7378fcc64ab - languageName: node - linkType: hard - -"prettier@npm:^2.8.3": - version: 2.8.8 - resolution: "prettier@npm:2.8.8" - bin: - prettier: bin-prettier.js - checksum: 10c0/463ea8f9a0946cd5b828d8cf27bd8b567345cf02f56562d5ecde198b91f47a76b7ac9eae0facd247ace70e927143af6135e8cf411986b8cb8478784a4d6d724a - languageName: node - linkType: hard - -"pretty-format@npm:^29.0.0, pretty-format@npm:^29.7.0": - version: 29.7.0 - resolution: "pretty-format@npm:29.7.0" - dependencies: - "@jest/schemas": "npm:^29.6.3" - ansi-styles: "npm:^5.0.0" - react-is: "npm:^18.0.0" - checksum: 10c0/edc5ff89f51916f036c62ed433506b55446ff739358de77207e63e88a28ca2894caac6e73dcb68166a606e51c8087d32d400473e6a9fdd2dbe743f46c9c0276f - languageName: node - linkType: hard - -"proc-log@npm:^3.0.0": - version: 3.0.0 - resolution: "proc-log@npm:3.0.0" - checksum: 10c0/f66430e4ff947dbb996058f6fd22de2c66612ae1a89b097744e17fb18a4e8e7a86db99eda52ccf15e53f00b63f4ec0b0911581ff2aac0355b625c8eac509b0dc - languageName: node - linkType: hard - -"proc-log@npm:^4.2.0": - version: 4.2.0 - resolution: "proc-log@npm:4.2.0" - checksum: 10c0/17db4757c2a5c44c1e545170e6c70a26f7de58feb985091fb1763f5081cab3d01b181fb2dd240c9f4a4255a1d9227d163d5771b7e69c9e49a561692db865efb9 - languageName: node - linkType: hard - -"promise-retry@npm:^2.0.1": - version: 2.0.1 - resolution: "promise-retry@npm:2.0.1" - dependencies: - err-code: "npm:^2.0.2" - retry: "npm:^0.12.0" - checksum: 10c0/9c7045a1a2928094b5b9b15336dcd2a7b1c052f674550df63cc3f36cd44028e5080448175b6f6ca32b642de81150f5e7b1a98b728f15cb069f2dd60ac2616b96 - languageName: node - linkType: hard - -"prompts@npm:^2.0.1": - version: 2.4.2 - resolution: "prompts@npm:2.4.2" - dependencies: - kleur: "npm:^3.0.3" - sisteransi: "npm:^1.0.5" - checksum: 10c0/16f1ac2977b19fe2cf53f8411cc98db7a3c8b115c479b2ca5c82b5527cd937aa405fa04f9a5960abeb9daef53191b53b4d13e35c1f5d50e8718c76917c5f1ea4 - languageName: node - linkType: hard - -"properties-reader@npm:^2.2.0": - version: 2.3.0 - resolution: "properties-reader@npm:2.3.0" - dependencies: - mkdirp: "npm:^1.0.4" - checksum: 10c0/f665057e3a9076c643ba1198afcc71703eda227a59913252f7ff9467ece8d29c0cf8bf14bf1abcaef71570840c32a4e257e6c39b7550451bbff1a777efcf5667 - languageName: node - linkType: hard - -"proxy-addr@npm:~2.0.7": - version: 2.0.7 - resolution: "proxy-addr@npm:2.0.7" - dependencies: - forwarded: "npm:0.2.0" - ipaddr.js: "npm:1.9.1" - checksum: 10c0/c3eed999781a35f7fd935f398b6d8920b6fb00bbc14287bc6de78128ccc1a02c89b95b56742bf7cf0362cc333c61d138532049c7dedc7a328ef13343eff81210 - languageName: node - linkType: hard - -"proxy-from-env@npm:^1.1.0": - version: 1.1.0 - resolution: "proxy-from-env@npm:1.1.0" - checksum: 10c0/fe7dd8b1bdbbbea18d1459107729c3e4a2243ca870d26d34c2c1bcd3e4425b7bcc5112362df2d93cc7fb9746f6142b5e272fd1cc5c86ddf8580175186f6ad42b - languageName: node - linkType: hard - -"pump@npm:^3.0.0": - version: 3.0.0 - resolution: "pump@npm:3.0.0" - dependencies: - end-of-stream: "npm:^1.1.0" - once: "npm:^1.3.1" - checksum: 10c0/bbdeda4f747cdf47db97428f3a135728669e56a0ae5f354a9ac5b74556556f5446a46f720a8f14ca2ece5be9b4d5d23c346db02b555f46739934cc6c093a5478 - languageName: node - linkType: hard - -"punycode@npm:^2.1.0": - version: 2.3.1 - resolution: "punycode@npm:2.3.1" - checksum: 10c0/14f76a8206bc3464f794fb2e3d3cc665ae416c01893ad7a02b23766eb07159144ee612ad67af5e84fa4479ccfe67678c4feb126b0485651b302babf66f04f9e9 - languageName: node - linkType: hard - -"pure-rand@npm:^6.0.0": - version: 6.1.0 - resolution: "pure-rand@npm:6.1.0" - checksum: 10c0/1abe217897bf74dcb3a0c9aba3555fe975023147b48db540aa2faf507aee91c03bf54f6aef0eb2bf59cc259a16d06b28eca37f0dc426d94f4692aeff02fb0e65 - languageName: node - linkType: hard - -"qs@npm:6.11.0": - version: 6.11.0 - resolution: "qs@npm:6.11.0" - dependencies: - side-channel: "npm:^1.0.4" - checksum: 10c0/4e4875e4d7c7c31c233d07a448e7e4650f456178b9dd3766b7cfa13158fdb24ecb8c4f059fa91e820dc6ab9f2d243721d071c9c0378892dcdad86e9e9a27c68f - languageName: node - linkType: hard - -"qs@npm:6.13.0": - version: 6.13.0 - resolution: "qs@npm:6.13.0" - dependencies: - side-channel: "npm:^1.0.6" - checksum: 10c0/62372cdeec24dc83a9fb240b7533c0fdcf0c5f7e0b83343edd7310f0ab4c8205a5e7c56406531f2e47e1b4878a3821d652be4192c841de5b032ca83619d8f860 - languageName: node - linkType: hard - -"qs@npm:^6.11.0": - version: 6.12.1 - resolution: "qs@npm:6.12.1" - dependencies: - side-channel: "npm:^1.0.6" - checksum: 10c0/439e6d7c6583e7c69f2cab2c39c55b97db7ce576e4c7c469082b938b7fc8746e8d547baacb69b4cd2b6666484776c3f4840ad7163a4c5326300b0afa0acdd84b - languageName: node - linkType: hard - -"queue-microtask@npm:^1.2.2": - version: 1.2.3 - resolution: "queue-microtask@npm:1.2.3" - checksum: 10c0/900a93d3cdae3acd7d16f642c29a642aea32c2026446151f0778c62ac089d4b8e6c986811076e1ae180a694cedf077d453a11b58ff0a865629a4f82ab558e102 - languageName: node - linkType: hard - -"range-parser@npm:~1.2.1": - version: 1.2.1 - resolution: "range-parser@npm:1.2.1" - checksum: 10c0/96c032ac2475c8027b7a4e9fe22dc0dfe0f6d90b85e496e0f016fbdb99d6d066de0112e680805075bd989905e2123b3b3d002765149294dce0c1f7f01fcc2ea0 - languageName: node - linkType: hard - -"raw-body@npm:2.5.2": - version: 2.5.2 - resolution: "raw-body@npm:2.5.2" - dependencies: - bytes: "npm:3.1.2" - http-errors: "npm:2.0.0" - iconv-lite: "npm:0.4.24" - unpipe: "npm:1.0.0" - checksum: 10c0/b201c4b66049369a60e766318caff5cb3cc5a900efd89bdac431463822d976ad0670912c931fdbdcf5543207daf6f6833bca57aa116e1661d2ea91e12ca692c4 - languageName: node - linkType: hard - -"rc@npm:^1.2.7": - version: 1.2.8 - resolution: "rc@npm:1.2.8" - dependencies: - deep-extend: "npm:^0.6.0" - ini: "npm:~1.3.0" - minimist: "npm:^1.2.0" - strip-json-comments: "npm:~2.0.1" - bin: - rc: ./cli.js - checksum: 10c0/24a07653150f0d9ac7168e52943cc3cb4b7a22c0e43c7dff3219977c2fdca5a2760a304a029c20811a0e79d351f57d46c9bde216193a0f73978496afc2b85b15 - languageName: node - linkType: hard - -"react-is@npm:^18.0.0": - version: 18.3.1 - resolution: "react-is@npm:18.3.1" - checksum: 10c0/f2f1e60010c683479e74c63f96b09fb41603527cd131a9959e2aee1e5a8b0caf270b365e5ca77d4a6b18aae659b60a86150bb3979073528877029b35aecd2072 - languageName: node - linkType: hard - -"readable-stream@npm:^3.1.1, readable-stream@npm:^3.4.0, readable-stream@npm:^3.6.0, readable-stream@npm:^3.6.2": - version: 3.6.2 - resolution: "readable-stream@npm:3.6.2" - dependencies: - inherits: "npm:^2.0.3" - string_decoder: "npm:^1.1.1" - util-deprecate: "npm:^1.0.1" - checksum: 10c0/e37be5c79c376fdd088a45fa31ea2e423e5d48854be7a22a58869b4e84d25047b193f6acb54f1012331e1bcd667ffb569c01b99d36b0bd59658fb33f513511b7 - languageName: node - linkType: hard - -"regenerator-runtime@npm:^0.14.0": - version: 0.14.1 - resolution: "regenerator-runtime@npm:0.14.1" - checksum: 10c0/1b16eb2c4bceb1665c89de70dcb64126a22bc8eb958feef3cd68fe11ac6d2a4899b5cd1b80b0774c7c03591dc57d16631a7f69d2daa2ec98100e2f29f7ec4cc4 - languageName: node - linkType: hard - -"require-directory@npm:^2.1.1": - version: 2.1.1 - resolution: "require-directory@npm:2.1.1" - checksum: 10c0/83aa76a7bc1531f68d92c75a2ca2f54f1b01463cb566cf3fbc787d0de8be30c9dbc211d1d46be3497dac5785fe296f2dd11d531945ac29730643357978966e99 - languageName: node - linkType: hard - -"resolve-cwd@npm:^3.0.0": - version: 3.0.0 - resolution: "resolve-cwd@npm:3.0.0" - dependencies: - resolve-from: "npm:^5.0.0" - checksum: 10c0/e608a3ebd15356264653c32d7ecbc8fd702f94c6703ea4ac2fb81d9c359180cba0ae2e6b71faa446631ed6145454d5a56b227efc33a2d40638ac13f8beb20ee4 - languageName: node - linkType: hard - -"resolve-from@npm:^4.0.0": - version: 4.0.0 - resolution: "resolve-from@npm:4.0.0" - checksum: 10c0/8408eec31a3112ef96e3746c37be7d64020cda07c03a920f5024e77290a218ea758b26ca9529fd7b1ad283947f34b2291c1c0f6aa0ed34acfdda9c6014c8d190 - languageName: node - linkType: hard - -"resolve-from@npm:^5.0.0": - version: 5.0.0 - resolution: "resolve-from@npm:5.0.0" - checksum: 10c0/b21cb7f1fb746de8107b9febab60095187781137fd803e6a59a76d421444b1531b641bba5857f5dc011974d8a5c635d61cec49e6bd3b7fc20e01f0fafc4efbf2 - languageName: node - linkType: hard - -"resolve.exports@npm:^2.0.0": - version: 2.0.2 - resolution: "resolve.exports@npm:2.0.2" - checksum: 10c0/cc4cffdc25447cf34730f388dca5021156ba9302a3bad3d7f168e790dc74b2827dff603f1bc6ad3d299bac269828dca96dd77e036dc9fba6a2a1807c47ab5c98 - languageName: node - linkType: hard - -"resolve@npm:^1.20.0": - version: 1.22.8 - resolution: "resolve@npm:1.22.8" - dependencies: - is-core-module: "npm:^2.13.0" - path-parse: "npm:^1.0.7" - supports-preserve-symlinks-flag: "npm:^1.0.0" - bin: - resolve: bin/resolve - checksum: 10c0/07e179f4375e1fd072cfb72ad66d78547f86e6196c4014b31cb0b8bb1db5f7ca871f922d08da0fbc05b94e9fd42206f819648fa3b5b873ebbc8e1dc68fec433a - languageName: node - linkType: hard - -"resolve@patch:resolve@npm%3A^1.20.0#optional!builtin<compat/resolve>": - version: 1.22.8 - resolution: "resolve@patch:resolve@npm%3A1.22.8#optional!builtin<compat/resolve>::version=1.22.8&hash=c3c19d" - dependencies: - is-core-module: "npm:^2.13.0" - path-parse: "npm:^1.0.7" - supports-preserve-symlinks-flag: "npm:^1.0.0" - bin: - resolve: bin/resolve - checksum: 10c0/0446f024439cd2e50c6c8fa8ba77eaa8370b4180f401a96abf3d1ebc770ac51c1955e12764cde449fde3fff480a61f84388e3505ecdbab778f4bef5f8212c729 - languageName: node - linkType: hard - -"retry@npm:^0.12.0": - version: 0.12.0 - resolution: "retry@npm:0.12.0" - checksum: 10c0/59933e8501727ba13ad73ef4a04d5280b3717fd650408460c987392efe9d7be2040778ed8ebe933c5cbd63da3dcc37919c141ef8af0a54a6e4fca5a2af177bfe - languageName: node - linkType: hard - -"reusify@npm:^1.0.4": - version: 1.0.4 - resolution: "reusify@npm:1.0.4" - checksum: 10c0/c19ef26e4e188f408922c46f7ff480d38e8dfc55d448310dfb518736b23ed2c4f547fb64a6ed5bdba92cd7e7ddc889d36ff78f794816d5e71498d645ef476107 - languageName: node - linkType: hard - -"rimraf@npm:^3.0.2": - version: 3.0.2 - resolution: "rimraf@npm:3.0.2" - dependencies: - glob: "npm:^7.1.3" - bin: - rimraf: bin.js - checksum: 10c0/9cb7757acb489bd83757ba1a274ab545eafd75598a9d817e0c3f8b164238dd90eba50d6b848bd4dcc5f3040912e882dc7ba71653e35af660d77b25c381d402e8 - languageName: node - linkType: hard - -"run-parallel@npm:^1.1.9": - version: 1.2.0 - resolution: "run-parallel@npm:1.2.0" - dependencies: - queue-microtask: "npm:^1.2.2" - checksum: 10c0/200b5ab25b5b8b7113f9901bfe3afc347e19bb7475b267d55ad0eb86a62a46d77510cb0f232507c9e5d497ebda569a08a9867d0d14f57a82ad5564d991588b39 - languageName: node - linkType: hard - -"safe-buffer@npm:5.2.1, safe-buffer@npm:^5.0.1, safe-buffer@npm:~5.2.0": - version: 5.2.1 - resolution: "safe-buffer@npm:5.2.1" - checksum: 10c0/6501914237c0a86e9675d4e51d89ca3c21ffd6a31642efeba25ad65720bce6921c9e7e974e5be91a786b25aa058b5303285d3c15dbabf983a919f5f630d349f3 - languageName: node - linkType: hard - -"safe-stable-stringify@npm:^2.3.1": - version: 2.4.3 - resolution: "safe-stable-stringify@npm:2.4.3" - checksum: 10c0/81dede06b8f2ae794efd868b1e281e3c9000e57b39801c6c162267eb9efda17bd7a9eafa7379e1f1cacd528d4ced7c80d7460ad26f62ada7c9e01dec61b2e768 - languageName: node - linkType: hard - -"safer-buffer@npm:>= 2.1.2 < 3, safer-buffer@npm:>= 2.1.2 < 3.0.0": - version: 2.1.2 - resolution: "safer-buffer@npm:2.1.2" - checksum: 10c0/7e3c8b2e88a1841c9671094bbaeebd94448111dd90a81a1f606f3f67708a6ec57763b3b47f06da09fc6054193e0e6709e77325415dc8422b04497a8070fa02d4 - languageName: node - linkType: hard - -"semver@npm:^6.0.0, semver@npm:^6.3.0, semver@npm:^6.3.1": - version: 6.3.1 - resolution: "semver@npm:6.3.1" - bin: - semver: bin/semver.js - checksum: 10c0/e3d79b609071caa78bcb6ce2ad81c7966a46a7431d9d58b8800cfa9cb6a63699b3899a0e4bcce36167a284578212d9ae6942b6929ba4aa5015c079a67751d42d - languageName: node - linkType: hard - -"semver@npm:^7.3.5, semver@npm:^7.3.7, semver@npm:^7.3.8, semver@npm:^7.5.3, semver@npm:^7.5.4": - version: 7.6.2 - resolution: "semver@npm:7.6.2" - bin: - semver: bin/semver.js - checksum: 10c0/97d3441e97ace8be4b1976433d1c32658f6afaff09f143e52c593bae7eef33de19e3e369c88bd985ce1042c6f441c80c6803078d1de2a9988080b66684cbb30c - languageName: node - linkType: hard - -"send@npm:0.18.0": - version: 0.18.0 - resolution: "send@npm:0.18.0" - dependencies: - debug: "npm:2.6.9" - depd: "npm:2.0.0" - destroy: "npm:1.2.0" - encodeurl: "npm:~1.0.2" - escape-html: "npm:~1.0.3" - etag: "npm:~1.8.1" - fresh: "npm:0.5.2" - http-errors: "npm:2.0.0" - mime: "npm:1.6.0" - ms: "npm:2.1.3" - on-finished: "npm:2.4.1" - range-parser: "npm:~1.2.1" - statuses: "npm:2.0.1" - checksum: 10c0/0eb134d6a51fc13bbcb976a1f4214ea1e33f242fae046efc311e80aff66c7a43603e26a79d9d06670283a13000e51be6e0a2cb80ff0942eaf9f1cd30b7ae736a - languageName: node - linkType: hard - -"send@npm:0.19.0": - version: 0.19.0 - resolution: "send@npm:0.19.0" - dependencies: - debug: "npm:2.6.9" - depd: "npm:2.0.0" - destroy: "npm:1.2.0" - encodeurl: "npm:~1.0.2" - escape-html: "npm:~1.0.3" - etag: "npm:~1.8.1" - fresh: "npm:0.5.2" - http-errors: "npm:2.0.0" - mime: "npm:1.6.0" - ms: "npm:2.1.3" - on-finished: "npm:2.4.1" - range-parser: "npm:~1.2.1" - statuses: "npm:2.0.1" - checksum: 10c0/ea3f8a67a8f0be3d6bf9080f0baed6d2c51d11d4f7b4470de96a5029c598a7011c497511ccc28968b70ef05508675cebff27da9151dd2ceadd60be4e6cf845e3 - languageName: node - linkType: hard - -"serve-static@npm:1.16.0": - version: 1.16.0 - resolution: "serve-static@npm:1.16.0" - dependencies: - encodeurl: "npm:~1.0.2" - escape-html: "npm:~1.0.3" - parseurl: "npm:~1.3.3" - send: "npm:0.18.0" - checksum: 10c0/d7a5beca08cc55f92998d8b87c111dd842d642404231c90c11f504f9650935da4599c13256747b0a988442a59851343271fe8e1946e03e92cd79c447b5f3ae01 - languageName: node - linkType: hard - -"set-blocking@npm:^2.0.0": - version: 2.0.0 - resolution: "set-blocking@npm:2.0.0" - checksum: 10c0/9f8c1b2d800800d0b589de1477c753492de5c1548d4ade52f57f1d1f5e04af5481554d75ce5e5c43d4004b80a3eb714398d6907027dc0534177b7539119f4454 - languageName: node - linkType: hard - -"set-function-length@npm:^1.2.1": - version: 1.2.2 - resolution: "set-function-length@npm:1.2.2" - dependencies: - define-data-property: "npm:^1.1.4" - es-errors: "npm:^1.3.0" - function-bind: "npm:^1.1.2" - get-intrinsic: "npm:^1.2.4" - gopd: "npm:^1.0.1" - has-property-descriptors: "npm:^1.0.2" - checksum: 10c0/82850e62f412a258b71e123d4ed3873fa9377c216809551192bb6769329340176f109c2eeae8c22a8d386c76739855f78e8716515c818bcaef384b51110f0f3c - languageName: node - linkType: hard - -"setprototypeof@npm:1.2.0": - version: 1.2.0 - resolution: "setprototypeof@npm:1.2.0" - checksum: 10c0/68733173026766fa0d9ecaeb07f0483f4c2dc70ca376b3b7c40b7cda909f94b0918f6c5ad5ce27a9160bdfb475efaa9d5e705a11d8eaae18f9835d20976028bc - languageName: node - linkType: hard - -"shebang-command@npm:^2.0.0": - version: 2.0.0 - resolution: "shebang-command@npm:2.0.0" - dependencies: - shebang-regex: "npm:^3.0.0" - checksum: 10c0/a41692e7d89a553ef21d324a5cceb5f686d1f3c040759c50aab69688634688c5c327f26f3ecf7001ebfd78c01f3c7c0a11a7c8bfd0a8bc9f6240d4f40b224e4e - languageName: node - linkType: hard - -"shebang-regex@npm:^3.0.0": - version: 3.0.0 - resolution: "shebang-regex@npm:3.0.0" - checksum: 10c0/1dbed0726dd0e1152a92696c76c7f06084eb32a90f0528d11acd764043aacf76994b2fb30aa1291a21bd019d6699164d048286309a278855ee7bec06cf6fb690 - languageName: node - linkType: hard - -"side-channel@npm:^1.0.4, side-channel@npm:^1.0.6": - version: 1.0.6 - resolution: "side-channel@npm:1.0.6" - dependencies: - call-bind: "npm:^1.0.7" - es-errors: "npm:^1.3.0" - get-intrinsic: "npm:^1.2.4" - object-inspect: "npm:^1.13.1" - checksum: 10c0/d2afd163dc733cc0a39aa6f7e39bf0c436293510dbccbff446733daeaf295857dbccf94297092ec8c53e2503acac30f0b78830876f0485991d62a90e9cad305f - languageName: node - linkType: hard - -"signal-exit@npm:^3.0.0, signal-exit@npm:^3.0.3, signal-exit@npm:^3.0.7": - version: 3.0.7 - resolution: "signal-exit@npm:3.0.7" - checksum: 10c0/25d272fa73e146048565e08f3309d5b942c1979a6f4a58a8c59d5fa299728e9c2fcd1a759ec870863b1fd38653670240cd420dad2ad9330c71f36608a6a1c912 - languageName: node - linkType: hard - -"signal-exit@npm:^4.0.1": - version: 4.1.0 - resolution: "signal-exit@npm:4.1.0" - checksum: 10c0/41602dce540e46d599edba9d9860193398d135f7ff72cab629db5171516cfae628d21e7bfccde1bbfdf11c48726bc2a6d1a8fb8701125852fbfda7cf19c6aa83 - languageName: node - linkType: hard - -"simple-concat@npm:^1.0.0": - version: 1.0.1 - resolution: "simple-concat@npm:1.0.1" - checksum: 10c0/62f7508e674414008910b5397c1811941d457dfa0db4fd5aa7fa0409eb02c3609608dfcd7508cace75b3a0bf67a2a77990711e32cd213d2c76f4fd12ee86d776 - languageName: node - linkType: hard - -"simple-get@npm:^4.0.0": - version: 4.0.1 - resolution: "simple-get@npm:4.0.1" - dependencies: - decompress-response: "npm:^6.0.0" - once: "npm:^1.3.1" - simple-concat: "npm:^1.0.0" - checksum: 10c0/b0649a581dbca741babb960423248899203165769747142033479a7dc5e77d7b0fced0253c731cd57cf21e31e4d77c9157c3069f4448d558ebc96cf9e1eebcf0 - languageName: node - linkType: hard - -"simple-swizzle@npm:^0.2.2": - version: 0.2.2 - resolution: "simple-swizzle@npm:0.2.2" - dependencies: - is-arrayish: "npm:^0.3.1" - checksum: 10c0/df5e4662a8c750bdba69af4e8263c5d96fe4cd0f9fe4bdfa3cbdeb45d2e869dff640beaaeb1ef0e99db4d8d2ec92f85508c269f50c972174851bc1ae5bd64308 - languageName: node - linkType: hard - -"sisteransi@npm:^1.0.5": - version: 1.0.5 - resolution: "sisteransi@npm:1.0.5" - checksum: 10c0/230ac975cca485b7f6fe2b96a711aa62a6a26ead3e6fb8ba17c5a00d61b8bed0d7adc21f5626b70d7c33c62ff4e63933017a6462942c719d1980bb0b1207ad46 - languageName: node - linkType: hard - -"slash@npm:^3.0.0": - version: 3.0.0 - resolution: "slash@npm:3.0.0" - checksum: 10c0/e18488c6a42bdfd4ac5be85b2ced3ccd0224773baae6ad42cfbb9ec74fc07f9fa8396bd35ee638084ead7a2a0818eb5e7151111544d4731ce843019dab4be47b - languageName: node - linkType: hard - -"slug@npm:^8.2.2": - version: 8.2.3 - resolution: "slug@npm:8.2.3" - bin: - slug: cli.js - checksum: 10c0/82499b57e5d2f8425e5fa366da86cf911b0d60c2d3143cadd89039fb6c5b8a3d340cd2fe26f4c85b62a6bdaa64327da5e7554ddbda176b58ee56ad932bfa3708 - languageName: node - linkType: hard - -"smart-buffer@npm:^4.2.0": - version: 4.2.0 - resolution: "smart-buffer@npm:4.2.0" - checksum: 10c0/a16775323e1404dd43fabafe7460be13a471e021637bc7889468eb45ce6a6b207261f454e4e530a19500cc962c4cc5348583520843b363f4193cee5c00e1e539 - languageName: node - linkType: hard - -"socks-proxy-agent@npm:^8.0.3": - version: 8.0.3 - resolution: "socks-proxy-agent@npm:8.0.3" - dependencies: - agent-base: "npm:^7.1.1" - debug: "npm:^4.3.4" - socks: "npm:^2.7.1" - checksum: 10c0/4950529affd8ccd6951575e21c1b7be8531b24d924aa4df3ee32df506af34b618c4e50d261f4cc603f1bfd8d426915b7d629966c8ce45b05fb5ad8c8b9a6459d - languageName: node - linkType: hard - -"socks@npm:^2.7.1": - version: 2.8.3 - resolution: "socks@npm:2.8.3" - dependencies: - ip-address: "npm:^9.0.5" - smart-buffer: "npm:^4.2.0" - checksum: 10c0/d54a52bf9325165770b674a67241143a3d8b4e4c8884560c4e0e078aace2a728dffc7f70150660f51b85797c4e1a3b82f9b7aa25e0a0ceae1a243365da5c51a7 - languageName: node - linkType: hard - -"source-map-support@npm:0.5.13": - version: 0.5.13 - resolution: "source-map-support@npm:0.5.13" - dependencies: - buffer-from: "npm:^1.0.0" - source-map: "npm:^0.6.0" - checksum: 10c0/137539f8c453fa0f496ea42049ab5da4569f96781f6ac8e5bfda26937be9494f4e8891f523c5f98f0e85f71b35d74127a00c46f83f6a4f54672b58d53202565e - languageName: node - linkType: hard - -"source-map@npm:^0.6.0, source-map@npm:^0.6.1": - version: 0.6.1 - resolution: "source-map@npm:0.6.1" - checksum: 10c0/ab55398007c5e5532957cb0beee2368529618ac0ab372d789806f5718123cc4367d57de3904b4e6a4170eb5a0b0f41373066d02ca0735a0c4d75c7d328d3e011 - languageName: node - linkType: hard - -"sprintf-js@npm:^1.1.3": - version: 1.1.3 - resolution: "sprintf-js@npm:1.1.3" - checksum: 10c0/09270dc4f30d479e666aee820eacd9e464215cdff53848b443964202bf4051490538e5dd1b42e1a65cf7296916ca17640aebf63dae9812749c7542ee5f288dec - languageName: node - linkType: hard - -"sprintf-js@npm:~1.0.2": - version: 1.0.3 - resolution: "sprintf-js@npm:1.0.3" - checksum: 10c0/ecadcfe4c771890140da5023d43e190b7566d9cf8b2d238600f31bec0fc653f328da4450eb04bd59a431771a8e9cc0e118f0aa3974b683a4981b4e07abc2a5bb - languageName: node - linkType: hard - -"ssri@npm:^10.0.0": - version: 10.0.6 - resolution: "ssri@npm:10.0.6" - dependencies: - minipass: "npm:^7.0.3" - checksum: 10c0/e5a1e23a4057a86a97971465418f22ea89bd439ac36ade88812dd920e4e61873e8abd6a9b72a03a67ef50faa00a2daf1ab745c5a15b46d03e0544a0296354227 - languageName: node - linkType: hard - -"stack-trace@npm:0.0.x": - version: 0.0.10 - resolution: "stack-trace@npm:0.0.10" - checksum: 10c0/9ff3dabfad4049b635a85456f927a075c9d0c210e3ea336412d18220b2a86cbb9b13ec46d6c37b70a302a4ea4d49e30e5d4944dd60ae784073f1cde778ac8f4b - languageName: node - linkType: hard - -"stack-utils@npm:^2.0.3": - version: 2.0.6 - resolution: "stack-utils@npm:2.0.6" - dependencies: - escape-string-regexp: "npm:^2.0.0" - checksum: 10c0/651c9f87667e077584bbe848acaecc6049bc71979f1e9a46c7b920cad4431c388df0f51b8ad7cfd6eed3db97a2878d0fc8b3122979439ea8bac29c61c95eec8a - languageName: node - linkType: hard - -"statuses@npm:2.0.1": - version: 2.0.1 - resolution: "statuses@npm:2.0.1" - checksum: 10c0/34378b207a1620a24804ce8b5d230fea0c279f00b18a7209646d5d47e419d1cc23e7cbf33a25a1e51ac38973dc2ac2e1e9c647a8e481ef365f77668d72becfd0 - languageName: node - linkType: hard - -"string-length@npm:^4.0.1": - version: 4.0.2 - resolution: "string-length@npm:4.0.2" - dependencies: - char-regex: "npm:^1.0.2" - strip-ansi: "npm:^6.0.0" - checksum: 10c0/1cd77409c3d7db7bc59406f6bcc9ef0783671dcbabb23597a1177c166906ef2ee7c8290f78cae73a8aec858768f189d2cb417797df5e15ec4eb5e16b3346340c - languageName: node - linkType: hard - -"string-width-cjs@npm:string-width@^4.2.0, string-width@npm:^1.0.2 || 2 || 3 || 4, string-width@npm:^4.1.0, string-width@npm:^4.2.0, string-width@npm:^4.2.3": - version: 4.2.3 - resolution: "string-width@npm:4.2.3" - dependencies: - emoji-regex: "npm:^8.0.0" - is-fullwidth-code-point: "npm:^3.0.0" - strip-ansi: "npm:^6.0.1" - checksum: 10c0/1e525e92e5eae0afd7454086eed9c818ee84374bb80328fc41217ae72ff5f065ef1c9d7f72da41de40c75fa8bb3dee63d92373fd492c84260a552c636392a47b - languageName: node - linkType: hard - -"string-width@npm:^5.0.1, string-width@npm:^5.1.2": - version: 5.1.2 - resolution: "string-width@npm:5.1.2" - dependencies: - eastasianwidth: "npm:^0.2.0" - emoji-regex: "npm:^9.2.2" - strip-ansi: "npm:^7.0.1" - checksum: 10c0/ab9c4264443d35b8b923cbdd513a089a60de339216d3b0ed3be3ba57d6880e1a192b70ae17225f764d7adbf5994e9bb8df253a944736c15a0240eff553c678ca - languageName: node - linkType: hard - -"string_decoder@npm:^1.1.1": - version: 1.3.0 - resolution: "string_decoder@npm:1.3.0" - dependencies: - safe-buffer: "npm:~5.2.0" - checksum: 10c0/810614ddb030e271cd591935dcd5956b2410dd079d64ff92a1844d6b7588bf992b3e1b69b0f4d34a3e06e0bd73046ac646b5264c1987b20d0601f81ef35d731d - languageName: node - linkType: hard - -"strip-ansi-cjs@npm:strip-ansi@^6.0.1, strip-ansi@npm:^6.0.0, strip-ansi@npm:^6.0.1": - version: 6.0.1 - resolution: "strip-ansi@npm:6.0.1" - dependencies: - ansi-regex: "npm:^5.0.1" - checksum: 10c0/1ae5f212a126fe5b167707f716942490e3933085a5ff6c008ab97ab2f272c8025d3aa218b7bd6ab25729ca20cc81cddb252102f8751e13482a5199e873680952 - languageName: node - linkType: hard - -"strip-ansi@npm:^7.0.1": - version: 7.1.0 - resolution: "strip-ansi@npm:7.1.0" - dependencies: - ansi-regex: "npm:^6.0.1" - checksum: 10c0/a198c3762e8832505328cbf9e8c8381de14a4fa50a4f9b2160138158ea88c0f5549fb50cb13c651c3088f47e63a108b34622ec18c0499b6c8c3a5ddf6b305ac4 - languageName: node - linkType: hard - -"strip-bom@npm:^4.0.0": - version: 4.0.0 - resolution: "strip-bom@npm:4.0.0" - checksum: 10c0/26abad1172d6bc48985ab9a5f96c21e440f6e7e476686de49be813b5a59b3566dccb5c525b831ec54fe348283b47f3ffb8e080bc3f965fde12e84df23f6bb7ef - languageName: node - linkType: hard - -"strip-final-newline@npm:^2.0.0": - version: 2.0.0 - resolution: "strip-final-newline@npm:2.0.0" - checksum: 10c0/bddf8ccd47acd85c0e09ad7375409d81653f645fda13227a9d459642277c253d877b68f2e5e4d819fe75733b0e626bac7e954c04f3236f6d196f79c94fa4a96f - languageName: node - linkType: hard - -"strip-json-comments@npm:^3.1.1": - version: 3.1.1 - resolution: "strip-json-comments@npm:3.1.1" - checksum: 10c0/9681a6257b925a7fa0f285851c0e613cc934a50661fa7bb41ca9cbbff89686bb4a0ee366e6ecedc4daafd01e83eee0720111ab294366fe7c185e935475ebcecd - languageName: node - linkType: hard - -"strip-json-comments@npm:~2.0.1": - version: 2.0.1 - resolution: "strip-json-comments@npm:2.0.1" - checksum: 10c0/b509231cbdee45064ff4f9fd73609e2bcc4e84a4d508e9dd0f31f70356473fde18abfb5838c17d56fb236f5a06b102ef115438de0600b749e818a35fbbc48c43 - languageName: node - linkType: hard - -"superagent@npm:^8.1.2": - version: 8.1.2 - resolution: "superagent@npm:8.1.2" - dependencies: - component-emitter: "npm:^1.3.0" - cookiejar: "npm:^2.1.4" - debug: "npm:^4.3.4" - fast-safe-stringify: "npm:^2.1.1" - form-data: "npm:^4.0.0" - formidable: "npm:^2.1.2" - methods: "npm:^1.1.2" - mime: "npm:2.6.0" - qs: "npm:^6.11.0" - semver: "npm:^7.3.8" - checksum: 10c0/016416fc9c3d3a04fb648bc0efb3d3d5c9d96da00de47e4a625d9976d28c6c37ab0a7f185f2c3ec6d653ee8bb522f70fba0c1072aea7774341a6c0269a9fa77f - languageName: node - linkType: hard - -"supertest@npm:^6.3.1": - version: 6.3.4 - resolution: "supertest@npm:6.3.4" - dependencies: - methods: "npm:^1.1.2" - superagent: "npm:^8.1.2" - checksum: 10c0/f8c0b6c73b5e87da31feee6ccb36e7af766a438513cad89d6907f22c97edd83b1e765b4c8de955d5f7af4bca5fd0aaf9149ff48e21567dd290b326a8633af2a7 - languageName: node - linkType: hard - -"supports-color@npm:^5.3.0": - version: 5.5.0 - resolution: "supports-color@npm:5.5.0" - dependencies: - has-flag: "npm:^3.0.0" - checksum: 10c0/6ae5ff319bfbb021f8a86da8ea1f8db52fac8bd4d499492e30ec17095b58af11f0c55f8577390a749b1c4dde691b6a0315dab78f5f54c9b3d83f8fb5905c1c05 - languageName: node - linkType: hard - -"supports-color@npm:^7.1.0": - version: 7.2.0 - resolution: "supports-color@npm:7.2.0" - dependencies: - has-flag: "npm:^4.0.0" - checksum: 10c0/afb4c88521b8b136b5f5f95160c98dee7243dc79d5432db7efc27efb219385bbc7d9427398e43dd6cc730a0f87d5085ce1652af7efbe391327bc0a7d0f7fc124 - languageName: node - linkType: hard - -"supports-color@npm:^8.0.0": - version: 8.1.1 - resolution: "supports-color@npm:8.1.1" - dependencies: - has-flag: "npm:^4.0.0" - checksum: 10c0/ea1d3c275dd604c974670f63943ed9bd83623edc102430c05adb8efc56ba492746b6e95386e7831b872ec3807fd89dd8eb43f735195f37b5ec343e4234cc7e89 - languageName: node - linkType: hard - -"supports-preserve-symlinks-flag@npm:^1.0.0": - version: 1.0.0 - resolution: "supports-preserve-symlinks-flag@npm:1.0.0" - checksum: 10c0/6c4032340701a9950865f7ae8ef38578d8d7053f5e10518076e6554a9381fa91bd9c6850193695c141f32b21f979c985db07265a758867bac95de05f7d8aeb39 - languageName: node - linkType: hard - -"tar-fs@npm:^2.0.0": - version: 2.1.1 - resolution: "tar-fs@npm:2.1.1" - dependencies: - chownr: "npm:^1.1.1" - mkdirp-classic: "npm:^0.5.2" - pump: "npm:^3.0.0" - tar-stream: "npm:^2.1.4" - checksum: 10c0/871d26a934bfb7beeae4c4d8a09689f530b565f79bd0cf489823ff0efa3705da01278160da10bb006d1a793fa0425cf316cec029b32a9159eacbeaff4965fb6d - languageName: node - linkType: hard - -"tar-stream@npm:^2.1.4": - version: 2.2.0 - resolution: "tar-stream@npm:2.2.0" - dependencies: - bl: "npm:^4.0.3" - end-of-stream: "npm:^1.4.1" - fs-constants: "npm:^1.0.0" - inherits: "npm:^2.0.3" - readable-stream: "npm:^3.1.1" - checksum: 10c0/2f4c910b3ee7196502e1ff015a7ba321ec6ea837667220d7bcb8d0852d51cb04b87f7ae471008a6fb8f5b1a1b5078f62f3a82d30c706f20ada1238ac797e7692 - languageName: node - linkType: hard - -"tar@npm:^6.1.11, tar@npm:^6.1.2": - version: 6.2.1 - resolution: "tar@npm:6.2.1" - dependencies: - chownr: "npm:^2.0.0" - fs-minipass: "npm:^2.0.0" - minipass: "npm:^5.0.0" - minizlib: "npm:^2.1.1" - mkdirp: "npm:^1.0.3" - yallist: "npm:^4.0.0" - checksum: 10c0/a5eca3eb50bc11552d453488344e6507156b9193efd7635e98e867fab275d527af53d8866e2370cd09dfe74378a18111622ace35af6a608e5223a7d27fe99537 - languageName: node - linkType: hard - -"test-exclude@npm:^6.0.0": - version: 6.0.0 - resolution: "test-exclude@npm:6.0.0" - dependencies: - "@istanbuljs/schema": "npm:^0.1.2" - glob: "npm:^7.1.4" - minimatch: "npm:^3.0.4" - checksum: 10c0/019d33d81adff3f9f1bfcff18125fb2d3c65564f437d9be539270ee74b994986abb8260c7c2ce90e8f30162178b09dbbce33c6389273afac4f36069c48521f57 - languageName: node - linkType: hard - -"text-hex@npm:1.0.x": - version: 1.0.0 - resolution: "text-hex@npm:1.0.0" - checksum: 10c0/57d8d320d92c79d7c03ffb8339b825bb9637c2cbccf14304309f51d8950015c44464b6fd1b6820a3d4821241c68825634f09f5a2d9d501e84f7c6fd14376860d - languageName: node - linkType: hard - -"text-table@npm:^0.2.0": - version: 0.2.0 - resolution: "text-table@npm:0.2.0" - checksum: 10c0/02805740c12851ea5982686810702e2f14369a5f4c5c40a836821e3eefc65ffeec3131ba324692a37608294b0fd8c1e55a2dd571ffed4909822787668ddbee5c - languageName: node - linkType: hard - -"tmpl@npm:1.0.5": - version: 1.0.5 - resolution: "tmpl@npm:1.0.5" - checksum: 10c0/f935537799c2d1922cb5d6d3805f594388f75338fe7a4a9dac41504dd539704ca4db45b883b52e7b0aa5b2fd5ddadb1452bf95cd23a69da2f793a843f9451cc9 - languageName: node - linkType: hard - -"to-fast-properties@npm:^2.0.0": - version: 2.0.0 - resolution: "to-fast-properties@npm:2.0.0" - checksum: 10c0/b214d21dbfb4bce3452b6244b336806ffea9c05297148d32ebb428d5c43ce7545bdfc65a1ceb58c9ef4376a65c0cb2854d645f33961658b3e3b4f84910ddcdd7 - languageName: node - linkType: hard - -"to-regex-range@npm:^5.0.1": - version: 5.0.1 - resolution: "to-regex-range@npm:5.0.1" - dependencies: - is-number: "npm:^7.0.0" - checksum: 10c0/487988b0a19c654ff3e1961b87f471702e708fa8a8dd02a298ef16da7206692e8552a0250e8b3e8759270f62e9d8314616f6da274734d3b558b1fc7b7724e892 - languageName: node - linkType: hard - -"toidentifier@npm:1.0.1": - version: 1.0.1 - resolution: "toidentifier@npm:1.0.1" - checksum: 10c0/93937279934bd66cc3270016dd8d0afec14fb7c94a05c72dc57321f8bd1fa97e5bea6d1f7c89e728d077ca31ea125b78320a616a6c6cd0e6b9cb94cb864381c1 - languageName: node - linkType: hard - -"tr46@npm:~0.0.3": - version: 0.0.3 - resolution: "tr46@npm:0.0.3" - checksum: 10c0/047cb209a6b60c742f05c9d3ace8fa510bff609995c129a37ace03476a9b12db4dbf975e74600830ef0796e18882b2381fb5fb1f6b4f96b832c374de3ab91a11 - languageName: node - linkType: hard - -"triple-beam@npm:^1.3.0": - version: 1.4.1 - resolution: "triple-beam@npm:1.4.1" - checksum: 10c0/4bf1db71e14fe3ff1c3adbe3c302f1fdb553b74d7591a37323a7badb32dc8e9c290738996cbb64f8b10dc5a3833645b5d8c26221aaaaa12e50d1251c9aba2fea - languageName: node - linkType: hard - -"tslib@npm:^1.8.1": - version: 1.14.1 - resolution: "tslib@npm:1.14.1" - checksum: 10c0/69ae09c49eea644bc5ebe1bca4fa4cc2c82b7b3e02f43b84bd891504edf66dbc6b2ec0eef31a957042de2269139e4acff911e6d186a258fb14069cd7f6febce2 - languageName: node - linkType: hard - -"tsutils@npm:^3.21.0": - version: 3.21.0 - resolution: "tsutils@npm:3.21.0" - dependencies: - tslib: "npm:^1.8.1" - peerDependencies: - typescript: ">=2.8.0 || >= 3.2.0-dev || >= 3.3.0-dev || >= 3.4.0-dev || >= 3.5.0-dev || >= 3.6.0-dev || >= 3.6.0-beta || >= 3.7.0-dev || >= 3.7.0-beta" - checksum: 10c0/02f19e458ec78ead8fffbf711f834ad8ecd2cc6ade4ec0320790713dccc0a412b99e7fd907c4cda2a1dc602c75db6f12e0108e87a5afad4b2f9e90a24cabd5a2 - languageName: node - linkType: hard - -"tunnel-agent@npm:^0.6.0": - version: 0.6.0 - resolution: "tunnel-agent@npm:0.6.0" - dependencies: - safe-buffer: "npm:^5.0.1" - checksum: 10c0/4c7a1b813e7beae66fdbf567a65ec6d46313643753d0beefb3c7973d66fcec3a1e7f39759f0a0b4465883499c6dc8b0750ab8b287399af2e583823e40410a17a - languageName: node - linkType: hard - -"type-check@npm:^0.4.0, type-check@npm:~0.4.0": - version: 0.4.0 - resolution: "type-check@npm:0.4.0" - dependencies: - prelude-ls: "npm:^1.2.1" - checksum: 10c0/7b3fd0ed43891e2080bf0c5c504b418fbb3e5c7b9708d3d015037ba2e6323a28152ec163bcb65212741fa5d2022e3075ac3c76440dbd344c9035f818e8ecee58 - languageName: node - linkType: hard - -"type-detect@npm:4.0.8": - version: 4.0.8 - resolution: "type-detect@npm:4.0.8" - checksum: 10c0/8fb9a51d3f365a7de84ab7f73b653534b61b622aa6800aecdb0f1095a4a646d3f5eb295322127b6573db7982afcd40ab492d038cf825a42093a58b1e1353e0bd - languageName: node - linkType: hard - -"type-fest@npm:^0.20.2": - version: 0.20.2 - resolution: "type-fest@npm:0.20.2" - checksum: 10c0/dea9df45ea1f0aaa4e2d3bed3f9a0bfe9e5b2592bddb92eb1bf06e50bcf98dbb78189668cd8bc31a0511d3fc25539b4cd5c704497e53e93e2d40ca764b10bfc3 - languageName: node - linkType: hard - -"type-fest@npm:^0.21.3": - version: 0.21.3 - resolution: "type-fest@npm:0.21.3" - checksum: 10c0/902bd57bfa30d51d4779b641c2bc403cdf1371fb9c91d3c058b0133694fcfdb817aef07a47f40faf79039eecbaa39ee9d3c532deff244f3a19ce68cea71a61e8 - languageName: node - linkType: hard - -"type-is@npm:~1.6.18": - version: 1.6.18 - resolution: "type-is@npm:1.6.18" - dependencies: - media-typer: "npm:0.3.0" - mime-types: "npm:~2.1.24" - checksum: 10c0/a23daeb538591b7efbd61ecf06b6feb2501b683ffdc9a19c74ef5baba362b4347e42f1b4ed81f5882a8c96a3bfff7f93ce3ffaf0cbbc879b532b04c97a55db9d - languageName: node - linkType: hard - -"typescript@npm:^4.9.5": - version: 4.9.5 - resolution: "typescript@npm:4.9.5" - bin: - tsc: bin/tsc - tsserver: bin/tsserver - checksum: 10c0/5f6cad2e728a8a063521328e612d7876e12f0d8a8390d3b3aaa452a6a65e24e9ac8ea22beb72a924fd96ea0a49ea63bb4e251fb922b12eedfb7f7a26475e5c56 - languageName: node - linkType: hard - -"typescript@patch:typescript@npm%3A^4.9.5#optional!builtin<compat/typescript>": - version: 4.9.5 - resolution: "typescript@patch:typescript@npm%3A4.9.5#optional!builtin<compat/typescript>::version=4.9.5&hash=289587" - bin: - tsc: bin/tsc - tsserver: bin/tsserver - checksum: 10c0/e3333f887c6829dfe0ab6c1dbe0dd1e3e2aeb56c66460cb85c5440c566f900c833d370ca34eb47558c0c69e78ced4bfe09b8f4f98b6de7afed9b84b8d1dd06a1 - languageName: node - linkType: hard - -"undici-types@npm:~5.26.4": - version: 5.26.5 - resolution: "undici-types@npm:5.26.5" - checksum: 10c0/bb673d7876c2d411b6eb6c560e0c571eef4a01c1c19925175d16e3a30c4c428181fb8d7ae802a261f283e4166a0ac435e2f505743aa9e45d893f9a3df017b501 - languageName: node - linkType: hard - -"unique-filename@npm:^3.0.0": - version: 3.0.0 - resolution: "unique-filename@npm:3.0.0" - dependencies: - unique-slug: "npm:^4.0.0" - checksum: 10c0/6363e40b2fa758eb5ec5e21b3c7fb83e5da8dcfbd866cc0c199d5534c42f03b9ea9ab069769cc388e1d7ab93b4eeef28ef506ab5f18d910ef29617715101884f - languageName: node - linkType: hard - -"unique-slug@npm:^4.0.0": - version: 4.0.0 - resolution: "unique-slug@npm:4.0.0" - dependencies: - imurmurhash: "npm:^0.1.4" - checksum: 10c0/cb811d9d54eb5821b81b18205750be84cb015c20a4a44280794e915f5a0a70223ce39066781a354e872df3572e8155c228f43ff0cce94c7cbf4da2cc7cbdd635 - languageName: node - linkType: hard - -"unpipe@npm:1.0.0, unpipe@npm:~1.0.0": - version: 1.0.0 - resolution: "unpipe@npm:1.0.0" - checksum: 10c0/193400255bd48968e5c5383730344fbb4fa114cdedfab26e329e50dd2d81b134244bb8a72c6ac1b10ab0281a58b363d06405632c9d49ca9dfd5e90cbd7d0f32c - languageName: node - linkType: hard - -"update-browserslist-db@npm:^1.0.16": - version: 1.0.16 - resolution: "update-browserslist-db@npm:1.0.16" - dependencies: - escalade: "npm:^3.1.2" - picocolors: "npm:^1.0.1" - peerDependencies: - browserslist: ">= 4.21.0" - bin: - update-browserslist-db: cli.js - checksum: 10c0/5995399fc202adbb51567e4810e146cdf7af630a92cc969365a099150cb00597e425cc14987ca7080b09a4d0cfd2a3de53fbe72eebff171aed7f9bb81f9bf405 - languageName: node - linkType: hard - -"uri-js@npm:^4.2.2": - version: 4.4.1 - resolution: "uri-js@npm:4.4.1" - dependencies: - punycode: "npm:^2.1.0" - checksum: 10c0/4ef57b45aa820d7ac6496e9208559986c665e49447cb072744c13b66925a362d96dd5a46c4530a6b8e203e5db5fe849369444440cb22ecfc26c679359e5dfa3c - languageName: node - linkType: hard - -"util-deprecate@npm:^1.0.1": - version: 1.0.2 - resolution: "util-deprecate@npm:1.0.2" - checksum: 10c0/41a5bdd214df2f6c3ecf8622745e4a366c4adced864bc3c833739791aeeeb1838119af7daed4ba36428114b5c67dcda034a79c882e97e43c03e66a4dd7389942 - languageName: node - linkType: hard - -"utils-merge@npm:1.0.1": - version: 1.0.1 - resolution: "utils-merge@npm:1.0.1" - checksum: 10c0/02ba649de1b7ca8854bfe20a82f1dfbdda3fb57a22ab4a8972a63a34553cf7aa51bc9081cf7e001b035b88186d23689d69e71b510e610a09a4c66f68aa95b672 - languageName: node - linkType: hard - -"uuid@npm:^9.0.0": - version: 9.0.1 - resolution: "uuid@npm:9.0.1" - bin: - uuid: dist/bin/uuid - checksum: 10c0/1607dd32ac7fc22f2d8f77051e6a64845c9bce5cd3dd8aa0070c074ec73e666a1f63c7b4e0f4bf2bc8b9d59dc85a15e17807446d9d2b17c8485fbc2147b27f9b - languageName: node - linkType: hard - -"v8-to-istanbul@npm:^9.0.1": - version: 9.2.0 - resolution: "v8-to-istanbul@npm:9.2.0" - dependencies: - "@jridgewell/trace-mapping": "npm:^0.3.12" - "@types/istanbul-lib-coverage": "npm:^2.0.1" - convert-source-map: "npm:^2.0.0" - checksum: 10c0/e691ba4dd0dea4a884e52c37dbda30cce6f9eeafe9b26721e449429c6bb0f4b6d1e33fabe7711d0f67f7a34c3bfd56c873f7375bba0b1534e6a2843ce99550e5 - languageName: node - linkType: hard - -"vary@npm:^1, vary@npm:~1.1.2": - version: 1.1.2 - resolution: "vary@npm:1.1.2" - checksum: 10c0/f15d588d79f3675135ba783c91a4083dcd290a2a5be9fcb6514220a1634e23df116847b1cc51f66bfb0644cf9353b2abb7815ae499bab06e46dd33c1a6bf1f4f - languageName: node - linkType: hard - -"walker@npm:^1.0.8": - version: 1.0.8 - resolution: "walker@npm:1.0.8" - dependencies: - makeerror: "npm:1.0.12" - checksum: 10c0/a17e037bccd3ca8a25a80cb850903facdfed0de4864bd8728f1782370715d679fa72e0a0f5da7c1c1379365159901e5935f35be531229da53bbfc0efdabdb48e - languageName: node - linkType: hard - -"webidl-conversions@npm:^3.0.0": - version: 3.0.1 - resolution: "webidl-conversions@npm:3.0.1" - checksum: 10c0/5612d5f3e54760a797052eb4927f0ddc01383550f542ccd33d5238cfd65aeed392a45ad38364970d0a0f4fea32e1f4d231b3d8dac4a3bdd385e5cf802ae097db - languageName: node - linkType: hard - -"whatwg-url@npm:^5.0.0": - version: 5.0.0 - resolution: "whatwg-url@npm:5.0.0" - dependencies: - tr46: "npm:~0.0.3" - webidl-conversions: "npm:^3.0.0" - checksum: 10c0/1588bed84d10b72d5eec1d0faa0722ba1962f1821e7539c535558fb5398d223b0c50d8acab950b8c488b4ba69043fd833cc2697056b167d8ad46fac3995a55d5 - languageName: node - linkType: hard - -"which@npm:^2.0.1": - version: 2.0.2 - resolution: "which@npm:2.0.2" - dependencies: - isexe: "npm:^2.0.0" - bin: - node-which: ./bin/node-which - checksum: 10c0/66522872a768b60c2a65a57e8ad184e5372f5b6a9ca6d5f033d4b0dc98aff63995655a7503b9c0a2598936f532120e81dd8cc155e2e92ed662a2b9377cc4374f - languageName: node - linkType: hard - -"which@npm:^4.0.0": - version: 4.0.0 - resolution: "which@npm:4.0.0" - dependencies: - isexe: "npm:^3.1.1" - bin: - node-which: bin/which.js - checksum: 10c0/449fa5c44ed120ccecfe18c433296a4978a7583bf2391c50abce13f76878d2476defde04d0f79db8165bdf432853c1f8389d0485ca6e8ebce3bbcded513d5e6a - languageName: node - linkType: hard - -"wide-align@npm:^1.1.2": - version: 1.1.5 - resolution: "wide-align@npm:1.1.5" - dependencies: - string-width: "npm:^1.0.2 || 2 || 3 || 4" - checksum: 10c0/1d9c2a3e36dfb09832f38e2e699c367ef190f96b82c71f809bc0822c306f5379df87bab47bed27ea99106d86447e50eb972d3c516c2f95782807a9d082fbea95 - languageName: node - linkType: hard - -"winston-transport@npm:^4.7.0": - version: 4.7.1 - resolution: "winston-transport@npm:4.7.1" - dependencies: - logform: "npm:^2.6.1" - readable-stream: "npm:^3.6.2" - triple-beam: "npm:^1.3.0" - checksum: 10c0/99b7b55cc2ef7f38988ab1717e7fd946c81b856b42a9530aef8ee725490ef2f2811f9cb06d63aa2f76a85fe99ae15b3bef10a54afde3be8b5059ce325e78481f - languageName: node - linkType: hard - -"winston@npm:^3.14.2": - version: 3.14.2 - resolution: "winston@npm:3.14.2" - dependencies: - "@colors/colors": "npm:^1.6.0" - "@dabh/diagnostics": "npm:^2.0.2" - async: "npm:^3.2.3" - is-stream: "npm:^2.0.0" - logform: "npm:^2.6.0" - one-time: "npm:^1.0.0" - readable-stream: "npm:^3.4.0" - safe-stable-stringify: "npm:^2.3.1" - stack-trace: "npm:0.0.x" - triple-beam: "npm:^1.3.0" - winston-transport: "npm:^4.7.0" - checksum: 10c0/3f8fe505ea18310982e60452f335dd2b22fdbc9b25839b6ad882971b2416d5adc94a1f1a46e24cb37d967ad01dfe5499adaf5e53575626b5ebb2a25ff30f4e1d - languageName: node - linkType: hard - -"word-wrap@npm:^1.2.5": - version: 1.2.5 - resolution: "word-wrap@npm:1.2.5" - checksum: 10c0/e0e4a1ca27599c92a6ca4c32260e8a92e8a44f4ef6ef93f803f8ed823f486e0889fc0b93be4db59c8d51b3064951d25e43d434e95dc8c960cc3a63d65d00ba20 - languageName: node - linkType: hard - -"wrap-ansi-cjs@npm:wrap-ansi@^7.0.0, wrap-ansi@npm:^7.0.0": - version: 7.0.0 - resolution: "wrap-ansi@npm:7.0.0" - dependencies: - ansi-styles: "npm:^4.0.0" - string-width: "npm:^4.1.0" - strip-ansi: "npm:^6.0.0" - checksum: 10c0/d15fc12c11e4cbc4044a552129ebc75ee3f57aa9c1958373a4db0292d72282f54373b536103987a4a7594db1ef6a4f10acf92978f79b98c49306a4b58c77d4da - languageName: node - linkType: hard - -"wrap-ansi@npm:^8.1.0": - version: 8.1.0 - resolution: "wrap-ansi@npm:8.1.0" - dependencies: - ansi-styles: "npm:^6.1.0" - string-width: "npm:^5.0.1" - strip-ansi: "npm:^7.0.1" - checksum: 10c0/138ff58a41d2f877eae87e3282c0630fc2789012fc1af4d6bd626eeb9a2f9a65ca92005e6e69a75c7b85a68479fe7443c7dbe1eb8fbaa681a4491364b7c55c60 - languageName: node - linkType: hard - -"wrappy@npm:1": - version: 1.0.2 - resolution: "wrappy@npm:1.0.2" - checksum: 10c0/56fece1a4018c6a6c8e28fbc88c87e0fbf4ea8fd64fc6c63b18f4acc4bd13e0ad2515189786dd2c30d3eec9663d70f4ecf699330002f8ccb547e4a18231fc9f0 - languageName: node - linkType: hard - -"write-file-atomic@npm:^4.0.2": - version: 4.0.2 - resolution: "write-file-atomic@npm:4.0.2" - dependencies: - imurmurhash: "npm:^0.1.4" - signal-exit: "npm:^3.0.7" - checksum: 10c0/a2c282c95ef5d8e1c27b335ae897b5eca00e85590d92a3fd69a437919b7b93ff36a69ea04145da55829d2164e724bc62202cdb5f4b208b425aba0807889375c7 - languageName: node - linkType: hard - -"y18n@npm:^5.0.5": - version: 5.0.8 - resolution: "y18n@npm:5.0.8" - checksum: 10c0/4df2842c36e468590c3691c894bc9cdbac41f520566e76e24f59401ba7d8b4811eb1e34524d57e54bc6d864bcb66baab7ffd9ca42bf1eda596618f9162b91249 - languageName: node - linkType: hard - -"yallist@npm:^3.0.2": - version: 3.1.1 - resolution: "yallist@npm:3.1.1" - checksum: 10c0/c66a5c46bc89af1625476f7f0f2ec3653c1a1791d2f9407cfb4c2ba812a1e1c9941416d71ba9719876530e3340a99925f697142989371b72d93b9ee628afd8c1 - languageName: node - linkType: hard - -"yallist@npm:^4.0.0": - version: 4.0.0 - resolution: "yallist@npm:4.0.0" - checksum: 10c0/2286b5e8dbfe22204ab66e2ef5cc9bbb1e55dfc873bbe0d568aa943eb255d131890dfd5bf243637273d31119b870f49c18fcde2c6ffbb7a7a092b870dc90625a - languageName: node - linkType: hard - -"yargs-parser@npm:^21.1.1": - version: 21.1.1 - resolution: "yargs-parser@npm:21.1.1" - checksum: 10c0/f84b5e48169479d2f402239c59f084cfd1c3acc197a05c59b98bab067452e6b3ea46d4dd8ba2985ba7b3d32a343d77df0debd6b343e5dae3da2aab2cdf5886b2 - languageName: node - linkType: hard - -"yargs@npm:^17.3.1": - version: 17.7.2 - resolution: "yargs@npm:17.7.2" - dependencies: - cliui: "npm:^8.0.1" - escalade: "npm:^3.1.1" - get-caller-file: "npm:^2.0.5" - require-directory: "npm:^2.1.1" - string-width: "npm:^4.2.3" - y18n: "npm:^5.0.5" - yargs-parser: "npm:^21.1.1" - checksum: 10c0/ccd7e723e61ad5965fffbb791366db689572b80cca80e0f96aad968dfff4156cd7cd1ad18607afe1046d8241e6fb2d6c08bf7fa7bfb5eaec818735d8feac8f05 - languageName: node - linkType: hard - -"yocto-queue@npm:^0.1.0": - version: 0.1.0 - resolution: "yocto-queue@npm:0.1.0" - checksum: 10c0/dceb44c28578b31641e13695d200d34ec4ab3966a5729814d5445b194933c096b7ced71494ce53a0e8820685d1d010df8b2422e5bf2cdea7e469d97ffbea306f - languageName: node - linkType: hard From 132af45568dbc03658bb28db179c31da4f3840d0 Mon Sep 17 00:00:00 2001 From: Mike Clark <mclarkgb@gmail.com> Date: Sat, 11 Jan 2025 12:54:23 +0000 Subject: [PATCH 54/55] fml --- packages/sync-server/package.json | 4 +- packages/sync-server/yarn.lock | 6481 +++++++++++++++++++++++++++++ yarn.lock | 8 +- 3 files changed, 6487 insertions(+), 6 deletions(-) create mode 100644 packages/sync-server/yarn.lock diff --git a/packages/sync-server/package.json b/packages/sync-server/package.json index 9f40c826446..36aa60ad32c 100644 --- a/packages/sync-server/package.json +++ b/packages/sync-server/package.json @@ -22,8 +22,8 @@ "health-check": "node src/scripts/health-check.js" }, "dependencies": { - "@actual-app/crdt": "*", - "@actual-app/web": "*", + "@actual-app/crdt": "2.1.0", + "@actual-app/web": "25.1.0", "bcrypt": "^5.1.1", "better-sqlite3": "^11.7.0", "body-parser": "^1.20.3", diff --git a/packages/sync-server/yarn.lock b/packages/sync-server/yarn.lock new file mode 100644 index 00000000000..54bcc6ef72a --- /dev/null +++ b/packages/sync-server/yarn.lock @@ -0,0 +1,6481 @@ +# This file is generated by running "yarn install" inside your project. +# Manual changes might be lost - proceed with caution! + +__metadata: + version: 8 + cacheKey: 10c0 + +"@actual-app/crdt@npm:2.1.0": + version: 2.1.0 + resolution: "@actual-app/crdt@npm:2.1.0" + dependencies: + google-protobuf: "npm:^3.12.0-rc.1" + murmurhash: "npm:^2.0.1" + uuid: "npm:^9.0.0" + checksum: 10c0/131050eb42218229eebe60a954ee55275380cff3b139a08b34e25f6056b621033f28a231ce6cc59022fdc80d475500fe70e5f134f9dc3250e37516e679922d5c + languageName: node + linkType: hard + +"@actual-app/web@npm:25.1.0": + version: 25.1.0 + resolution: "@actual-app/web@npm:25.1.0" + checksum: 10c0/8164184d7d0fe36591d996d4cf0e3fbd9ead1f3fc144f7326e4bd36f831d1a63f44ae5878585e125475334d2a8118e20d65e5fdccddd32f9b8cb656842da278c + languageName: node + linkType: hard + +"@ampproject/remapping@npm:^2.2.0": + version: 2.3.0 + resolution: "@ampproject/remapping@npm:2.3.0" + dependencies: + "@jridgewell/gen-mapping": "npm:^0.3.5" + "@jridgewell/trace-mapping": "npm:^0.3.24" + checksum: 10c0/81d63cca5443e0f0c72ae18b544cc28c7c0ec2cea46e7cb888bb0e0f411a1191d0d6b7af798d54e30777d8d1488b2ec0732aac2be342d3d7d3ffd271c6f489ed + languageName: node + linkType: hard + +"@babel/code-frame@npm:^7.0.0, @babel/code-frame@npm:^7.12.13, @babel/code-frame@npm:^7.24.7": + version: 7.24.7 + resolution: "@babel/code-frame@npm:7.24.7" + dependencies: + "@babel/highlight": "npm:^7.24.7" + picocolors: "npm:^1.0.0" + checksum: 10c0/ab0af539473a9f5aeaac7047e377cb4f4edd255a81d84a76058595f8540784cc3fbe8acf73f1e073981104562490aabfb23008cd66dc677a456a4ed5390fdde6 + languageName: node + linkType: hard + +"@babel/compat-data@npm:^7.24.7": + version: 7.24.7 + resolution: "@babel/compat-data@npm:7.24.7" + checksum: 10c0/dcd93a5632b04536498fbe2be5af1057f635fd7f7090483d8e797878559037e5130b26862ceb359acbae93ed27e076d395ddb4663db6b28a665756ffd02d324f + languageName: node + linkType: hard + +"@babel/core@npm:^7.11.6, @babel/core@npm:^7.12.3, @babel/core@npm:^7.23.9": + version: 7.24.7 + resolution: "@babel/core@npm:7.24.7" + dependencies: + "@ampproject/remapping": "npm:^2.2.0" + "@babel/code-frame": "npm:^7.24.7" + "@babel/generator": "npm:^7.24.7" + "@babel/helper-compilation-targets": "npm:^7.24.7" + "@babel/helper-module-transforms": "npm:^7.24.7" + "@babel/helpers": "npm:^7.24.7" + "@babel/parser": "npm:^7.24.7" + "@babel/template": "npm:^7.24.7" + "@babel/traverse": "npm:^7.24.7" + "@babel/types": "npm:^7.24.7" + convert-source-map: "npm:^2.0.0" + debug: "npm:^4.1.0" + gensync: "npm:^1.0.0-beta.2" + json5: "npm:^2.2.3" + semver: "npm:^6.3.1" + checksum: 10c0/4004ba454d3c20a46ea66264e06c15b82e9f6bdc35f88819907d24620da70dbf896abac1cb4cc4b6bb8642969e45f4d808497c9054a1388a386cf8c12e9b9e0d + languageName: node + linkType: hard + +"@babel/generator@npm:^7.24.7, @babel/generator@npm:^7.7.2": + version: 7.24.7 + resolution: "@babel/generator@npm:7.24.7" + dependencies: + "@babel/types": "npm:^7.24.7" + "@jridgewell/gen-mapping": "npm:^0.3.5" + "@jridgewell/trace-mapping": "npm:^0.3.25" + jsesc: "npm:^2.5.1" + checksum: 10c0/06b1f3350baf527a3309e50ffd7065f7aee04dd06e1e7db794ddfde7fe9d81f28df64edd587173f8f9295496a7ddb74b9a185d4bf4de7bb619e6d4ec45c8fd35 + languageName: node + linkType: hard + +"@babel/helper-annotate-as-pure@npm:^7.24.7": + version: 7.24.7 + resolution: "@babel/helper-annotate-as-pure@npm:7.24.7" + dependencies: + "@babel/types": "npm:^7.24.7" + checksum: 10c0/4679f7df4dffd5b3e26083ae65228116c3da34c3fff2c11ae11b259a61baec440f51e30fd236f7a0435b9d471acd93d0bc5a95df8213cbf02b1e083503d81b9a + languageName: node + linkType: hard + +"@babel/helper-compilation-targets@npm:^7.24.7": + version: 7.24.7 + resolution: "@babel/helper-compilation-targets@npm:7.24.7" + dependencies: + "@babel/compat-data": "npm:^7.24.7" + "@babel/helper-validator-option": "npm:^7.24.7" + browserslist: "npm:^4.22.2" + lru-cache: "npm:^5.1.1" + semver: "npm:^6.3.1" + checksum: 10c0/1d580a9bcacefe65e6bf02ba1dafd7ab278269fef45b5e281d8354d95c53031e019890464e7f9351898c01502dd2e633184eb0bcda49ed2ecd538675ce310f51 + languageName: node + linkType: hard + +"@babel/helper-create-class-features-plugin@npm:^7.24.7": + version: 7.24.7 + resolution: "@babel/helper-create-class-features-plugin@npm:7.24.7" + dependencies: + "@babel/helper-annotate-as-pure": "npm:^7.24.7" + "@babel/helper-environment-visitor": "npm:^7.24.7" + "@babel/helper-function-name": "npm:^7.24.7" + "@babel/helper-member-expression-to-functions": "npm:^7.24.7" + "@babel/helper-optimise-call-expression": "npm:^7.24.7" + "@babel/helper-replace-supers": "npm:^7.24.7" + "@babel/helper-skip-transparent-expression-wrappers": "npm:^7.24.7" + "@babel/helper-split-export-declaration": "npm:^7.24.7" + semver: "npm:^6.3.1" + peerDependencies: + "@babel/core": ^7.0.0 + checksum: 10c0/6b7b47d70b41c00f39f86790cff67acf2bce0289d52a7c182b28e797f4e0e6d69027e3d06eccf1d54dddc2e5dde1df663bb1932437e5f447aeb8635d8d64a6ab + languageName: node + linkType: hard + +"@babel/helper-environment-visitor@npm:^7.24.7": + version: 7.24.7 + resolution: "@babel/helper-environment-visitor@npm:7.24.7" + dependencies: + "@babel/types": "npm:^7.24.7" + checksum: 10c0/36ece78882b5960e2d26abf13cf15ff5689bf7c325b10a2895a74a499e712de0d305f8d78bb382dd3c05cfba7e47ec98fe28aab5674243e0625cd38438dd0b2d + languageName: node + linkType: hard + +"@babel/helper-function-name@npm:^7.24.7": + version: 7.24.7 + resolution: "@babel/helper-function-name@npm:7.24.7" + dependencies: + "@babel/template": "npm:^7.24.7" + "@babel/types": "npm:^7.24.7" + checksum: 10c0/e5e41e6cf86bd0f8bf272cbb6e7c5ee0f3e9660414174435a46653efba4f2479ce03ce04abff2aa2ef9359cf057c79c06cb7b134a565ad9c0e8a50dcdc3b43c4 + languageName: node + linkType: hard + +"@babel/helper-hoist-variables@npm:^7.24.7": + version: 7.24.7 + resolution: "@babel/helper-hoist-variables@npm:7.24.7" + dependencies: + "@babel/types": "npm:^7.24.7" + checksum: 10c0/19ee37563bbd1219f9d98991ad0e9abef77803ee5945fd85aa7aa62a67c69efca9a801696a1b58dda27f211e878b3327789e6fd2a6f6c725ccefe36774b5ce95 + languageName: node + linkType: hard + +"@babel/helper-member-expression-to-functions@npm:^7.24.7": + version: 7.24.7 + resolution: "@babel/helper-member-expression-to-functions@npm:7.24.7" + dependencies: + "@babel/traverse": "npm:^7.24.7" + "@babel/types": "npm:^7.24.7" + checksum: 10c0/9638c1d33cf6aba028461ccd3db6061c76ff863ca0d5013dd9a088bf841f2f77c46956493f9da18355c16759449d23b74cc1de4da357ade5c5c34c858f840f0a + languageName: node + linkType: hard + +"@babel/helper-module-imports@npm:^7.24.7": + version: 7.24.7 + resolution: "@babel/helper-module-imports@npm:7.24.7" + dependencies: + "@babel/traverse": "npm:^7.24.7" + "@babel/types": "npm:^7.24.7" + checksum: 10c0/97c57db6c3eeaea31564286e328a9fb52b0313c5cfcc7eee4bc226aebcf0418ea5b6fe78673c0e4a774512ec6c86e309d0f326e99d2b37bfc16a25a032498af0 + languageName: node + linkType: hard + +"@babel/helper-module-transforms@npm:^7.24.7": + version: 7.24.7 + resolution: "@babel/helper-module-transforms@npm:7.24.7" + dependencies: + "@babel/helper-environment-visitor": "npm:^7.24.7" + "@babel/helper-module-imports": "npm:^7.24.7" + "@babel/helper-simple-access": "npm:^7.24.7" + "@babel/helper-split-export-declaration": "npm:^7.24.7" + "@babel/helper-validator-identifier": "npm:^7.24.7" + peerDependencies: + "@babel/core": ^7.0.0 + checksum: 10c0/4f311755fcc3b4cbdb689386309cdb349cf0575a938f0b9ab5d678e1a81bbb265aa34ad93174838245f2ac7ff6d5ddbd0104638a75e4e961958ed514355687b6 + languageName: node + linkType: hard + +"@babel/helper-optimise-call-expression@npm:^7.24.7": + version: 7.24.7 + resolution: "@babel/helper-optimise-call-expression@npm:7.24.7" + dependencies: + "@babel/types": "npm:^7.24.7" + checksum: 10c0/ca6a9884705dea5c95a8b3ce132d1e3f2ae951ff74987d400d1d9c215dae9c0f9e29924d8f8e131e116533d182675bc261927be72f6a9a2968eaeeaa51eb1d0f + languageName: node + linkType: hard + +"@babel/helper-plugin-utils@npm:^7.0.0, @babel/helper-plugin-utils@npm:^7.10.4, @babel/helper-plugin-utils@npm:^7.12.13, @babel/helper-plugin-utils@npm:^7.14.5, @babel/helper-plugin-utils@npm:^7.24.7, @babel/helper-plugin-utils@npm:^7.8.0": + version: 7.24.7 + resolution: "@babel/helper-plugin-utils@npm:7.24.7" + checksum: 10c0/c3d38cd9b3520757bb4a279255cc3f956fc0ac1c193964bd0816ebd5c86e30710be8e35252227e0c9d9e0f4f56d9b5f916537f2bc588084b0988b4787a967d31 + languageName: node + linkType: hard + +"@babel/helper-replace-supers@npm:^7.24.7": + version: 7.24.7 + resolution: "@babel/helper-replace-supers@npm:7.24.7" + dependencies: + "@babel/helper-environment-visitor": "npm:^7.24.7" + "@babel/helper-member-expression-to-functions": "npm:^7.24.7" + "@babel/helper-optimise-call-expression": "npm:^7.24.7" + peerDependencies: + "@babel/core": ^7.0.0 + checksum: 10c0/0e133bb03371dee78e519c334a09c08e1493103a239d9628db0132dfaac3fc16380479ca3c590d278a9b71b624030a338c18ebbfe6d430ebb2e4653775c4b3e3 + languageName: node + linkType: hard + +"@babel/helper-simple-access@npm:^7.24.7": + version: 7.24.7 + resolution: "@babel/helper-simple-access@npm:7.24.7" + dependencies: + "@babel/traverse": "npm:^7.24.7" + "@babel/types": "npm:^7.24.7" + checksum: 10c0/7230e419d59a85f93153415100a5faff23c133d7442c19e0cd070da1784d13cd29096ee6c5a5761065c44e8164f9f80e3a518c41a0256df39e38f7ad6744fed7 + languageName: node + linkType: hard + +"@babel/helper-skip-transparent-expression-wrappers@npm:^7.24.7": + version: 7.24.7 + resolution: "@babel/helper-skip-transparent-expression-wrappers@npm:7.24.7" + dependencies: + "@babel/traverse": "npm:^7.24.7" + "@babel/types": "npm:^7.24.7" + checksum: 10c0/e3a9b8ac9c262ac976a1bcb5fe59694db5e6f0b4f9e7bdba5c7693b8b5e28113c23bdaa60fe8d3ec32a337091b67720b2053bcb3d5655f5406536c3d0584242b + languageName: node + linkType: hard + +"@babel/helper-split-export-declaration@npm:^7.24.7": + version: 7.24.7 + resolution: "@babel/helper-split-export-declaration@npm:7.24.7" + dependencies: + "@babel/types": "npm:^7.24.7" + checksum: 10c0/0254577d7086bf09b01bbde98f731d4fcf4b7c3fa9634fdb87929801307c1f6202a1352e3faa5492450fa8da4420542d44de604daf540704ff349594a78184f6 + languageName: node + linkType: hard + +"@babel/helper-string-parser@npm:^7.24.7": + version: 7.24.7 + resolution: "@babel/helper-string-parser@npm:7.24.7" + checksum: 10c0/47840c7004e735f3dc93939c77b099bb41a64bf3dda0cae62f60e6f74a5ff80b63e9b7cf77b5ec25a324516381fc994e1f62f922533236a8e3a6af57decb5e1e + languageName: node + linkType: hard + +"@babel/helper-validator-identifier@npm:^7.24.7": + version: 7.24.7 + resolution: "@babel/helper-validator-identifier@npm:7.24.7" + checksum: 10c0/87ad608694c9477814093ed5b5c080c2e06d44cb1924ae8320474a74415241223cc2a725eea2640dd783ff1e3390e5f95eede978bc540e870053152e58f1d651 + languageName: node + linkType: hard + +"@babel/helper-validator-option@npm:^7.24.7": + version: 7.24.7 + resolution: "@babel/helper-validator-option@npm:7.24.7" + checksum: 10c0/21aea2b7bc5cc8ddfb828741d5c8116a84cbc35b4a3184ec53124f08e09746f1f67a6f9217850188995ca86059a7942e36d8965a6730784901def777b7e8a436 + languageName: node + linkType: hard + +"@babel/helpers@npm:^7.24.7": + version: 7.24.7 + resolution: "@babel/helpers@npm:7.24.7" + dependencies: + "@babel/template": "npm:^7.24.7" + "@babel/types": "npm:^7.24.7" + checksum: 10c0/aa8e230f6668773e17e141dbcab63e935c514b4b0bf1fed04d2eaefda17df68e16b61a56573f7f1d4d1e605ce6cc162b5f7e9fdf159fde1fd9b77c920ae47d27 + languageName: node + linkType: hard + +"@babel/highlight@npm:^7.24.7": + version: 7.24.7 + resolution: "@babel/highlight@npm:7.24.7" + dependencies: + "@babel/helper-validator-identifier": "npm:^7.24.7" + chalk: "npm:^2.4.2" + js-tokens: "npm:^4.0.0" + picocolors: "npm:^1.0.0" + checksum: 10c0/674334c571d2bb9d1c89bdd87566383f59231e16bcdcf5bb7835babdf03c9ae585ca0887a7b25bdf78f303984af028df52831c7989fecebb5101cc132da9393a + languageName: node + linkType: hard + +"@babel/parser@npm:^7.1.0, @babel/parser@npm:^7.14.7, @babel/parser@npm:^7.20.7, @babel/parser@npm:^7.23.9, @babel/parser@npm:^7.24.7": + version: 7.24.7 + resolution: "@babel/parser@npm:7.24.7" + bin: + parser: ./bin/babel-parser.js + checksum: 10c0/8b244756872185a1c6f14b979b3535e682ff08cb5a2a5fd97cc36c017c7ef431ba76439e95e419d43000c5b07720495b00cf29a7f0d9a483643d08802b58819b + languageName: node + linkType: hard + +"@babel/plugin-syntax-async-generators@npm:^7.8.4": + version: 7.8.4 + resolution: "@babel/plugin-syntax-async-generators@npm:7.8.4" + dependencies: + "@babel/helper-plugin-utils": "npm:^7.8.0" + peerDependencies: + "@babel/core": ^7.0.0-0 + checksum: 10c0/d13efb282838481348c71073b6be6245b35d4f2f964a8f71e4174f235009f929ef7613df25f8d2338e2d3e44bc4265a9f8638c6aaa136d7a61fe95985f9725c8 + languageName: node + linkType: hard + +"@babel/plugin-syntax-bigint@npm:^7.8.3": + version: 7.8.3 + resolution: "@babel/plugin-syntax-bigint@npm:7.8.3" + dependencies: + "@babel/helper-plugin-utils": "npm:^7.8.0" + peerDependencies: + "@babel/core": ^7.0.0-0 + checksum: 10c0/686891b81af2bc74c39013655da368a480f17dd237bf9fbc32048e5865cb706d5a8f65438030da535b332b1d6b22feba336da8fa931f663b6b34e13147d12dde + languageName: node + linkType: hard + +"@babel/plugin-syntax-class-properties@npm:^7.8.3": + version: 7.12.13 + resolution: "@babel/plugin-syntax-class-properties@npm:7.12.13" + dependencies: + "@babel/helper-plugin-utils": "npm:^7.12.13" + peerDependencies: + "@babel/core": ^7.0.0-0 + checksum: 10c0/95168fa186416195280b1264fb18afcdcdcea780b3515537b766cb90de6ce042d42dd6a204a39002f794ae5845b02afb0fd4861a3308a861204a55e68310a120 + languageName: node + linkType: hard + +"@babel/plugin-syntax-import-meta@npm:^7.8.3": + version: 7.10.4 + resolution: "@babel/plugin-syntax-import-meta@npm:7.10.4" + dependencies: + "@babel/helper-plugin-utils": "npm:^7.10.4" + peerDependencies: + "@babel/core": ^7.0.0-0 + checksum: 10c0/0b08b5e4c3128523d8e346f8cfc86824f0da2697b1be12d71af50a31aff7a56ceb873ed28779121051475010c28d6146a6bfea8518b150b71eeb4e46190172ee + languageName: node + linkType: hard + +"@babel/plugin-syntax-json-strings@npm:^7.8.3": + version: 7.8.3 + resolution: "@babel/plugin-syntax-json-strings@npm:7.8.3" + dependencies: + "@babel/helper-plugin-utils": "npm:^7.8.0" + peerDependencies: + "@babel/core": ^7.0.0-0 + checksum: 10c0/e98f31b2ec406c57757d115aac81d0336e8434101c224edd9a5c93cefa53faf63eacc69f3138960c8b25401315af03df37f68d316c151c4b933136716ed6906e + languageName: node + linkType: hard + +"@babel/plugin-syntax-jsx@npm:^7.24.7, @babel/plugin-syntax-jsx@npm:^7.7.2": + version: 7.24.7 + resolution: "@babel/plugin-syntax-jsx@npm:7.24.7" + dependencies: + "@babel/helper-plugin-utils": "npm:^7.24.7" + peerDependencies: + "@babel/core": ^7.0.0-0 + checksum: 10c0/f44d927a9ae8d5ef016ff5b450e1671e56629ddc12e56b938e41fd46e141170d9dfc9a53d6cb2b9a20a7dd266a938885e6a3981c60c052a2e1daed602ac80e51 + languageName: node + linkType: hard + +"@babel/plugin-syntax-logical-assignment-operators@npm:^7.8.3": + version: 7.10.4 + resolution: "@babel/plugin-syntax-logical-assignment-operators@npm:7.10.4" + dependencies: + "@babel/helper-plugin-utils": "npm:^7.10.4" + peerDependencies: + "@babel/core": ^7.0.0-0 + checksum: 10c0/2594cfbe29411ad5bc2ad4058de7b2f6a8c5b86eda525a993959438615479e59c012c14aec979e538d60a584a1a799b60d1b8942c3b18468cb9d99b8fd34cd0b + languageName: node + linkType: hard + +"@babel/plugin-syntax-nullish-coalescing-operator@npm:^7.8.3": + version: 7.8.3 + resolution: "@babel/plugin-syntax-nullish-coalescing-operator@npm:7.8.3" + dependencies: + "@babel/helper-plugin-utils": "npm:^7.8.0" + peerDependencies: + "@babel/core": ^7.0.0-0 + checksum: 10c0/2024fbb1162899094cfc81152449b12bd0cc7053c6d4bda8ac2852545c87d0a851b1b72ed9560673cbf3ef6248257262c3c04aabf73117215c1b9cc7dd2542ce + languageName: node + linkType: hard + +"@babel/plugin-syntax-numeric-separator@npm:^7.8.3": + version: 7.10.4 + resolution: "@babel/plugin-syntax-numeric-separator@npm:7.10.4" + dependencies: + "@babel/helper-plugin-utils": "npm:^7.10.4" + peerDependencies: + "@babel/core": ^7.0.0-0 + checksum: 10c0/c55a82b3113480942c6aa2fcbe976ff9caa74b7b1109ff4369641dfbc88d1da348aceb3c31b6ed311c84d1e7c479440b961906c735d0ab494f688bf2fd5b9bb9 + languageName: node + linkType: hard + +"@babel/plugin-syntax-object-rest-spread@npm:^7.8.3": + version: 7.8.3 + resolution: "@babel/plugin-syntax-object-rest-spread@npm:7.8.3" + dependencies: + "@babel/helper-plugin-utils": "npm:^7.8.0" + peerDependencies: + "@babel/core": ^7.0.0-0 + checksum: 10c0/ee1eab52ea6437e3101a0a7018b0da698545230015fc8ab129d292980ec6dff94d265e9e90070e8ae5fed42f08f1622c14c94552c77bcac784b37f503a82ff26 + languageName: node + linkType: hard + +"@babel/plugin-syntax-optional-catch-binding@npm:^7.8.3": + version: 7.8.3 + resolution: "@babel/plugin-syntax-optional-catch-binding@npm:7.8.3" + dependencies: + "@babel/helper-plugin-utils": "npm:^7.8.0" + peerDependencies: + "@babel/core": ^7.0.0-0 + checksum: 10c0/27e2493ab67a8ea6d693af1287f7e9acec206d1213ff107a928e85e173741e1d594196f99fec50e9dde404b09164f39dec5864c767212154ffe1caa6af0bc5af + languageName: node + linkType: hard + +"@babel/plugin-syntax-optional-chaining@npm:^7.8.3": + version: 7.8.3 + resolution: "@babel/plugin-syntax-optional-chaining@npm:7.8.3" + dependencies: + "@babel/helper-plugin-utils": "npm:^7.8.0" + peerDependencies: + "@babel/core": ^7.0.0-0 + checksum: 10c0/46edddf2faa6ebf94147b8e8540dfc60a5ab718e2de4d01b2c0bdf250a4d642c2bd47cbcbb739febcb2bf75514dbcefad3c52208787994b8d0f8822490f55e81 + languageName: node + linkType: hard + +"@babel/plugin-syntax-top-level-await@npm:^7.8.3": + version: 7.14.5 + resolution: "@babel/plugin-syntax-top-level-await@npm:7.14.5" + dependencies: + "@babel/helper-plugin-utils": "npm:^7.14.5" + peerDependencies: + "@babel/core": ^7.0.0-0 + checksum: 10c0/14bf6e65d5bc1231ffa9def5f0ef30b19b51c218fcecaa78cd1bdf7939dfdf23f90336080b7f5196916368e399934ce5d581492d8292b46a2fb569d8b2da106f + languageName: node + linkType: hard + +"@babel/plugin-syntax-typescript@npm:^7.24.7, @babel/plugin-syntax-typescript@npm:^7.7.2": + version: 7.24.7 + resolution: "@babel/plugin-syntax-typescript@npm:7.24.7" + dependencies: + "@babel/helper-plugin-utils": "npm:^7.24.7" + peerDependencies: + "@babel/core": ^7.0.0-0 + checksum: 10c0/cdabd2e8010fb0ad15b49c2c270efc97c4bfe109ead36c7bbcf22da7a74bc3e49702fc4f22f12d2d6049e8e22a5769258df1fd05f0420ae45e11bdd5bc07805a + languageName: node + linkType: hard + +"@babel/plugin-transform-modules-commonjs@npm:^7.24.7": + version: 7.24.7 + resolution: "@babel/plugin-transform-modules-commonjs@npm:7.24.7" + dependencies: + "@babel/helper-module-transforms": "npm:^7.24.7" + "@babel/helper-plugin-utils": "npm:^7.24.7" + "@babel/helper-simple-access": "npm:^7.24.7" + peerDependencies: + "@babel/core": ^7.0.0-0 + checksum: 10c0/9442292b3daf6a5076cdc3c4c32bf423bda824ccaeb0dd0dc8b3effaa1fecfcb0130ae6e647fef12a5d5ff25bcc99a0d6bfc6d24a7525345e1bcf46fcdf81752 + languageName: node + linkType: hard + +"@babel/plugin-transform-typescript@npm:^7.24.7": + version: 7.24.7 + resolution: "@babel/plugin-transform-typescript@npm:7.24.7" + dependencies: + "@babel/helper-annotate-as-pure": "npm:^7.24.7" + "@babel/helper-create-class-features-plugin": "npm:^7.24.7" + "@babel/helper-plugin-utils": "npm:^7.24.7" + "@babel/plugin-syntax-typescript": "npm:^7.24.7" + peerDependencies: + "@babel/core": ^7.0.0-0 + checksum: 10c0/e8dacdc153a4c4599014b66eb01b94e3dc933d58d4f0cc3039c1a8f432e77b9df14f34a61964e014b975bf466f3fefd8c4768b3e887d3da1be9dc942799bdfdf + languageName: node + linkType: hard + +"@babel/preset-typescript@npm:^7.20.2": + version: 7.24.7 + resolution: "@babel/preset-typescript@npm:7.24.7" + dependencies: + "@babel/helper-plugin-utils": "npm:^7.24.7" + "@babel/helper-validator-option": "npm:^7.24.7" + "@babel/plugin-syntax-jsx": "npm:^7.24.7" + "@babel/plugin-transform-modules-commonjs": "npm:^7.24.7" + "@babel/plugin-transform-typescript": "npm:^7.24.7" + peerDependencies: + "@babel/core": ^7.0.0-0 + checksum: 10c0/986bc0978eedb4da33aba8e1e13a3426dd1829515313b7e8f4ba5d8c18aff1663b468939d471814e7acf4045d326ae6cff37239878d169ac3fe53a8fde71f8ee + languageName: node + linkType: hard + +"@babel/runtime@npm:^7.21.0": + version: 7.24.7 + resolution: "@babel/runtime@npm:7.24.7" + dependencies: + regenerator-runtime: "npm:^0.14.0" + checksum: 10c0/b6fa3ec61a53402f3c1d75f4d808f48b35e0dfae0ec8e2bb5c6fc79fb95935da75766e0ca534d0f1c84871f6ae0d2ebdd950727cfadb745a2cdbef13faef5513 + languageName: node + linkType: hard + +"@babel/template@npm:^7.24.7, @babel/template@npm:^7.3.3": + version: 7.24.7 + resolution: "@babel/template@npm:7.24.7" + dependencies: + "@babel/code-frame": "npm:^7.24.7" + "@babel/parser": "npm:^7.24.7" + "@babel/types": "npm:^7.24.7" + checksum: 10c0/95b0b3ee80fcef685b7f4426f5713a855ea2cd5ac4da829b213f8fb5afe48a2a14683c2ea04d446dbc7f711c33c5cd4a965ef34dcbe5bc387c9e966b67877ae3 + languageName: node + linkType: hard + +"@babel/traverse@npm:^7.24.7": + version: 7.24.7 + resolution: "@babel/traverse@npm:7.24.7" + dependencies: + "@babel/code-frame": "npm:^7.24.7" + "@babel/generator": "npm:^7.24.7" + "@babel/helper-environment-visitor": "npm:^7.24.7" + "@babel/helper-function-name": "npm:^7.24.7" + "@babel/helper-hoist-variables": "npm:^7.24.7" + "@babel/helper-split-export-declaration": "npm:^7.24.7" + "@babel/parser": "npm:^7.24.7" + "@babel/types": "npm:^7.24.7" + debug: "npm:^4.3.1" + globals: "npm:^11.1.0" + checksum: 10c0/a5135e589c3f1972b8877805f50a084a04865ccb1d68e5e1f3b94a8841b3485da4142e33413d8fd76bc0e6444531d3adf1f59f359c11ffac452b743d835068ab + languageName: node + linkType: hard + +"@babel/types@npm:^7.0.0, @babel/types@npm:^7.20.7, @babel/types@npm:^7.24.7, @babel/types@npm:^7.3.3, @babel/types@npm:^7.8.3": + version: 7.24.7 + resolution: "@babel/types@npm:7.24.7" + dependencies: + "@babel/helper-string-parser": "npm:^7.24.7" + "@babel/helper-validator-identifier": "npm:^7.24.7" + to-fast-properties: "npm:^2.0.0" + checksum: 10c0/d9ecbfc3eb2b05fb1e6eeea546836ac30d990f395ef3fe3f75ced777a222c3cfc4489492f72e0ce3d9a5a28860a1ce5f81e66b88cf5088909068b3ff4fab72c1 + languageName: node + linkType: hard + +"@bcoe/v8-coverage@npm:^0.2.3": + version: 0.2.3 + resolution: "@bcoe/v8-coverage@npm:0.2.3" + checksum: 10c0/6b80ae4cb3db53f486da2dc63b6e190a74c8c3cca16bb2733f234a0b6a9382b09b146488ae08e2b22cf00f6c83e20f3e040a2f7894f05c045c946d6a090b1d52 + languageName: node + linkType: hard + +"@colors/colors@npm:1.6.0, @colors/colors@npm:^1.6.0": + version: 1.6.0 + resolution: "@colors/colors@npm:1.6.0" + checksum: 10c0/9328a0778a5b0db243af54455b79a69e3fb21122d6c15ef9e9fcc94881d8d17352d8b2b2590f9bdd46fac5c2d6c1636dcfc14358a20c70e22daf89e1a759b629 + languageName: node + linkType: hard + +"@dabh/diagnostics@npm:^2.0.2": + version: 2.0.3 + resolution: "@dabh/diagnostics@npm:2.0.3" + dependencies: + colorspace: "npm:1.1.x" + enabled: "npm:2.0.x" + kuler: "npm:^2.0.0" + checksum: 10c0/a5133df8492802465ed01f2f0a5784585241a1030c362d54a602ed1839816d6c93d71dde05cf2ddb4fd0796238c19774406bd62fa2564b637907b495f52425fe + languageName: node + linkType: hard + +"@eslint-community/eslint-utils@npm:^4.2.0": + version: 4.4.0 + resolution: "@eslint-community/eslint-utils@npm:4.4.0" + dependencies: + eslint-visitor-keys: "npm:^3.3.0" + peerDependencies: + eslint: ^6.0.0 || ^7.0.0 || >=8.0.0 + checksum: 10c0/7e559c4ce59cd3a06b1b5a517b593912e680a7f981ae7affab0d01d709e99cd5647019be8fafa38c350305bc32f1f7d42c7073edde2ab536c745e365f37b607e + languageName: node + linkType: hard + +"@eslint-community/regexpp@npm:^4.4.0, @eslint-community/regexpp@npm:^4.6.1": + version: 4.10.1 + resolution: "@eslint-community/regexpp@npm:4.10.1" + checksum: 10c0/f59376025d0c91dd9fdf18d33941df499292a3ecba3e9889c360f3f6590197d30755604588786cdca0f9030be315a26b206014af4b65c0ff85b4ec49043de780 + languageName: node + linkType: hard + +"@eslint/eslintrc@npm:^2.1.4": + version: 2.1.4 + resolution: "@eslint/eslintrc@npm:2.1.4" + dependencies: + ajv: "npm:^6.12.4" + debug: "npm:^4.3.2" + espree: "npm:^9.6.0" + globals: "npm:^13.19.0" + ignore: "npm:^5.2.0" + import-fresh: "npm:^3.2.1" + js-yaml: "npm:^4.1.0" + minimatch: "npm:^3.1.2" + strip-json-comments: "npm:^3.1.1" + checksum: 10c0/32f67052b81768ae876c84569ffd562491ec5a5091b0c1e1ca1e0f3c24fb42f804952fdd0a137873bc64303ba368a71ba079a6f691cee25beee9722d94cc8573 + languageName: node + linkType: hard + +"@eslint/js@npm:8.57.0": + version: 8.57.0 + resolution: "@eslint/js@npm:8.57.0" + checksum: 10c0/9a518bb8625ba3350613903a6d8c622352ab0c6557a59fe6ff6178bf882bf57123f9d92aa826ee8ac3ee74b9c6203fe630e9ee00efb03d753962dcf65ee4bd94 + languageName: node + linkType: hard + +"@humanwhocodes/config-array@npm:^0.11.14": + version: 0.11.14 + resolution: "@humanwhocodes/config-array@npm:0.11.14" + dependencies: + "@humanwhocodes/object-schema": "npm:^2.0.2" + debug: "npm:^4.3.1" + minimatch: "npm:^3.0.5" + checksum: 10c0/66f725b4ee5fdd8322c737cb5013e19fac72d4d69c8bf4b7feb192fcb83442b035b92186f8e9497c220e58b2d51a080f28a73f7899bc1ab288c3be172c467541 + languageName: node + linkType: hard + +"@humanwhocodes/module-importer@npm:^1.0.1": + version: 1.0.1 + resolution: "@humanwhocodes/module-importer@npm:1.0.1" + checksum: 10c0/909b69c3b86d482c26b3359db16e46a32e0fb30bd306a3c176b8313b9e7313dba0f37f519de6aa8b0a1921349e505f259d19475e123182416a506d7f87e7f529 + languageName: node + linkType: hard + +"@humanwhocodes/object-schema@npm:^2.0.2": + version: 2.0.3 + resolution: "@humanwhocodes/object-schema@npm:2.0.3" + checksum: 10c0/80520eabbfc2d32fe195a93557cef50dfe8c8905de447f022675aaf66abc33ae54098f5ea78548d925aa671cd4ab7c7daa5ad704fe42358c9b5e7db60f80696c + languageName: node + linkType: hard + +"@isaacs/cliui@npm:^8.0.2": + version: 8.0.2 + resolution: "@isaacs/cliui@npm:8.0.2" + dependencies: + string-width: "npm:^5.1.2" + string-width-cjs: "npm:string-width@^4.2.0" + strip-ansi: "npm:^7.0.1" + strip-ansi-cjs: "npm:strip-ansi@^6.0.1" + wrap-ansi: "npm:^8.1.0" + wrap-ansi-cjs: "npm:wrap-ansi@^7.0.0" + checksum: 10c0/b1bf42535d49f11dc137f18d5e4e63a28c5569de438a221c369483731e9dac9fb797af554e8bf02b6192d1e5eba6e6402cf93900c3d0ac86391d00d04876789e + languageName: node + linkType: hard + +"@istanbuljs/load-nyc-config@npm:^1.0.0": + version: 1.1.0 + resolution: "@istanbuljs/load-nyc-config@npm:1.1.0" + dependencies: + camelcase: "npm:^5.3.1" + find-up: "npm:^4.1.0" + get-package-type: "npm:^0.1.0" + js-yaml: "npm:^3.13.1" + resolve-from: "npm:^5.0.0" + checksum: 10c0/dd2a8b094887da5a1a2339543a4933d06db2e63cbbc2e288eb6431bd832065df0c099d091b6a67436e71b7d6bf85f01ce7c15f9253b4cbebcc3b9a496165ba42 + languageName: node + linkType: hard + +"@istanbuljs/schema@npm:^0.1.2, @istanbuljs/schema@npm:^0.1.3": + version: 0.1.3 + resolution: "@istanbuljs/schema@npm:0.1.3" + checksum: 10c0/61c5286771676c9ca3eb2bd8a7310a9c063fb6e0e9712225c8471c582d157392c88f5353581c8c9adbe0dff98892317d2fdfc56c3499aa42e0194405206a963a + languageName: node + linkType: hard + +"@jest/console@npm:^29.7.0": + version: 29.7.0 + resolution: "@jest/console@npm:29.7.0" + dependencies: + "@jest/types": "npm:^29.6.3" + "@types/node": "npm:*" + chalk: "npm:^4.0.0" + jest-message-util: "npm:^29.7.0" + jest-util: "npm:^29.7.0" + slash: "npm:^3.0.0" + checksum: 10c0/7be408781d0a6f657e969cbec13b540c329671819c2f57acfad0dae9dbfe2c9be859f38fe99b35dba9ff1536937dc6ddc69fdcd2794812fa3c647a1619797f6c + languageName: node + linkType: hard + +"@jest/core@npm:^29.7.0": + version: 29.7.0 + resolution: "@jest/core@npm:29.7.0" + dependencies: + "@jest/console": "npm:^29.7.0" + "@jest/reporters": "npm:^29.7.0" + "@jest/test-result": "npm:^29.7.0" + "@jest/transform": "npm:^29.7.0" + "@jest/types": "npm:^29.6.3" + "@types/node": "npm:*" + ansi-escapes: "npm:^4.2.1" + chalk: "npm:^4.0.0" + ci-info: "npm:^3.2.0" + exit: "npm:^0.1.2" + graceful-fs: "npm:^4.2.9" + jest-changed-files: "npm:^29.7.0" + jest-config: "npm:^29.7.0" + jest-haste-map: "npm:^29.7.0" + jest-message-util: "npm:^29.7.0" + jest-regex-util: "npm:^29.6.3" + jest-resolve: "npm:^29.7.0" + jest-resolve-dependencies: "npm:^29.7.0" + jest-runner: "npm:^29.7.0" + jest-runtime: "npm:^29.7.0" + jest-snapshot: "npm:^29.7.0" + jest-util: "npm:^29.7.0" + jest-validate: "npm:^29.7.0" + jest-watcher: "npm:^29.7.0" + micromatch: "npm:^4.0.4" + pretty-format: "npm:^29.7.0" + slash: "npm:^3.0.0" + strip-ansi: "npm:^6.0.0" + peerDependencies: + node-notifier: ^8.0.1 || ^9.0.0 || ^10.0.0 + peerDependenciesMeta: + node-notifier: + optional: true + checksum: 10c0/934f7bf73190f029ac0f96662c85cd276ec460d407baf6b0dbaec2872e157db4d55a7ee0b1c43b18874602f662b37cb973dda469a4e6d88b4e4845b521adeeb2 + languageName: node + linkType: hard + +"@jest/environment@npm:^29.7.0": + version: 29.7.0 + resolution: "@jest/environment@npm:29.7.0" + dependencies: + "@jest/fake-timers": "npm:^29.7.0" + "@jest/types": "npm:^29.6.3" + "@types/node": "npm:*" + jest-mock: "npm:^29.7.0" + checksum: 10c0/c7b1b40c618f8baf4d00609022d2afa086d9c6acc706f303a70bb4b67275868f620ad2e1a9efc5edd418906157337cce50589a627a6400bbdf117d351b91ef86 + languageName: node + linkType: hard + +"@jest/expect-utils@npm:^29.7.0": + version: 29.7.0 + resolution: "@jest/expect-utils@npm:29.7.0" + dependencies: + jest-get-type: "npm:^29.6.3" + checksum: 10c0/60b79d23a5358dc50d9510d726443316253ecda3a7fb8072e1526b3e0d3b14f066ee112db95699b7a43ad3f0b61b750c72e28a5a1cac361d7a2bb34747fa938a + languageName: node + linkType: hard + +"@jest/expect@npm:^29.7.0": + version: 29.7.0 + resolution: "@jest/expect@npm:29.7.0" + dependencies: + expect: "npm:^29.7.0" + jest-snapshot: "npm:^29.7.0" + checksum: 10c0/b41f193fb697d3ced134349250aed6ccea075e48c4f803159db102b826a4e473397c68c31118259868fd69a5cba70e97e1c26d2c2ff716ca39dc73a2ccec037e + languageName: node + linkType: hard + +"@jest/fake-timers@npm:^29.7.0": + version: 29.7.0 + resolution: "@jest/fake-timers@npm:29.7.0" + dependencies: + "@jest/types": "npm:^29.6.3" + "@sinonjs/fake-timers": "npm:^10.0.2" + "@types/node": "npm:*" + jest-message-util: "npm:^29.7.0" + jest-mock: "npm:^29.7.0" + jest-util: "npm:^29.7.0" + checksum: 10c0/cf0a8bcda801b28dc2e2b2ba36302200ee8104a45ad7a21e6c234148932f826cb3bc57c8df3b7b815aeea0861d7b6ca6f0d4778f93b9219398ef28749e03595c + languageName: node + linkType: hard + +"@jest/globals@npm:^29.7.0": + version: 29.7.0 + resolution: "@jest/globals@npm:29.7.0" + dependencies: + "@jest/environment": "npm:^29.7.0" + "@jest/expect": "npm:^29.7.0" + "@jest/types": "npm:^29.6.3" + jest-mock: "npm:^29.7.0" + checksum: 10c0/a385c99396878fe6e4460c43bd7bb0a5cc52befb462cc6e7f2a3810f9e7bcce7cdeb51908fd530391ee452dc856c98baa2c5f5fa8a5b30b071d31ef7f6955cea + languageName: node + linkType: hard + +"@jest/reporters@npm:^29.7.0": + version: 29.7.0 + resolution: "@jest/reporters@npm:29.7.0" + dependencies: + "@bcoe/v8-coverage": "npm:^0.2.3" + "@jest/console": "npm:^29.7.0" + "@jest/test-result": "npm:^29.7.0" + "@jest/transform": "npm:^29.7.0" + "@jest/types": "npm:^29.6.3" + "@jridgewell/trace-mapping": "npm:^0.3.18" + "@types/node": "npm:*" + chalk: "npm:^4.0.0" + collect-v8-coverage: "npm:^1.0.0" + exit: "npm:^0.1.2" + glob: "npm:^7.1.3" + graceful-fs: "npm:^4.2.9" + istanbul-lib-coverage: "npm:^3.0.0" + istanbul-lib-instrument: "npm:^6.0.0" + istanbul-lib-report: "npm:^3.0.0" + istanbul-lib-source-maps: "npm:^4.0.0" + istanbul-reports: "npm:^3.1.3" + jest-message-util: "npm:^29.7.0" + jest-util: "npm:^29.7.0" + jest-worker: "npm:^29.7.0" + slash: "npm:^3.0.0" + string-length: "npm:^4.0.1" + strip-ansi: "npm:^6.0.0" + v8-to-istanbul: "npm:^9.0.1" + peerDependencies: + node-notifier: ^8.0.1 || ^9.0.0 || ^10.0.0 + peerDependenciesMeta: + node-notifier: + optional: true + checksum: 10c0/a754402a799541c6e5aff2c8160562525e2a47e7d568f01ebfc4da66522de39cbb809bbb0a841c7052e4270d79214e70aec3c169e4eae42a03bc1a8a20cb9fa2 + languageName: node + linkType: hard + +"@jest/schemas@npm:^29.6.3": + version: 29.6.3 + resolution: "@jest/schemas@npm:29.6.3" + dependencies: + "@sinclair/typebox": "npm:^0.27.8" + checksum: 10c0/b329e89cd5f20b9278ae1233df74016ebf7b385e0d14b9f4c1ad18d096c4c19d1e687aa113a9c976b16ec07f021ae53dea811fb8c1248a50ac34fbe009fdf6be + languageName: node + linkType: hard + +"@jest/source-map@npm:^29.6.3": + version: 29.6.3 + resolution: "@jest/source-map@npm:29.6.3" + dependencies: + "@jridgewell/trace-mapping": "npm:^0.3.18" + callsites: "npm:^3.0.0" + graceful-fs: "npm:^4.2.9" + checksum: 10c0/a2f177081830a2e8ad3f2e29e20b63bd40bade294880b595acf2fc09ec74b6a9dd98f126a2baa2bf4941acd89b13a4ade5351b3885c224107083a0059b60a219 + languageName: node + linkType: hard + +"@jest/test-result@npm:^29.7.0": + version: 29.7.0 + resolution: "@jest/test-result@npm:29.7.0" + dependencies: + "@jest/console": "npm:^29.7.0" + "@jest/types": "npm:^29.6.3" + "@types/istanbul-lib-coverage": "npm:^2.0.0" + collect-v8-coverage: "npm:^1.0.0" + checksum: 10c0/7de54090e54a674ca173470b55dc1afdee994f2d70d185c80236003efd3fa2b753fff51ffcdda8e2890244c411fd2267529d42c4a50a8303755041ee493e6a04 + languageName: node + linkType: hard + +"@jest/test-sequencer@npm:^29.7.0": + version: 29.7.0 + resolution: "@jest/test-sequencer@npm:29.7.0" + dependencies: + "@jest/test-result": "npm:^29.7.0" + graceful-fs: "npm:^4.2.9" + jest-haste-map: "npm:^29.7.0" + slash: "npm:^3.0.0" + checksum: 10c0/593a8c4272797bb5628984486080cbf57aed09c7cfdc0a634e8c06c38c6bef329c46c0016e84555ee55d1cd1f381518cf1890990ff845524c1123720c8c1481b + languageName: node + linkType: hard + +"@jest/transform@npm:^29.7.0": + version: 29.7.0 + resolution: "@jest/transform@npm:29.7.0" + dependencies: + "@babel/core": "npm:^7.11.6" + "@jest/types": "npm:^29.6.3" + "@jridgewell/trace-mapping": "npm:^0.3.18" + babel-plugin-istanbul: "npm:^6.1.1" + chalk: "npm:^4.0.0" + convert-source-map: "npm:^2.0.0" + fast-json-stable-stringify: "npm:^2.1.0" + graceful-fs: "npm:^4.2.9" + jest-haste-map: "npm:^29.7.0" + jest-regex-util: "npm:^29.6.3" + jest-util: "npm:^29.7.0" + micromatch: "npm:^4.0.4" + pirates: "npm:^4.0.4" + slash: "npm:^3.0.0" + write-file-atomic: "npm:^4.0.2" + checksum: 10c0/7f4a7f73dcf45dfdf280c7aa283cbac7b6e5a904813c3a93ead7e55873761fc20d5c4f0191d2019004fac6f55f061c82eb3249c2901164ad80e362e7a7ede5a6 + languageName: node + linkType: hard + +"@jest/types@npm:^29.6.3": + version: 29.6.3 + resolution: "@jest/types@npm:29.6.3" + dependencies: + "@jest/schemas": "npm:^29.6.3" + "@types/istanbul-lib-coverage": "npm:^2.0.0" + "@types/istanbul-reports": "npm:^3.0.0" + "@types/node": "npm:*" + "@types/yargs": "npm:^17.0.8" + chalk: "npm:^4.0.0" + checksum: 10c0/ea4e493dd3fb47933b8ccab201ae573dcc451f951dc44ed2a86123cd8541b82aa9d2b1031caf9b1080d6673c517e2dcc25a44b2dc4f3fbc37bfc965d444888c0 + languageName: node + linkType: hard + +"@jridgewell/gen-mapping@npm:^0.3.5": + version: 0.3.5 + resolution: "@jridgewell/gen-mapping@npm:0.3.5" + dependencies: + "@jridgewell/set-array": "npm:^1.2.1" + "@jridgewell/sourcemap-codec": "npm:^1.4.10" + "@jridgewell/trace-mapping": "npm:^0.3.24" + checksum: 10c0/1be4fd4a6b0f41337c4f5fdf4afc3bd19e39c3691924817108b82ffcb9c9e609c273f936932b9fba4b3a298ce2eb06d9bff4eb1cc3bd81c4f4ee1b4917e25feb + languageName: node + linkType: hard + +"@jridgewell/resolve-uri@npm:^3.1.0": + version: 3.1.2 + resolution: "@jridgewell/resolve-uri@npm:3.1.2" + checksum: 10c0/d502e6fb516b35032331406d4e962c21fe77cdf1cbdb49c6142bcbd9e30507094b18972778a6e27cbad756209cfe34b1a27729e6fa08a2eb92b33943f680cf1e + languageName: node + linkType: hard + +"@jridgewell/set-array@npm:^1.2.1": + version: 1.2.1 + resolution: "@jridgewell/set-array@npm:1.2.1" + checksum: 10c0/2a5aa7b4b5c3464c895c802d8ae3f3d2b92fcbe84ad12f8d0bfbb1f5ad006717e7577ee1fd2eac00c088abe486c7adb27976f45d2941ff6b0b92b2c3302c60f4 + languageName: node + linkType: hard + +"@jridgewell/sourcemap-codec@npm:^1.4.10, @jridgewell/sourcemap-codec@npm:^1.4.14": + version: 1.4.15 + resolution: "@jridgewell/sourcemap-codec@npm:1.4.15" + checksum: 10c0/0c6b5ae663087558039052a626d2d7ed5208da36cfd707dcc5cea4a07cfc918248403dcb5989a8f7afaf245ce0573b7cc6fd94c4a30453bd10e44d9363940ba5 + languageName: node + linkType: hard + +"@jridgewell/trace-mapping@npm:^0.3.12, @jridgewell/trace-mapping@npm:^0.3.18, @jridgewell/trace-mapping@npm:^0.3.24, @jridgewell/trace-mapping@npm:^0.3.25": + version: 0.3.25 + resolution: "@jridgewell/trace-mapping@npm:0.3.25" + dependencies: + "@jridgewell/resolve-uri": "npm:^3.1.0" + "@jridgewell/sourcemap-codec": "npm:^1.4.14" + checksum: 10c0/3d1ce6ebc69df9682a5a8896b414c6537e428a1d68b02fcc8363b04284a8ca0df04d0ee3013132252ab14f2527bc13bea6526a912ecb5658f0e39fd2860b4df4 + languageName: node + linkType: hard + +"@mapbox/node-pre-gyp@npm:^1.0.11": + version: 1.0.11 + resolution: "@mapbox/node-pre-gyp@npm:1.0.11" + dependencies: + detect-libc: "npm:^2.0.0" + https-proxy-agent: "npm:^5.0.0" + make-dir: "npm:^3.1.0" + node-fetch: "npm:^2.6.7" + nopt: "npm:^5.0.0" + npmlog: "npm:^5.0.1" + rimraf: "npm:^3.0.2" + semver: "npm:^7.3.5" + tar: "npm:^6.1.11" + bin: + node-pre-gyp: bin/node-pre-gyp + checksum: 10c0/2b24b93c31beca1c91336fa3b3769fda98e202fb7f9771f0f4062588d36dcc30fcf8118c36aa747fa7f7610d8cf601872bdaaf62ce7822bb08b545d1bbe086cc + languageName: node + linkType: hard + +"@nodelib/fs.scandir@npm:2.1.5": + version: 2.1.5 + resolution: "@nodelib/fs.scandir@npm:2.1.5" + dependencies: + "@nodelib/fs.stat": "npm:2.0.5" + run-parallel: "npm:^1.1.9" + checksum: 10c0/732c3b6d1b1e967440e65f284bd06e5821fedf10a1bea9ed2bb75956ea1f30e08c44d3def9d6a230666574edbaf136f8cfd319c14fd1f87c66e6a44449afb2eb + languageName: node + linkType: hard + +"@nodelib/fs.stat@npm:2.0.5, @nodelib/fs.stat@npm:^2.0.2": + version: 2.0.5 + resolution: "@nodelib/fs.stat@npm:2.0.5" + checksum: 10c0/88dafe5e3e29a388b07264680dc996c17f4bda48d163a9d4f5c1112979f0ce8ec72aa7116122c350b4e7976bc5566dc3ddb579be1ceaacc727872eb4ed93926d + languageName: node + linkType: hard + +"@nodelib/fs.walk@npm:^1.2.3, @nodelib/fs.walk@npm:^1.2.8": + version: 1.2.8 + resolution: "@nodelib/fs.walk@npm:1.2.8" + dependencies: + "@nodelib/fs.scandir": "npm:2.1.5" + fastq: "npm:^1.6.0" + checksum: 10c0/db9de047c3bb9b51f9335a7bb46f4fcfb6829fb628318c12115fbaf7d369bfce71c15b103d1fc3b464812d936220ee9bc1c8f762d032c9f6be9acc99249095b1 + languageName: node + linkType: hard + +"@npmcli/agent@npm:^2.0.0": + version: 2.2.2 + resolution: "@npmcli/agent@npm:2.2.2" + dependencies: + agent-base: "npm:^7.1.0" + http-proxy-agent: "npm:^7.0.0" + https-proxy-agent: "npm:^7.0.1" + lru-cache: "npm:^10.0.1" + socks-proxy-agent: "npm:^8.0.3" + checksum: 10c0/325e0db7b287d4154ecd164c0815c08007abfb07653cc57bceded17bb7fd240998a3cbdbe87d700e30bef494885eccc725ab73b668020811d56623d145b524ae + languageName: node + linkType: hard + +"@npmcli/fs@npm:^3.1.0": + version: 3.1.1 + resolution: "@npmcli/fs@npm:3.1.1" + dependencies: + semver: "npm:^7.3.5" + checksum: 10c0/c37a5b4842bfdece3d14dfdb054f73fe15ed2d3da61b34ff76629fb5b1731647c49166fd2a8bf8b56fcfa51200382385ea8909a3cbecdad612310c114d3f6c99 + languageName: node + linkType: hard + +"@pkgjs/parseargs@npm:^0.11.0": + version: 0.11.0 + resolution: "@pkgjs/parseargs@npm:0.11.0" + checksum: 10c0/5bd7576bb1b38a47a7fc7b51ac9f38748e772beebc56200450c4a817d712232b8f1d3ef70532c80840243c657d491cf6a6be1e3a214cff907645819fdc34aadd + languageName: node + linkType: hard + +"@sinclair/typebox@npm:^0.27.8": + version: 0.27.8 + resolution: "@sinclair/typebox@npm:0.27.8" + checksum: 10c0/ef6351ae073c45c2ac89494dbb3e1f87cc60a93ce4cde797b782812b6f97da0d620ae81973f104b43c9b7eaa789ad20ba4f6a1359f1cc62f63729a55a7d22d4e + languageName: node + linkType: hard + +"@sinonjs/commons@npm:^3.0.0": + version: 3.0.1 + resolution: "@sinonjs/commons@npm:3.0.1" + dependencies: + type-detect: "npm:4.0.8" + checksum: 10c0/1227a7b5bd6c6f9584274db996d7f8cee2c8c350534b9d0141fc662eaf1f292ea0ae3ed19e5e5271c8fd390d27e492ca2803acd31a1978be2cdc6be0da711403 + languageName: node + linkType: hard + +"@sinonjs/fake-timers@npm:^10.0.2": + version: 10.3.0 + resolution: "@sinonjs/fake-timers@npm:10.3.0" + dependencies: + "@sinonjs/commons": "npm:^3.0.0" + checksum: 10c0/2e2fb6cc57f227912814085b7b01fede050cd4746ea8d49a1e44d5a0e56a804663b0340ae2f11af7559ea9bf4d087a11f2f646197a660ea3cb04e19efc04aa63 + languageName: node + linkType: hard + +"@types/babel__core@npm:^7.1.14": + version: 7.20.5 + resolution: "@types/babel__core@npm:7.20.5" + dependencies: + "@babel/parser": "npm:^7.20.7" + "@babel/types": "npm:^7.20.7" + "@types/babel__generator": "npm:*" + "@types/babel__template": "npm:*" + "@types/babel__traverse": "npm:*" + checksum: 10c0/bdee3bb69951e833a4b811b8ee9356b69a61ed5b7a23e1a081ec9249769117fa83aaaf023bb06562a038eb5845155ff663e2d5c75dd95c1d5ccc91db012868ff + languageName: node + linkType: hard + +"@types/babel__generator@npm:*": + version: 7.6.8 + resolution: "@types/babel__generator@npm:7.6.8" + dependencies: + "@babel/types": "npm:^7.0.0" + checksum: 10c0/f0ba105e7d2296bf367d6e055bb22996886c114261e2cb70bf9359556d0076c7a57239d019dee42bb063f565bade5ccb46009bce2044b2952d964bf9a454d6d2 + languageName: node + linkType: hard + +"@types/babel__template@npm:*": + version: 7.4.4 + resolution: "@types/babel__template@npm:7.4.4" + dependencies: + "@babel/parser": "npm:^7.1.0" + "@babel/types": "npm:^7.0.0" + checksum: 10c0/cc84f6c6ab1eab1427e90dd2b76ccee65ce940b778a9a67be2c8c39e1994e6f5bbc8efa309f6cea8dc6754994524cd4d2896558df76d92e7a1f46ecffee7112b + languageName: node + linkType: hard + +"@types/babel__traverse@npm:*, @types/babel__traverse@npm:^7.0.6": + version: 7.20.6 + resolution: "@types/babel__traverse@npm:7.20.6" + dependencies: + "@babel/types": "npm:^7.20.7" + checksum: 10c0/7ba7db61a53e28cac955aa99af280d2600f15a8c056619c05b6fc911cbe02c61aa4f2823299221b23ce0cce00b294c0e5f618ec772aa3f247523c2e48cf7b888 + languageName: node + linkType: hard + +"@types/bcrypt@npm:^5.0.2": + version: 5.0.2 + resolution: "@types/bcrypt@npm:5.0.2" + dependencies: + "@types/node": "npm:*" + checksum: 10c0/dd7f05e183b9b1fc08ec499069febf197ab8e9c720766b5bbb5628395082e248f9a444c60882fe7788361fcadc302e21e055ab9c26a300f100e08791c353e6aa + languageName: node + linkType: hard + +"@types/better-sqlite3@npm:^7.6.12": + version: 7.6.12 + resolution: "@types/better-sqlite3@npm:7.6.12" + dependencies: + "@types/node": "npm:*" + checksum: 10c0/5367de7492e2c697aa20cc4024ba26210971d15f60c01ef691eddfbbfd39ccf9f80d5129fd7fd6c76c98804739325e23d2b156b0eac8f5a7665ba374a08ac1e7 + languageName: node + linkType: hard + +"@types/body-parser@npm:*": + version: 1.19.5 + resolution: "@types/body-parser@npm:1.19.5" + dependencies: + "@types/connect": "npm:*" + "@types/node": "npm:*" + checksum: 10c0/aebeb200f25e8818d8cf39cd0209026750d77c9b85381cdd8deeb50913e4d18a1ebe4b74ca9b0b4d21952511eeaba5e9fbbf739b52731a2061e206ec60d568df + languageName: node + linkType: hard + +"@types/connect@npm:*": + version: 3.4.38 + resolution: "@types/connect@npm:3.4.38" + dependencies: + "@types/node": "npm:*" + checksum: 10c0/2e1cdba2c410f25649e77856505cd60223250fa12dff7a503e492208dbfdd25f62859918f28aba95315251fd1f5e1ffbfca1e25e73037189ab85dd3f8d0a148c + languageName: node + linkType: hard + +"@types/cookiejar@npm:^2.1.5": + version: 2.1.5 + resolution: "@types/cookiejar@npm:2.1.5" + checksum: 10c0/af38c3d84aebb3ccc6e46fb6afeeaac80fb26e63a487dd4db5a8b87e6ad3d4b845ba1116b2ae90d6f886290a36200fa433d8b1f6fe19c47da6b81872ce9a2764 + languageName: node + linkType: hard + +"@types/cors@npm:^2.8.13": + version: 2.8.17 + resolution: "@types/cors@npm:2.8.17" + dependencies: + "@types/node": "npm:*" + checksum: 10c0/457364c28c89f3d9ed34800e1de5c6eaaf344d1bb39af122f013322a50bc606eb2aa6f63de4e41a7a08ba7ef454473926c94a830636723da45bf786df032696d + languageName: node + linkType: hard + +"@types/express-actuator@npm:^1.8.0": + version: 1.8.3 + resolution: "@types/express-actuator@npm:1.8.3" + dependencies: + "@types/express": "npm:*" + checksum: 10c0/e7a5ffae28aa89c636edc75594adbf63bc3a0f86d27d0bdd4567e8ac91d6de8d0b76b85009ab65a9714625cfc443374eab227b11e58cb4450093500123eef0a2 + languageName: node + linkType: hard + +"@types/express-serve-static-core@npm:^4.17.33": + version: 4.19.3 + resolution: "@types/express-serve-static-core@npm:4.19.3" + dependencies: + "@types/node": "npm:*" + "@types/qs": "npm:*" + "@types/range-parser": "npm:*" + "@types/send": "npm:*" + checksum: 10c0/5d2a1fb96a17a8e0e8c59325dfeb6d454bbc5c9b9b6796eec0397ddf9dbd262892040d5da3d72b5d7148f34bb3fcd438faf1b37fcba8c5a03e75fae491ad1edf + languageName: node + linkType: hard + +"@types/express@npm:*, @types/express@npm:^4.17.17": + version: 4.17.21 + resolution: "@types/express@npm:4.17.21" + dependencies: + "@types/body-parser": "npm:*" + "@types/express-serve-static-core": "npm:^4.17.33" + "@types/qs": "npm:*" + "@types/serve-static": "npm:*" + checksum: 10c0/12e562c4571da50c7d239e117e688dc434db1bac8be55613294762f84fd77fbd0658ccd553c7d3ab02408f385bc93980992369dd30e2ecd2c68c358e6af8fabf + languageName: node + linkType: hard + +"@types/graceful-fs@npm:^4.1.3": + version: 4.1.9 + resolution: "@types/graceful-fs@npm:4.1.9" + dependencies: + "@types/node": "npm:*" + checksum: 10c0/235d2fc69741448e853333b7c3d1180a966dd2b8972c8cbcd6b2a0c6cd7f8d582ab2b8e58219dbc62cce8f1b40aa317ff78ea2201cdd8249da5025adebed6f0b + languageName: node + linkType: hard + +"@types/http-errors@npm:*": + version: 2.0.4 + resolution: "@types/http-errors@npm:2.0.4" + checksum: 10c0/494670a57ad4062fee6c575047ad5782506dd35a6b9ed3894cea65830a94367bd84ba302eb3dde331871f6d70ca287bfedb1b2cf658e6132cd2cbd427ab56836 + languageName: node + linkType: hard + +"@types/istanbul-lib-coverage@npm:*, @types/istanbul-lib-coverage@npm:^2.0.0, @types/istanbul-lib-coverage@npm:^2.0.1": + version: 2.0.6 + resolution: "@types/istanbul-lib-coverage@npm:2.0.6" + checksum: 10c0/3948088654f3eeb45363f1db158354fb013b362dba2a5c2c18c559484d5eb9f6fd85b23d66c0a7c2fcfab7308d0a585b14dadaca6cc8bf89ebfdc7f8f5102fb7 + languageName: node + linkType: hard + +"@types/istanbul-lib-report@npm:*": + version: 3.0.3 + resolution: "@types/istanbul-lib-report@npm:3.0.3" + dependencies: + "@types/istanbul-lib-coverage": "npm:*" + checksum: 10c0/247e477bbc1a77248f3c6de5dadaae85ff86ac2d76c5fc6ab1776f54512a745ff2a5f791d22b942e3990ddbd40f3ef5289317c4fca5741bedfaa4f01df89051c + languageName: node + linkType: hard + +"@types/istanbul-reports@npm:^3.0.0": + version: 3.0.4 + resolution: "@types/istanbul-reports@npm:3.0.4" + dependencies: + "@types/istanbul-lib-report": "npm:*" + checksum: 10c0/1647fd402aced5b6edac87274af14ebd6b3a85447ef9ad11853a70fd92a98d35f81a5d3ea9fcb5dbb5834e800c6e35b64475e33fcae6bfa9acc70d61497c54ee + languageName: node + linkType: hard + +"@types/jest@npm:^29.2.3": + version: 29.5.12 + resolution: "@types/jest@npm:29.5.12" + dependencies: + expect: "npm:^29.0.0" + pretty-format: "npm:^29.0.0" + checksum: 10c0/25fc8e4c611fa6c4421e631432e9f0a6865a8cb07c9815ec9ac90d630271cad773b2ee5fe08066f7b95bebd18bb967f8ce05d018ee9ab0430f9dfd1d84665b6f + languageName: node + linkType: hard + +"@types/json-schema@npm:^7.0.9": + version: 7.0.15 + resolution: "@types/json-schema@npm:7.0.15" + checksum: 10c0/a996a745e6c5d60292f36731dd41341339d4eeed8180bb09226e5c8d23759067692b1d88e5d91d72ee83dfc00d3aca8e7bd43ea120516c17922cbcb7c3e252db + languageName: node + linkType: hard + +"@types/methods@npm:^1.1.4": + version: 1.1.4 + resolution: "@types/methods@npm:1.1.4" + checksum: 10c0/a78534d79c300718298bfff92facd07bf38429c66191f640c1db4c9cff1e36f819304298a96f7536b6512bfc398e5c3e6b831405e138cd774b88ad7be78d682a + languageName: node + linkType: hard + +"@types/mime@npm:^1": + version: 1.3.5 + resolution: "@types/mime@npm:1.3.5" + checksum: 10c0/c2ee31cd9b993804df33a694d5aa3fa536511a49f2e06eeab0b484fef59b4483777dbb9e42a4198a0809ffbf698081fdbca1e5c2218b82b91603dfab10a10fbc + languageName: node + linkType: hard + +"@types/node@npm:*": + version: 20.14.5 + resolution: "@types/node@npm:20.14.5" + dependencies: + undici-types: "npm:~5.26.4" + checksum: 10c0/06a8c304b5f7f190d4497807dc67ad09ee7b14ea2996bfdc823553c624698d8cab1ef9d16f8b764f20cb9eb11caa0e832787741e9ef70e1c89d620797ab28436 + languageName: node + linkType: hard + +"@types/node@npm:^17.0.45": + version: 17.0.45 + resolution: "@types/node@npm:17.0.45" + checksum: 10c0/0db377133d709b33a47892581a21a41cd7958f22723a3cc6c71d55ac018121382de42fbfc7970d5ae3e7819dbe5f40e1c6a5174aedf7e7964e9cb8fa72b580b0 + languageName: node + linkType: hard + +"@types/qs@npm:*": + version: 6.9.15 + resolution: "@types/qs@npm:6.9.15" + checksum: 10c0/49c5ff75ca3adb18a1939310042d273c9fc55920861bd8e5100c8a923b3cda90d759e1a95e18334092da1c8f7b820084687770c83a1ccef04fb2c6908117c823 + languageName: node + linkType: hard + +"@types/range-parser@npm:*": + version: 1.2.7 + resolution: "@types/range-parser@npm:1.2.7" + checksum: 10c0/361bb3e964ec5133fa40644a0b942279ed5df1949f21321d77de79f48b728d39253e5ce0408c9c17e4e0fd95ca7899da36841686393b9f7a1e209916e9381a3c + languageName: node + linkType: hard + +"@types/semver@npm:^7.3.12": + version: 7.5.8 + resolution: "@types/semver@npm:7.5.8" + checksum: 10c0/8663ff927234d1c5fcc04b33062cb2b9fcfbe0f5f351ed26c4d1e1581657deebd506b41ff7fdf89e787e3d33ce05854bc01686379b89e9c49b564c4cfa988efa + languageName: node + linkType: hard + +"@types/send@npm:*": + version: 0.17.4 + resolution: "@types/send@npm:0.17.4" + dependencies: + "@types/mime": "npm:^1" + "@types/node": "npm:*" + checksum: 10c0/7f17fa696cb83be0a104b04b424fdedc7eaba1c9a34b06027239aba513b398a0e2b7279778af521f516a397ced417c96960e5f50fcfce40c4bc4509fb1a5883c + languageName: node + linkType: hard + +"@types/serve-static@npm:*": + version: 1.15.7 + resolution: "@types/serve-static@npm:1.15.7" + dependencies: + "@types/http-errors": "npm:*" + "@types/node": "npm:*" + "@types/send": "npm:*" + checksum: 10c0/26ec864d3a626ea627f8b09c122b623499d2221bbf2f470127f4c9ebfe92bd8a6bb5157001372d4c4bd0dd37a1691620217d9dc4df5aa8f779f3fd996b1c60ae + languageName: node + linkType: hard + +"@types/stack-utils@npm:^2.0.0": + version: 2.0.3 + resolution: "@types/stack-utils@npm:2.0.3" + checksum: 10c0/1f4658385ae936330581bcb8aa3a066df03867d90281cdf89cc356d404bd6579be0f11902304e1f775d92df22c6dd761d4451c804b0a4fba973e06211e9bd77c + languageName: node + linkType: hard + +"@types/superagent@npm:*": + version: 8.1.7 + resolution: "@types/superagent@npm:8.1.7" + dependencies: + "@types/cookiejar": "npm:^2.1.5" + "@types/methods": "npm:^1.1.4" + "@types/node": "npm:*" + checksum: 10c0/4676d539f5feaaea9d39d7409c86ae9e15b92a43c28456aff9d9897e47e9fe5ebd3807600c5310f84fe5ebea30f3fe5e2b3b101a87821a478ca79e3a56fd8c9e + languageName: node + linkType: hard + +"@types/supertest@npm:^2.0.12": + version: 2.0.16 + resolution: "@types/supertest@npm:2.0.16" + dependencies: + "@types/superagent": "npm:*" + checksum: 10c0/e1b4a4d788c19cd92a3f2e6d0979fb0f679c49aefae2011895a4d9c35aa960d43463aca8783a0b3382bbf0b4eb7ceaf8752d7dc80b8f5a9644fa14e1b1bdbc90 + languageName: node + linkType: hard + +"@types/triple-beam@npm:^1.3.2": + version: 1.3.5 + resolution: "@types/triple-beam@npm:1.3.5" + checksum: 10c0/d5d7f25da612f6d79266f4f1bb9c1ef8f1684e9f60abab251e1261170631062b656ba26ff22631f2760caeafd372abc41e64867cde27fba54fafb73a35b9056a + languageName: node + linkType: hard + +"@types/uuid@npm:^9.0.0": + version: 9.0.8 + resolution: "@types/uuid@npm:9.0.8" + checksum: 10c0/b411b93054cb1d4361919579ef3508a1f12bf15b5fdd97337d3d351bece6c921b52b6daeef89b62340fd73fd60da407878432a1af777f40648cbe53a01723489 + languageName: node + linkType: hard + +"@types/yargs-parser@npm:*": + version: 21.0.3 + resolution: "@types/yargs-parser@npm:21.0.3" + checksum: 10c0/e71c3bd9d0b73ca82e10bee2064c384ab70f61034bbfb78e74f5206283fc16a6d85267b606b5c22cb2a3338373586786fed595b2009825d6a9115afba36560a0 + languageName: node + linkType: hard + +"@types/yargs@npm:^17.0.8": + version: 17.0.32 + resolution: "@types/yargs@npm:17.0.32" + dependencies: + "@types/yargs-parser": "npm:*" + checksum: 10c0/2095e8aad8a4e66b86147415364266b8d607a3b95b4239623423efd7e29df93ba81bb862784a6e08664f645cc1981b25fd598f532019174cd3e5e1e689e1cccf + languageName: node + linkType: hard + +"@typescript-eslint/eslint-plugin@npm:^5.51.0": + version: 5.62.0 + resolution: "@typescript-eslint/eslint-plugin@npm:5.62.0" + dependencies: + "@eslint-community/regexpp": "npm:^4.4.0" + "@typescript-eslint/scope-manager": "npm:5.62.0" + "@typescript-eslint/type-utils": "npm:5.62.0" + "@typescript-eslint/utils": "npm:5.62.0" + debug: "npm:^4.3.4" + graphemer: "npm:^1.4.0" + ignore: "npm:^5.2.0" + natural-compare-lite: "npm:^1.4.0" + semver: "npm:^7.3.7" + tsutils: "npm:^3.21.0" + peerDependencies: + "@typescript-eslint/parser": ^5.0.0 + eslint: ^6.0.0 || ^7.0.0 || ^8.0.0 + peerDependenciesMeta: + typescript: + optional: true + checksum: 10c0/3f40cb6bab5a2833c3544e4621b9fdacd8ea53420cadc1c63fac3b89cdf5c62be1e6b7bcf56976dede5db4c43830de298ced3db60b5494a3b961ca1b4bff9f2a + languageName: node + linkType: hard + +"@typescript-eslint/parser@npm:^5.51.0": + version: 5.62.0 + resolution: "@typescript-eslint/parser@npm:5.62.0" + dependencies: + "@typescript-eslint/scope-manager": "npm:5.62.0" + "@typescript-eslint/types": "npm:5.62.0" + "@typescript-eslint/typescript-estree": "npm:5.62.0" + debug: "npm:^4.3.4" + peerDependencies: + eslint: ^6.0.0 || ^7.0.0 || ^8.0.0 + peerDependenciesMeta: + typescript: + optional: true + checksum: 10c0/315194b3bf39beb9bd16c190956c46beec64b8371e18d6bb72002108b250983eb1e186a01d34b77eb4045f4941acbb243b16155fbb46881105f65e37dc9e24d4 + languageName: node + linkType: hard + +"@typescript-eslint/scope-manager@npm:5.62.0": + version: 5.62.0 + resolution: "@typescript-eslint/scope-manager@npm:5.62.0" + dependencies: + "@typescript-eslint/types": "npm:5.62.0" + "@typescript-eslint/visitor-keys": "npm:5.62.0" + checksum: 10c0/861253235576c1c5c1772d23cdce1418c2da2618a479a7de4f6114a12a7ca853011a1e530525d0931c355a8fd237b9cd828fac560f85f9623e24054fd024726f + languageName: node + linkType: hard + +"@typescript-eslint/type-utils@npm:5.62.0": + version: 5.62.0 + resolution: "@typescript-eslint/type-utils@npm:5.62.0" + dependencies: + "@typescript-eslint/typescript-estree": "npm:5.62.0" + "@typescript-eslint/utils": "npm:5.62.0" + debug: "npm:^4.3.4" + tsutils: "npm:^3.21.0" + peerDependencies: + eslint: "*" + peerDependenciesMeta: + typescript: + optional: true + checksum: 10c0/93112e34026069a48f0484b98caca1c89d9707842afe14e08e7390af51cdde87378df29d213d3bbd10a7cfe6f91b228031b56218515ce077bdb62ddea9d9f474 + languageName: node + linkType: hard + +"@typescript-eslint/types@npm:5.62.0": + version: 5.62.0 + resolution: "@typescript-eslint/types@npm:5.62.0" + checksum: 10c0/7febd3a7f0701c0b927e094f02e82d8ee2cada2b186fcb938bc2b94ff6fbad88237afc304cbaf33e82797078bbbb1baf91475f6400912f8b64c89be79bfa4ddf + languageName: node + linkType: hard + +"@typescript-eslint/typescript-estree@npm:5.62.0": + version: 5.62.0 + resolution: "@typescript-eslint/typescript-estree@npm:5.62.0" + dependencies: + "@typescript-eslint/types": "npm:5.62.0" + "@typescript-eslint/visitor-keys": "npm:5.62.0" + debug: "npm:^4.3.4" + globby: "npm:^11.1.0" + is-glob: "npm:^4.0.3" + semver: "npm:^7.3.7" + tsutils: "npm:^3.21.0" + peerDependenciesMeta: + typescript: + optional: true + checksum: 10c0/d7984a3e9d56897b2481940ec803cb8e7ead03df8d9cfd9797350be82ff765dfcf3cfec04e7355e1779e948da8f02bc5e11719d07a596eb1cb995c48a95e38cf + languageName: node + linkType: hard + +"@typescript-eslint/utils@npm:5.62.0": + version: 5.62.0 + resolution: "@typescript-eslint/utils@npm:5.62.0" + dependencies: + "@eslint-community/eslint-utils": "npm:^4.2.0" + "@types/json-schema": "npm:^7.0.9" + "@types/semver": "npm:^7.3.12" + "@typescript-eslint/scope-manager": "npm:5.62.0" + "@typescript-eslint/types": "npm:5.62.0" + "@typescript-eslint/typescript-estree": "npm:5.62.0" + eslint-scope: "npm:^5.1.1" + semver: "npm:^7.3.7" + peerDependencies: + eslint: ^6.0.0 || ^7.0.0 || ^8.0.0 + checksum: 10c0/f09b7d9952e4a205eb1ced31d7684dd55cee40bf8c2d78e923aa8a255318d97279825733902742c09d8690f37a50243f4c4d383ab16bd7aefaf9c4b438f785e1 + languageName: node + linkType: hard + +"@typescript-eslint/visitor-keys@npm:5.62.0": + version: 5.62.0 + resolution: "@typescript-eslint/visitor-keys@npm:5.62.0" + dependencies: + "@typescript-eslint/types": "npm:5.62.0" + eslint-visitor-keys: "npm:^3.3.0" + checksum: 10c0/7c3b8e4148e9b94d9b7162a596a1260d7a3efc4e65199693b8025c71c4652b8042501c0bc9f57654c1e2943c26da98c0f77884a746c6ae81389fcb0b513d995d + languageName: node + linkType: hard + +"@ungap/structured-clone@npm:^1.2.0": + version: 1.2.0 + resolution: "@ungap/structured-clone@npm:1.2.0" + checksum: 10c0/8209c937cb39119f44eb63cf90c0b73e7c754209a6411c707be08e50e29ee81356dca1a848a405c8bdeebfe2f5e4f831ad310ae1689eeef65e7445c090c6657d + languageName: node + linkType: hard + +"abbrev@npm:1": + version: 1.1.1 + resolution: "abbrev@npm:1.1.1" + checksum: 10c0/3f762677702acb24f65e813070e306c61fafe25d4b2583f9dfc935131f774863f3addd5741572ed576bd69cabe473c5af18e1e108b829cb7b6b4747884f726e6 + languageName: node + linkType: hard + +"abbrev@npm:^2.0.0": + version: 2.0.0 + resolution: "abbrev@npm:2.0.0" + checksum: 10c0/f742a5a107473946f426c691c08daba61a1d15942616f300b5d32fd735be88fef5cba24201757b6c407fd564555fb48c751cfa33519b2605c8a7aadd22baf372 + languageName: node + linkType: hard + +"accepts@npm:~1.3.8": + version: 1.3.8 + resolution: "accepts@npm:1.3.8" + dependencies: + mime-types: "npm:~2.1.34" + negotiator: "npm:0.6.3" + checksum: 10c0/3a35c5f5586cfb9a21163ca47a5f77ac34fa8ceb5d17d2fa2c0d81f41cbd7f8c6fa52c77e2c039acc0f4d09e71abdc51144246900f6bef5e3c4b333f77d89362 + languageName: node + linkType: hard + +"acorn-jsx@npm:^5.3.2": + version: 5.3.2 + resolution: "acorn-jsx@npm:5.3.2" + peerDependencies: + acorn: ^6.0.0 || ^7.0.0 || ^8.0.0 + checksum: 10c0/4c54868fbef3b8d58927d5e33f0a4de35f59012fe7b12cf9dfbb345fb8f46607709e1c4431be869a23fb63c151033d84c4198fa9f79385cec34fcb1dd53974c1 + languageName: node + linkType: hard + +"acorn@npm:^8.9.0": + version: 8.12.0 + resolution: "acorn@npm:8.12.0" + bin: + acorn: bin/acorn + checksum: 10c0/a19f9dead009d3b430fa3c253710b47778cdaace15b316de6de93a68c355507bc1072a9956372b6c990cbeeb167d4a929249d0faeb8ae4bb6911d68d53299549 + languageName: node + linkType: hard + +"actual-sync@workspace:.": + version: 0.0.0-use.local + resolution: "actual-sync@workspace:." + dependencies: + "@actual-app/crdt": "npm:2.1.0" + "@actual-app/web": "npm:25.1.0" + "@babel/preset-typescript": "npm:^7.20.2" + "@types/bcrypt": "npm:^5.0.2" + "@types/better-sqlite3": "npm:^7.6.12" + "@types/cors": "npm:^2.8.13" + "@types/express": "npm:^4.17.17" + "@types/express-actuator": "npm:^1.8.0" + "@types/jest": "npm:^29.2.3" + "@types/node": "npm:^17.0.45" + "@types/supertest": "npm:^2.0.12" + "@types/uuid": "npm:^9.0.0" + "@typescript-eslint/eslint-plugin": "npm:^5.51.0" + "@typescript-eslint/parser": "npm:^5.51.0" + bcrypt: "npm:^5.1.1" + better-sqlite3: "npm:^11.7.0" + body-parser: "npm:^1.20.3" + cors: "npm:^2.8.5" + date-fns: "npm:^2.30.0" + debug: "npm:^4.3.4" + eslint: "npm:^8.33.0" + eslint-plugin-prettier: "npm:^4.2.1" + express: "npm:4.20.0" + express-actuator: "npm:1.8.4" + express-rate-limit: "npm:^6.7.0" + express-response-size: "npm:^0.0.3" + express-winston: "npm:^4.2.0" + jest: "npm:^29.3.1" + jws: "npm:^4.0.0" + migrate: "npm:^2.0.1" + nordigen-node: "npm:^1.4.0" + openid-client: "npm:^5.4.2" + prettier: "npm:^2.8.3" + supertest: "npm:^6.3.1" + typescript: "npm:^4.9.5" + uuid: "npm:^9.0.0" + winston: "npm:^3.14.2" + languageName: unknown + linkType: soft + +"agent-base@npm:6": + version: 6.0.2 + resolution: "agent-base@npm:6.0.2" + dependencies: + debug: "npm:4" + checksum: 10c0/dc4f757e40b5f3e3d674bc9beb4f1048f4ee83af189bae39be99f57bf1f48dde166a8b0a5342a84b5944ee8e6ed1e5a9d801858f4ad44764e84957122fe46261 + languageName: node + linkType: hard + +"agent-base@npm:^7.0.2, agent-base@npm:^7.1.0, agent-base@npm:^7.1.1": + version: 7.1.1 + resolution: "agent-base@npm:7.1.1" + dependencies: + debug: "npm:^4.3.4" + checksum: 10c0/e59ce7bed9c63bf071a30cc471f2933862044c97fd9958967bfe22521d7a0f601ce4ed5a8c011799d0c726ca70312142ae193bbebb60f576b52be19d4a363b50 + languageName: node + linkType: hard + +"aggregate-error@npm:^3.0.0": + version: 3.1.0 + resolution: "aggregate-error@npm:3.1.0" + dependencies: + clean-stack: "npm:^2.0.0" + indent-string: "npm:^4.0.0" + checksum: 10c0/a42f67faa79e3e6687a4923050e7c9807db3848a037076f791d10e092677d65c1d2d863b7848560699f40fc0502c19f40963fb1cd1fb3d338a7423df8e45e039 + languageName: node + linkType: hard + +"ajv@npm:^6.12.4": + version: 6.12.6 + resolution: "ajv@npm:6.12.6" + dependencies: + fast-deep-equal: "npm:^3.1.1" + fast-json-stable-stringify: "npm:^2.0.0" + json-schema-traverse: "npm:^0.4.1" + uri-js: "npm:^4.2.2" + checksum: 10c0/41e23642cbe545889245b9d2a45854ebba51cda6c778ebced9649420d9205f2efb39cb43dbc41e358409223b1ea43303ae4839db682c848b891e4811da1a5a71 + languageName: node + linkType: hard + +"ansi-escapes@npm:^4.2.1": + version: 4.3.2 + resolution: "ansi-escapes@npm:4.3.2" + dependencies: + type-fest: "npm:^0.21.3" + checksum: 10c0/da917be01871525a3dfcf925ae2977bc59e8c513d4423368645634bf5d4ceba5401574eb705c1e92b79f7292af5a656f78c5725a4b0e1cec97c4b413705c1d50 + languageName: node + linkType: hard + +"ansi-regex@npm:^5.0.1": + version: 5.0.1 + resolution: "ansi-regex@npm:5.0.1" + checksum: 10c0/9a64bb8627b434ba9327b60c027742e5d17ac69277960d041898596271d992d4d52ba7267a63ca10232e29f6107fc8a835f6ce8d719b88c5f8493f8254813737 + languageName: node + linkType: hard + +"ansi-regex@npm:^6.0.1": + version: 6.0.1 + resolution: "ansi-regex@npm:6.0.1" + checksum: 10c0/cbe16dbd2c6b2735d1df7976a7070dd277326434f0212f43abf6d87674095d247968209babdaad31bb00882fa68807256ba9be340eec2f1004de14ca75f52a08 + languageName: node + linkType: hard + +"ansi-styles@npm:^3.2.1": + version: 3.2.1 + resolution: "ansi-styles@npm:3.2.1" + dependencies: + color-convert: "npm:^1.9.0" + checksum: 10c0/ece5a8ef069fcc5298f67e3f4771a663129abd174ea2dfa87923a2be2abf6cd367ef72ac87942da00ce85bd1d651d4cd8595aebdb1b385889b89b205860e977b + languageName: node + linkType: hard + +"ansi-styles@npm:^4.0.0, ansi-styles@npm:^4.1.0": + version: 4.3.0 + resolution: "ansi-styles@npm:4.3.0" + dependencies: + color-convert: "npm:^2.0.1" + checksum: 10c0/895a23929da416f2bd3de7e9cb4eabd340949328ab85ddd6e484a637d8f6820d485f53933446f5291c3b760cbc488beb8e88573dd0f9c7daf83dccc8fe81b041 + languageName: node + linkType: hard + +"ansi-styles@npm:^5.0.0": + version: 5.2.0 + resolution: "ansi-styles@npm:5.2.0" + checksum: 10c0/9c4ca80eb3c2fb7b33841c210d2f20807f40865d27008d7c3f707b7f95cab7d67462a565e2388ac3285b71cb3d9bb2173de8da37c57692a362885ec34d6e27df + languageName: node + linkType: hard + +"ansi-styles@npm:^6.1.0": + version: 6.2.1 + resolution: "ansi-styles@npm:6.2.1" + checksum: 10c0/5d1ec38c123984bcedd996eac680d548f31828bd679a66db2bdf11844634dde55fec3efa9c6bb1d89056a5e79c1ac540c4c784d592ea1d25028a92227d2f2d5c + languageName: node + linkType: hard + +"anymatch@npm:^3.0.3": + version: 3.1.3 + resolution: "anymatch@npm:3.1.3" + dependencies: + normalize-path: "npm:^3.0.0" + picomatch: "npm:^2.0.4" + checksum: 10c0/57b06ae984bc32a0d22592c87384cd88fe4511b1dd7581497831c56d41939c8a001b28e7b853e1450f2bf61992dfcaa8ae2d0d161a0a90c4fb631ef07098fbac + languageName: node + linkType: hard + +"aproba@npm:^1.0.3 || ^2.0.0": + version: 2.0.0 + resolution: "aproba@npm:2.0.0" + checksum: 10c0/d06e26384a8f6245d8c8896e138c0388824e259a329e0c9f196b4fa533c82502a6fd449586e3604950a0c42921832a458bb3aa0aa9f0ba449cfd4f50fd0d09b5 + languageName: node + linkType: hard + +"are-we-there-yet@npm:^2.0.0": + version: 2.0.0 + resolution: "are-we-there-yet@npm:2.0.0" + dependencies: + delegates: "npm:^1.0.0" + readable-stream: "npm:^3.6.0" + checksum: 10c0/375f753c10329153c8d66dc95e8f8b6c7cc2aa66e05cb0960bd69092b10dae22900cacc7d653ad11d26b3ecbdbfe1e8bfb6ccf0265ba8077a7d979970f16b99c + languageName: node + linkType: hard + +"argparse@npm:^1.0.7": + version: 1.0.10 + resolution: "argparse@npm:1.0.10" + dependencies: + sprintf-js: "npm:~1.0.2" + checksum: 10c0/b2972c5c23c63df66bca144dbc65d180efa74f25f8fd9b7d9a0a6c88ae839db32df3d54770dcb6460cf840d232b60695d1a6b1053f599d84e73f7437087712de + languageName: node + linkType: hard + +"argparse@npm:^2.0.1": + version: 2.0.1 + resolution: "argparse@npm:2.0.1" + checksum: 10c0/c5640c2d89045371c7cedd6a70212a04e360fd34d6edeae32f6952c63949e3525ea77dbec0289d8213a99bbaeab5abfa860b5c12cf88a2e6cf8106e90dd27a7e + languageName: node + linkType: hard + +"array-flatten@npm:1.1.1": + version: 1.1.1 + resolution: "array-flatten@npm:1.1.1" + checksum: 10c0/806966c8abb2f858b08f5324d9d18d7737480610f3bd5d3498aaae6eb5efdc501a884ba019c9b4a8f02ff67002058749d05548fd42fa8643f02c9c7f22198b91 + languageName: node + linkType: hard + +"array-union@npm:^2.1.0": + version: 2.1.0 + resolution: "array-union@npm:2.1.0" + checksum: 10c0/429897e68110374f39b771ec47a7161fc6a8fc33e196857c0a396dc75df0b5f65e4d046674db764330b6bb66b39ef48dd7c53b6a2ee75cfb0681e0c1a7033962 + languageName: node + linkType: hard + +"asap@npm:^2.0.0": + version: 2.0.6 + resolution: "asap@npm:2.0.6" + checksum: 10c0/c6d5e39fe1f15e4b87677460bd66b66050cd14c772269cee6688824c1410a08ab20254bb6784f9afb75af9144a9f9a7692d49547f4d19d715aeb7c0318f3136d + languageName: node + linkType: hard + +"async@npm:^3.2.3": + version: 3.2.5 + resolution: "async@npm:3.2.5" + checksum: 10c0/1408287b26c6db67d45cb346e34892cee555b8b59e6c68e6f8c3e495cad5ca13b4f218180e871f3c2ca30df4ab52693b66f2f6ff43644760cab0b2198bda79c1 + languageName: node + linkType: hard + +"asynckit@npm:^0.4.0": + version: 0.4.0 + resolution: "asynckit@npm:0.4.0" + checksum: 10c0/d73e2ddf20c4eb9337e1b3df1a0f6159481050a5de457c55b14ea2e5cb6d90bb69e004c9af54737a5ee0917fcf2c9e25de67777bbe58261847846066ba75bc9d + languageName: node + linkType: hard + +"axios@npm:^1.2.1": + version: 1.7.4 + resolution: "axios@npm:1.7.4" + dependencies: + follow-redirects: "npm:^1.15.6" + form-data: "npm:^4.0.0" + proxy-from-env: "npm:^1.1.0" + checksum: 10c0/5ea1a93140ca1d49db25ef8e1bd8cfc59da6f9220159a944168860ad15a2743ea21c5df2967795acb15cbe81362f5b157fdebbea39d53117ca27658bab9f7f17 + languageName: node + linkType: hard + +"babel-jest@npm:^29.7.0": + version: 29.7.0 + resolution: "babel-jest@npm:29.7.0" + dependencies: + "@jest/transform": "npm:^29.7.0" + "@types/babel__core": "npm:^7.1.14" + babel-plugin-istanbul: "npm:^6.1.1" + babel-preset-jest: "npm:^29.6.3" + chalk: "npm:^4.0.0" + graceful-fs: "npm:^4.2.9" + slash: "npm:^3.0.0" + peerDependencies: + "@babel/core": ^7.8.0 + checksum: 10c0/2eda9c1391e51936ca573dd1aedfee07b14c59b33dbe16ef347873ddd777bcf6e2fc739681e9e9661ab54ef84a3109a03725be2ac32cd2124c07ea4401cbe8c1 + languageName: node + linkType: hard + +"babel-plugin-istanbul@npm:^6.1.1": + version: 6.1.1 + resolution: "babel-plugin-istanbul@npm:6.1.1" + dependencies: + "@babel/helper-plugin-utils": "npm:^7.0.0" + "@istanbuljs/load-nyc-config": "npm:^1.0.0" + "@istanbuljs/schema": "npm:^0.1.2" + istanbul-lib-instrument: "npm:^5.0.4" + test-exclude: "npm:^6.0.0" + checksum: 10c0/1075657feb705e00fd9463b329921856d3775d9867c5054b449317d39153f8fbcebd3e02ebf00432824e647faff3683a9ca0a941325ef1afe9b3c4dd51b24beb + languageName: node + linkType: hard + +"babel-plugin-jest-hoist@npm:^29.6.3": + version: 29.6.3 + resolution: "babel-plugin-jest-hoist@npm:29.6.3" + dependencies: + "@babel/template": "npm:^7.3.3" + "@babel/types": "npm:^7.3.3" + "@types/babel__core": "npm:^7.1.14" + "@types/babel__traverse": "npm:^7.0.6" + checksum: 10c0/7e6451caaf7dce33d010b8aafb970e62f1b0c0b57f4978c37b0d457bbcf0874d75a395a102daf0bae0bd14eafb9f6e9a165ee5e899c0a4f1f3bb2e07b304ed2e + languageName: node + linkType: hard + +"babel-preset-current-node-syntax@npm:^1.0.0": + version: 1.0.1 + resolution: "babel-preset-current-node-syntax@npm:1.0.1" + dependencies: + "@babel/plugin-syntax-async-generators": "npm:^7.8.4" + "@babel/plugin-syntax-bigint": "npm:^7.8.3" + "@babel/plugin-syntax-class-properties": "npm:^7.8.3" + "@babel/plugin-syntax-import-meta": "npm:^7.8.3" + "@babel/plugin-syntax-json-strings": "npm:^7.8.3" + "@babel/plugin-syntax-logical-assignment-operators": "npm:^7.8.3" + "@babel/plugin-syntax-nullish-coalescing-operator": "npm:^7.8.3" + "@babel/plugin-syntax-numeric-separator": "npm:^7.8.3" + "@babel/plugin-syntax-object-rest-spread": "npm:^7.8.3" + "@babel/plugin-syntax-optional-catch-binding": "npm:^7.8.3" + "@babel/plugin-syntax-optional-chaining": "npm:^7.8.3" + "@babel/plugin-syntax-top-level-await": "npm:^7.8.3" + peerDependencies: + "@babel/core": ^7.0.0 + checksum: 10c0/5ba39a3a0e6c37d25e56a4fb843be632dac98d54706d8a0933f9bcb1a07987a96d55c2b5a6c11788a74063fb2534fe68c1f1dbb6c93626850c785e0938495627 + languageName: node + linkType: hard + +"babel-preset-jest@npm:^29.6.3": + version: 29.6.3 + resolution: "babel-preset-jest@npm:29.6.3" + dependencies: + babel-plugin-jest-hoist: "npm:^29.6.3" + babel-preset-current-node-syntax: "npm:^1.0.0" + peerDependencies: + "@babel/core": ^7.0.0 + checksum: 10c0/ec5fd0276b5630b05f0c14bb97cc3815c6b31600c683ebb51372e54dcb776cff790bdeeabd5b8d01ede375a040337ccbf6a3ccd68d3a34219125945e167ad943 + languageName: node + linkType: hard + +"balanced-match@npm:^1.0.0": + version: 1.0.2 + resolution: "balanced-match@npm:1.0.2" + checksum: 10c0/9308baf0a7e4838a82bbfd11e01b1cb0f0cf2893bc1676c27c2a8c0e70cbae1c59120c3268517a8ae7fb6376b4639ef81ca22582611dbee4ed28df945134aaee + languageName: node + linkType: hard + +"base64-js@npm:^1.3.1": + version: 1.5.1 + resolution: "base64-js@npm:1.5.1" + checksum: 10c0/f23823513b63173a001030fae4f2dabe283b99a9d324ade3ad3d148e218134676f1ee8568c877cd79ec1c53158dcf2d2ba527a97c606618928ba99dd930102bf + languageName: node + linkType: hard + +"bcrypt@npm:^5.1.1": + version: 5.1.1 + resolution: "bcrypt@npm:5.1.1" + dependencies: + "@mapbox/node-pre-gyp": "npm:^1.0.11" + node-addon-api: "npm:^5.0.0" + checksum: 10c0/743231158c866bddc46f25eb8e9617fe38bc1a6f5f3052aba35e361d349b7f8fb80e96b45c48a4c23c45c29967ccd11c81cf31166454fc0ab019801c336cab40 + languageName: node + linkType: hard + +"better-sqlite3@npm:^11.7.0": + version: 11.7.0 + resolution: "better-sqlite3@npm:11.7.0" + dependencies: + bindings: "npm:^1.5.0" + node-gyp: "npm:latest" + prebuild-install: "npm:^7.1.1" + checksum: 10c0/66e78fb7e12f55dd78469efec7f6fcf69079e149e974be9ea24befac7c67b8fe0e23a419cae412ac4ea025c73841d8d54bd222eece1c007485e3f6bd56fd1c94 + languageName: node + linkType: hard + +"bindings@npm:^1.5.0": + version: 1.5.0 + resolution: "bindings@npm:1.5.0" + dependencies: + file-uri-to-path: "npm:1.0.0" + checksum: 10c0/3dab2491b4bb24124252a91e656803eac24292473e56554e35bbfe3cc1875332cfa77600c3bac7564049dc95075bf6fcc63a4609920ff2d64d0fe405fcf0d4ba + languageName: node + linkType: hard + +"bl@npm:^4.0.3": + version: 4.1.0 + resolution: "bl@npm:4.1.0" + dependencies: + buffer: "npm:^5.5.0" + inherits: "npm:^2.0.4" + readable-stream: "npm:^3.4.0" + checksum: 10c0/02847e1d2cb089c9dc6958add42e3cdeaf07d13f575973963335ac0fdece563a50ac770ac4c8fa06492d2dd276f6cc3b7f08c7cd9c7a7ad0f8d388b2a28def5f + languageName: node + linkType: hard + +"body-parser@npm:1.20.3, body-parser@npm:^1.20.3": + version: 1.20.3 + resolution: "body-parser@npm:1.20.3" + dependencies: + bytes: "npm:3.1.2" + content-type: "npm:~1.0.5" + debug: "npm:2.6.9" + depd: "npm:2.0.0" + destroy: "npm:1.2.0" + http-errors: "npm:2.0.0" + iconv-lite: "npm:0.4.24" + on-finished: "npm:2.4.1" + qs: "npm:6.13.0" + raw-body: "npm:2.5.2" + type-is: "npm:~1.6.18" + unpipe: "npm:1.0.0" + checksum: 10c0/0a9a93b7518f222885498dcecaad528cf010dd109b071bf471c93def4bfe30958b83e03496eb9c1ad4896db543d999bb62be1a3087294162a88cfa1b42c16310 + languageName: node + linkType: hard + +"brace-expansion@npm:^1.1.7": + version: 1.1.11 + resolution: "brace-expansion@npm:1.1.11" + dependencies: + balanced-match: "npm:^1.0.0" + concat-map: "npm:0.0.1" + checksum: 10c0/695a56cd058096a7cb71fb09d9d6a7070113c7be516699ed361317aca2ec169f618e28b8af352e02ab4233fb54eb0168460a40dc320bab0034b36ab59aaad668 + languageName: node + linkType: hard + +"brace-expansion@npm:^2.0.1": + version: 2.0.1 + resolution: "brace-expansion@npm:2.0.1" + dependencies: + balanced-match: "npm:^1.0.0" + checksum: 10c0/b358f2fe060e2d7a87aa015979ecea07f3c37d4018f8d6deb5bd4c229ad3a0384fe6029bb76cd8be63c81e516ee52d1a0673edbe2023d53a5191732ae3c3e49f + languageName: node + linkType: hard + +"braces@npm:^3.0.3": + version: 3.0.3 + resolution: "braces@npm:3.0.3" + dependencies: + fill-range: "npm:^7.1.1" + checksum: 10c0/7c6dfd30c338d2997ba77500539227b9d1f85e388a5f43220865201e407e076783d0881f2d297b9f80951b4c957fcf0b51c1d2d24227631643c3f7c284b0aa04 + languageName: node + linkType: hard + +"browserslist@npm:^4.22.2": + version: 4.23.1 + resolution: "browserslist@npm:4.23.1" + dependencies: + caniuse-lite: "npm:^1.0.30001629" + electron-to-chromium: "npm:^1.4.796" + node-releases: "npm:^2.0.14" + update-browserslist-db: "npm:^1.0.16" + bin: + browserslist: cli.js + checksum: 10c0/eb47c7ab9d60db25ce2faca70efeb278faa7282a2f62b7f2fa2f92e5f5251cf65144244566c86559419ff4f6d78f59ea50e39911321ad91f3b27788901f1f5e9 + languageName: node + linkType: hard + +"bser@npm:2.1.1": + version: 2.1.1 + resolution: "bser@npm:2.1.1" + dependencies: + node-int64: "npm:^0.4.0" + checksum: 10c0/24d8dfb7b6d457d73f32744e678a60cc553e4ec0e9e1a01cf614b44d85c3c87e188d3cc78ef0442ce5032ee6818de20a0162ba1074725c0d08908f62ea979227 + languageName: node + linkType: hard + +"buffer-equal-constant-time@npm:1.0.1": + version: 1.0.1 + resolution: "buffer-equal-constant-time@npm:1.0.1" + checksum: 10c0/fb2294e64d23c573d0dd1f1e7a466c3e978fe94a4e0f8183937912ca374619773bef8e2aceb854129d2efecbbc515bbd0cc78d2734a3e3031edb0888531bbc8e + languageName: node + linkType: hard + +"buffer-from@npm:^1.0.0": + version: 1.1.2 + resolution: "buffer-from@npm:1.1.2" + checksum: 10c0/124fff9d66d691a86d3b062eff4663fe437a9d9ee4b47b1b9e97f5a5d14f6d5399345db80f796827be7c95e70a8e765dd404b7c3ff3b3324f98e9b0c8826cc34 + languageName: node + linkType: hard + +"buffer@npm:^5.5.0": + version: 5.7.1 + resolution: "buffer@npm:5.7.1" + dependencies: + base64-js: "npm:^1.3.1" + ieee754: "npm:^1.1.13" + checksum: 10c0/27cac81cff434ed2876058d72e7c4789d11ff1120ef32c9de48f59eab58179b66710c488987d295ae89a228f835fc66d088652dffeb8e3ba8659f80eb091d55e + languageName: node + linkType: hard + +"bytes@npm:3.1.2": + version: 3.1.2 + resolution: "bytes@npm:3.1.2" + checksum: 10c0/76d1c43cbd602794ad8ad2ae94095cddeb1de78c5dddaa7005c51af10b0176c69971a6d88e805a90c2b6550d76636e43c40d8427a808b8645ede885de4a0358e + languageName: node + linkType: hard + +"cacache@npm:^18.0.0": + version: 18.0.3 + resolution: "cacache@npm:18.0.3" + dependencies: + "@npmcli/fs": "npm:^3.1.0" + fs-minipass: "npm:^3.0.0" + glob: "npm:^10.2.2" + lru-cache: "npm:^10.0.1" + minipass: "npm:^7.0.3" + minipass-collect: "npm:^2.0.1" + minipass-flush: "npm:^1.0.5" + minipass-pipeline: "npm:^1.2.4" + p-map: "npm:^4.0.0" + ssri: "npm:^10.0.0" + tar: "npm:^6.1.11" + unique-filename: "npm:^3.0.0" + checksum: 10c0/dfda92840bb371fb66b88c087c61a74544363b37a265023223a99965b16a16bbb87661fe4948718d79df6e0cc04e85e62784fbcf1832b2a5e54ff4c46fbb45b7 + languageName: node + linkType: hard + +"call-bind@npm:^1.0.7": + version: 1.0.7 + resolution: "call-bind@npm:1.0.7" + dependencies: + es-define-property: "npm:^1.0.0" + es-errors: "npm:^1.3.0" + function-bind: "npm:^1.1.2" + get-intrinsic: "npm:^1.2.4" + set-function-length: "npm:^1.2.1" + checksum: 10c0/a3ded2e423b8e2a265983dba81c27e125b48eefb2655e7dfab6be597088da3d47c47976c24bc51b8fd9af1061f8f87b4ab78a314f3c77784b2ae2ba535ad8b8d + languageName: node + linkType: hard + +"callsites@npm:^3.0.0": + version: 3.1.0 + resolution: "callsites@npm:3.1.0" + checksum: 10c0/fff92277400eb06c3079f9e74f3af120db9f8ea03bad0e84d9aede54bbe2d44a56cccb5f6cf12211f93f52306df87077ecec5b712794c5a9b5dac6d615a3f301 + languageName: node + linkType: hard + +"camelcase@npm:^5.3.1": + version: 5.3.1 + resolution: "camelcase@npm:5.3.1" + checksum: 10c0/92ff9b443bfe8abb15f2b1513ca182d16126359ad4f955ebc83dc4ddcc4ef3fdd2c078bc223f2673dc223488e75c99b16cc4d056624374b799e6a1555cf61b23 + languageName: node + linkType: hard + +"camelcase@npm:^6.2.0": + version: 6.3.0 + resolution: "camelcase@npm:6.3.0" + checksum: 10c0/0d701658219bd3116d12da3eab31acddb3f9440790c0792e0d398f0a520a6a4058018e546862b6fba89d7ae990efaeb97da71e1913e9ebf5a8b5621a3d55c710 + languageName: node + linkType: hard + +"caniuse-lite@npm:^1.0.30001629": + version: 1.0.30001636 + resolution: "caniuse-lite@npm:1.0.30001636" + checksum: 10c0/e5f965b4da7bae1531fd9f93477d015729ff9e3fa12670ead39a9e6cdc4c43e62c272d47857c5cc332e7b02d697cb3f2f965a1030870ac7476da60c2fc81ee94 + languageName: node + linkType: hard + +"chalk@npm:^2.4.2": + version: 2.4.2 + resolution: "chalk@npm:2.4.2" + dependencies: + ansi-styles: "npm:^3.2.1" + escape-string-regexp: "npm:^1.0.5" + supports-color: "npm:^5.3.0" + checksum: 10c0/e6543f02ec877732e3a2d1c3c3323ddb4d39fbab687c23f526e25bd4c6a9bf3b83a696e8c769d078e04e5754921648f7821b2a2acfd16c550435fd630026e073 + languageName: node + linkType: hard + +"chalk@npm:^4.0.0, chalk@npm:^4.1.2": + version: 4.1.2 + resolution: "chalk@npm:4.1.2" + dependencies: + ansi-styles: "npm:^4.1.0" + supports-color: "npm:^7.1.0" + checksum: 10c0/4a3fef5cc34975c898ffe77141450f679721df9dde00f6c304353fa9c8b571929123b26a0e4617bde5018977eb655b31970c297b91b63ee83bb82aeb04666880 + languageName: node + linkType: hard + +"char-regex@npm:^1.0.2": + version: 1.0.2 + resolution: "char-regex@npm:1.0.2" + checksum: 10c0/57a09a86371331e0be35d9083ba429e86c4f4648ecbe27455dbfb343037c16ee6fdc7f6b61f433a57cc5ded5561d71c56a150e018f40c2ffb7bc93a26dae341e + languageName: node + linkType: hard + +"chownr@npm:^1.1.1": + version: 1.1.4 + resolution: "chownr@npm:1.1.4" + checksum: 10c0/ed57952a84cc0c802af900cf7136de643d3aba2eecb59d29344bc2f3f9bf703a301b9d84cdc71f82c3ffc9ccde831b0d92f5b45f91727d6c9da62f23aef9d9db + languageName: node + linkType: hard + +"chownr@npm:^2.0.0": + version: 2.0.0 + resolution: "chownr@npm:2.0.0" + checksum: 10c0/594754e1303672171cc04e50f6c398ae16128eb134a88f801bf5354fd96f205320f23536a045d9abd8b51024a149696e51231565891d4efdab8846021ecf88e6 + languageName: node + linkType: hard + +"ci-info@npm:^3.2.0": + version: 3.9.0 + resolution: "ci-info@npm:3.9.0" + checksum: 10c0/6f0109e36e111684291d46123d491bc4e7b7a1934c3a20dea28cba89f1d4a03acd892f5f6a81ed3855c38647e285a150e3c9ba062e38943bef57fee6c1554c3a + languageName: node + linkType: hard + +"cjs-module-lexer@npm:^1.0.0": + version: 1.3.1 + resolution: "cjs-module-lexer@npm:1.3.1" + checksum: 10c0/cd98fbf3c7f4272fb0ebf71d08d0c54bc75ce0e30b9d186114e15b4ba791f3d310af65a339eea2a0318599af2818cdd8886d353b43dfab94468f72987397ad16 + languageName: node + linkType: hard + +"clean-stack@npm:^2.0.0": + version: 2.2.0 + resolution: "clean-stack@npm:2.2.0" + checksum: 10c0/1f90262d5f6230a17e27d0c190b09d47ebe7efdd76a03b5a1127863f7b3c9aec4c3e6c8bb3a7bbf81d553d56a1fd35728f5a8ef4c63f867ac8d690109742a8c1 + languageName: node + linkType: hard + +"cliui@npm:^8.0.1": + version: 8.0.1 + resolution: "cliui@npm:8.0.1" + dependencies: + string-width: "npm:^4.2.0" + strip-ansi: "npm:^6.0.1" + wrap-ansi: "npm:^7.0.0" + checksum: 10c0/4bda0f09c340cbb6dfdc1ed508b3ca080f12992c18d68c6be4d9cf51756033d5266e61ec57529e610dacbf4da1c634423b0c1b11037709cc6b09045cbd815df5 + languageName: node + linkType: hard + +"co@npm:^4.6.0": + version: 4.6.0 + resolution: "co@npm:4.6.0" + checksum: 10c0/c0e85ea0ca8bf0a50cbdca82efc5af0301240ca88ebe3644a6ffb8ffe911f34d40f8fbcf8f1d52c5ddd66706abd4d3bfcd64259f1e8e2371d4f47573b0dc8c28 + languageName: node + linkType: hard + +"collect-v8-coverage@npm:^1.0.0": + version: 1.0.2 + resolution: "collect-v8-coverage@npm:1.0.2" + checksum: 10c0/ed7008e2e8b6852c5483b444a3ae6e976e088d4335a85aa0a9db2861c5f1d31bd2d7ff97a60469b3388deeba661a619753afbe201279fb159b4b9548ab8269a1 + languageName: node + linkType: hard + +"color-convert@npm:^1.9.0, color-convert@npm:^1.9.3": + version: 1.9.3 + resolution: "color-convert@npm:1.9.3" + dependencies: + color-name: "npm:1.1.3" + checksum: 10c0/5ad3c534949a8c68fca8fbc6f09068f435f0ad290ab8b2f76841b9e6af7e0bb57b98cb05b0e19fe33f5d91e5a8611ad457e5f69e0a484caad1f7487fd0e8253c + languageName: node + linkType: hard + +"color-convert@npm:^2.0.1": + version: 2.0.1 + resolution: "color-convert@npm:2.0.1" + dependencies: + color-name: "npm:~1.1.4" + checksum: 10c0/37e1150172f2e311fe1b2df62c6293a342ee7380da7b9cfdba67ea539909afbd74da27033208d01d6d5cfc65ee7868a22e18d7e7648e004425441c0f8a15a7d7 + languageName: node + linkType: hard + +"color-name@npm:1.1.3": + version: 1.1.3 + resolution: "color-name@npm:1.1.3" + checksum: 10c0/566a3d42cca25b9b3cd5528cd7754b8e89c0eb646b7f214e8e2eaddb69994ac5f0557d9c175eb5d8f0ad73531140d9c47525085ee752a91a2ab15ab459caf6d6 + languageName: node + linkType: hard + +"color-name@npm:^1.0.0, color-name@npm:~1.1.4": + version: 1.1.4 + resolution: "color-name@npm:1.1.4" + checksum: 10c0/a1a3f914156960902f46f7f56bc62effc6c94e84b2cae157a526b1c1f74b677a47ec602bf68a61abfa2b42d15b7c5651c6dbe72a43af720bc588dff885b10f95 + languageName: node + linkType: hard + +"color-string@npm:^1.6.0": + version: 1.9.1 + resolution: "color-string@npm:1.9.1" + dependencies: + color-name: "npm:^1.0.0" + simple-swizzle: "npm:^0.2.2" + checksum: 10c0/b0bfd74c03b1f837f543898b512f5ea353f71630ccdd0d66f83028d1f0924a7d4272deb278b9aef376cacf1289b522ac3fb175e99895283645a2dc3a33af2404 + languageName: node + linkType: hard + +"color-support@npm:^1.1.2": + version: 1.1.3 + resolution: "color-support@npm:1.1.3" + bin: + color-support: bin.js + checksum: 10c0/8ffeaa270a784dc382f62d9be0a98581db43e11eee301af14734a6d089bd456478b1a8b3e7db7ca7dc5b18a75f828f775c44074020b51c05fc00e6d0992b1cc6 + languageName: node + linkType: hard + +"color@npm:^3.1.3": + version: 3.2.1 + resolution: "color@npm:3.2.1" + dependencies: + color-convert: "npm:^1.9.3" + color-string: "npm:^1.6.0" + checksum: 10c0/39345d55825884c32a88b95127d417a2c24681d8b57069413596d9fcbb721459ef9d9ec24ce3e65527b5373ce171b73e38dbcd9c830a52a6487e7f37bf00e83c + languageName: node + linkType: hard + +"colorspace@npm:1.1.x": + version: 1.1.4 + resolution: "colorspace@npm:1.1.4" + dependencies: + color: "npm:^3.1.3" + text-hex: "npm:1.0.x" + checksum: 10c0/af5f91ff7f8e146b96e439ac20ed79b197210193bde721b47380a75b21751d90fa56390c773bb67c0aedd34ff85091883a437ab56861c779bd507d639ba7e123 + languageName: node + linkType: hard + +"combined-stream@npm:^1.0.8": + version: 1.0.8 + resolution: "combined-stream@npm:1.0.8" + dependencies: + delayed-stream: "npm:~1.0.0" + checksum: 10c0/0dbb829577e1b1e839fa82b40c07ffaf7de8a09b935cadd355a73652ae70a88b4320db322f6634a4ad93424292fa80973ac6480986247f1734a1137debf271d5 + languageName: node + linkType: hard + +"commander@npm:^2.20.3": + version: 2.20.3 + resolution: "commander@npm:2.20.3" + checksum: 10c0/74c781a5248c2402a0a3e966a0a2bba3c054aad144f5c023364be83265e796b20565aa9feff624132ff629aa64e16999fa40a743c10c12f7c61e96a794b99288 + languageName: node + linkType: hard + +"component-emitter@npm:^1.3.0": + version: 1.3.1 + resolution: "component-emitter@npm:1.3.1" + checksum: 10c0/e4900b1b790b5e76b8d71b328da41482118c0f3523a516a41be598dc2785a07fd721098d9bf6e22d89b19f4fa4e1025160dc00317ea111633a3e4f75c2b86032 + languageName: node + linkType: hard + +"concat-map@npm:0.0.1": + version: 0.0.1 + resolution: "concat-map@npm:0.0.1" + checksum: 10c0/c996b1cfdf95b6c90fee4dae37e332c8b6eb7d106430c17d538034c0ad9a1630cb194d2ab37293b1bdd4d779494beee7786d586a50bd9376fd6f7bcc2bd4c98f + languageName: node + linkType: hard + +"console-control-strings@npm:^1.0.0, console-control-strings@npm:^1.1.0": + version: 1.1.0 + resolution: "console-control-strings@npm:1.1.0" + checksum: 10c0/7ab51d30b52d461412cd467721bb82afe695da78fff8f29fe6f6b9cbaac9a2328e27a22a966014df9532100f6dd85370460be8130b9c677891ba36d96a343f50 + languageName: node + linkType: hard + +"content-disposition@npm:0.5.4": + version: 0.5.4 + resolution: "content-disposition@npm:0.5.4" + dependencies: + safe-buffer: "npm:5.2.1" + checksum: 10c0/bac0316ebfeacb8f381b38285dc691c9939bf0a78b0b7c2d5758acadad242d04783cee5337ba7d12a565a19075af1b3c11c728e1e4946de73c6ff7ce45f3f1bb + languageName: node + linkType: hard + +"content-type@npm:~1.0.4, content-type@npm:~1.0.5": + version: 1.0.5 + resolution: "content-type@npm:1.0.5" + checksum: 10c0/b76ebed15c000aee4678c3707e0860cb6abd4e680a598c0a26e17f0bfae723ec9cc2802f0ff1bc6e4d80603719010431d2231018373d4dde10f9ccff9dadf5af + languageName: node + linkType: hard + +"convert-source-map@npm:^2.0.0": + version: 2.0.0 + resolution: "convert-source-map@npm:2.0.0" + checksum: 10c0/8f2f7a27a1a011cc6cc88cc4da2d7d0cfa5ee0369508baae3d98c260bb3ac520691464e5bbe4ae7cdf09860c1d69ecc6f70c63c6e7c7f7e3f18ec08484dc7d9b + languageName: node + linkType: hard + +"cookie-signature@npm:1.0.6": + version: 1.0.6 + resolution: "cookie-signature@npm:1.0.6" + checksum: 10c0/b36fd0d4e3fef8456915fcf7742e58fbfcc12a17a018e0eb9501c9d5ef6893b596466f03b0564b81af29ff2538fd0aa4b9d54fe5ccbfb4c90ea50ad29fe2d221 + languageName: node + linkType: hard + +"cookie@npm:0.6.0": + version: 0.6.0 + resolution: "cookie@npm:0.6.0" + checksum: 10c0/f2318b31af7a31b4ddb4a678d024514df5e705f9be5909a192d7f116cfb6d45cbacf96a473fa733faa95050e7cff26e7832bb3ef94751592f1387b71c8956686 + languageName: node + linkType: hard + +"cookiejar@npm:^2.1.4": + version: 2.1.4 + resolution: "cookiejar@npm:2.1.4" + checksum: 10c0/2dae55611c6e1678f34d93984cbd4bda58f4fe3e5247cc4993f4a305cd19c913bbaf325086ed952e892108115073a747596453d3dc1c34947f47f731818b8ad1 + languageName: node + linkType: hard + +"cors@npm:^2.8.5": + version: 2.8.5 + resolution: "cors@npm:2.8.5" + dependencies: + object-assign: "npm:^4" + vary: "npm:^1" + checksum: 10c0/373702b7999409922da80de4a61938aabba6929aea5b6fd9096fefb9e8342f626c0ebd7507b0e8b0b311380744cc985f27edebc0a26e0ddb784b54e1085de761 + languageName: node + linkType: hard + +"create-jest@npm:^29.7.0": + version: 29.7.0 + resolution: "create-jest@npm:29.7.0" + dependencies: + "@jest/types": "npm:^29.6.3" + chalk: "npm:^4.0.0" + exit: "npm:^0.1.2" + graceful-fs: "npm:^4.2.9" + jest-config: "npm:^29.7.0" + jest-util: "npm:^29.7.0" + prompts: "npm:^2.0.1" + bin: + create-jest: bin/create-jest.js + checksum: 10c0/e7e54c280692470d3398f62a6238fd396327e01c6a0757002833f06d00afc62dd7bfe04ff2b9cd145264460e6b4d1eb8386f2925b7e567f97939843b7b0e812f + languageName: node + linkType: hard + +"cross-spawn@npm:^7.0.0, cross-spawn@npm:^7.0.2, cross-spawn@npm:^7.0.3": + version: 7.0.6 + resolution: "cross-spawn@npm:7.0.6" + dependencies: + path-key: "npm:^3.1.0" + shebang-command: "npm:^2.0.0" + which: "npm:^2.0.1" + checksum: 10c0/053ea8b2135caff68a9e81470e845613e374e7309a47731e81639de3eaeb90c3d01af0e0b44d2ab9d50b43467223b88567dfeb3262db942dc063b9976718ffc1 + languageName: node + linkType: hard + +"date-fns@npm:^2.30.0": + version: 2.30.0 + resolution: "date-fns@npm:2.30.0" + dependencies: + "@babel/runtime": "npm:^7.21.0" + checksum: 10c0/e4b521fbf22bc8c3db332bbfb7b094fd3e7627de0259a9d17c7551e2d2702608a7307a449206065916538e384f37b181565447ce2637ae09828427aed9cb5581 + languageName: node + linkType: hard + +"dateformat@npm:^4.6.3": + version: 4.6.3 + resolution: "dateformat@npm:4.6.3" + checksum: 10c0/e2023b905e8cfe2eb8444fb558562b524807a51cdfe712570f360f873271600b5c94aebffaf11efb285e2c072264a7cf243eadb68f3eba0f8cc85fb86cd25df6 + languageName: node + linkType: hard + +"dayjs@npm:^1.11.3": + version: 1.11.11 + resolution: "dayjs@npm:1.11.11" + checksum: 10c0/0131d10516b9945f05a57e13f4af49a6814de5573a494824e103131a3bbe4cc470b1aefe8e17e51f9a478a22cd116084be1ee5725cedb66ec4c3f9091202dc4b + languageName: node + linkType: hard + +"debug@npm:2.6.9": + version: 2.6.9 + resolution: "debug@npm:2.6.9" + dependencies: + ms: "npm:2.0.0" + checksum: 10c0/121908fb839f7801180b69a7e218a40b5a0b718813b886b7d6bdb82001b931c938e2941d1e4450f33a1b1df1da653f5f7a0440c197f29fbf8a6e9d45ff6ef589 + languageName: node + linkType: hard + +"debug@npm:4, debug@npm:^4.1.0, debug@npm:^4.1.1, debug@npm:^4.3.1, debug@npm:^4.3.2, debug@npm:^4.3.4": + version: 4.3.5 + resolution: "debug@npm:4.3.5" + dependencies: + ms: "npm:2.1.2" + peerDependenciesMeta: + supports-color: + optional: true + checksum: 10c0/082c375a2bdc4f4469c99f325ff458adad62a3fc2c482d59923c260cb08152f34e2659f72b3767db8bb2f21ca81a60a42d1019605a412132d7b9f59363a005cc + languageName: node + linkType: hard + +"decompress-response@npm:^6.0.0": + version: 6.0.0 + resolution: "decompress-response@npm:6.0.0" + dependencies: + mimic-response: "npm:^3.1.0" + checksum: 10c0/bd89d23141b96d80577e70c54fb226b2f40e74a6817652b80a116d7befb8758261ad073a8895648a29cc0a5947021ab66705cb542fa9c143c82022b27c5b175e + languageName: node + linkType: hard + +"dedent@npm:^1.0.0": + version: 1.5.3 + resolution: "dedent@npm:1.5.3" + peerDependencies: + babel-plugin-macros: ^3.1.0 + peerDependenciesMeta: + babel-plugin-macros: + optional: true + checksum: 10c0/d94bde6e6f780be4da4fd760288fcf755ec368872f4ac5218197200d86430aeb8d90a003a840bff1c20221188e3f23adced0119cb811c6873c70d0ac66d12832 + languageName: node + linkType: hard + +"deep-extend@npm:^0.6.0": + version: 0.6.0 + resolution: "deep-extend@npm:0.6.0" + checksum: 10c0/1c6b0abcdb901e13a44c7d699116d3d4279fdb261983122a3783e7273844d5f2537dc2e1c454a23fcf645917f93fbf8d07101c1d03c015a87faa662755212566 + languageName: node + linkType: hard + +"deep-is@npm:^0.1.3": + version: 0.1.4 + resolution: "deep-is@npm:0.1.4" + checksum: 10c0/7f0ee496e0dff14a573dc6127f14c95061b448b87b995fc96c017ce0a1e66af1675e73f1d6064407975bc4ea6ab679497a29fff7b5b9c4e99cb10797c1ad0b4c + languageName: node + linkType: hard + +"deepmerge@npm:^4.2.2": + version: 4.3.1 + resolution: "deepmerge@npm:4.3.1" + checksum: 10c0/e53481aaf1aa2c4082b5342be6b6d8ad9dfe387bc92ce197a66dea08bd4265904a087e75e464f14d1347cf2ac8afe1e4c16b266e0561cc5df29382d3c5f80044 + languageName: node + linkType: hard + +"define-data-property@npm:^1.1.4": + version: 1.1.4 + resolution: "define-data-property@npm:1.1.4" + dependencies: + es-define-property: "npm:^1.0.0" + es-errors: "npm:^1.3.0" + gopd: "npm:^1.0.1" + checksum: 10c0/dea0606d1483eb9db8d930d4eac62ca0fa16738b0b3e07046cddfacf7d8c868bbe13fa0cb263eb91c7d0d527960dc3f2f2471a69ed7816210307f6744fe62e37 + languageName: node + linkType: hard + +"delayed-stream@npm:~1.0.0": + version: 1.0.0 + resolution: "delayed-stream@npm:1.0.0" + checksum: 10c0/d758899da03392e6712f042bec80aa293bbe9e9ff1b2634baae6a360113e708b91326594c8a486d475c69d6259afb7efacdc3537bfcda1c6c648e390ce601b19 + languageName: node + linkType: hard + +"delegates@npm:^1.0.0": + version: 1.0.0 + resolution: "delegates@npm:1.0.0" + checksum: 10c0/ba05874b91148e1db4bf254750c042bf2215febd23a6d3cda2e64896aef79745fbd4b9996488bd3cafb39ce19dbce0fd6e3b6665275638befffe1c9b312b91b5 + languageName: node + linkType: hard + +"depd@npm:2.0.0": + version: 2.0.0 + resolution: "depd@npm:2.0.0" + checksum: 10c0/58bd06ec20e19529b06f7ad07ddab60e504d9e0faca4bd23079fac2d279c3594334d736508dc350e06e510aba5e22e4594483b3a6562ce7c17dd797f4cc4ad2c + languageName: node + linkType: hard + +"destroy@npm:1.2.0": + version: 1.2.0 + resolution: "destroy@npm:1.2.0" + checksum: 10c0/bd7633942f57418f5a3b80d5cb53898127bcf53e24cdf5d5f4396be471417671f0fee48a4ebe9a1e9defbde2a31280011af58a57e090ff822f589b443ed4e643 + languageName: node + linkType: hard + +"detect-libc@npm:^2.0.0": + version: 2.0.3 + resolution: "detect-libc@npm:2.0.3" + checksum: 10c0/88095bda8f90220c95f162bf92cad70bd0e424913e655c20578600e35b91edc261af27531cf160a331e185c0ced93944bc7e09939143225f56312d7fd800fdb7 + languageName: node + linkType: hard + +"detect-newline@npm:^3.0.0": + version: 3.1.0 + resolution: "detect-newline@npm:3.1.0" + checksum: 10c0/c38cfc8eeb9fda09febb44bcd85e467c970d4e3bf526095394e5a4f18bc26dd0cf6b22c69c1fa9969261521c593836db335c2795218f6d781a512aea2fb8209d + languageName: node + linkType: hard + +"dezalgo@npm:^1.0.4": + version: 1.0.4 + resolution: "dezalgo@npm:1.0.4" + dependencies: + asap: "npm:^2.0.0" + wrappy: "npm:1" + checksum: 10c0/8a870ed42eade9a397e6141fe5c025148a59ed52f1f28b1db5de216b4d57f0af7a257070c3af7ce3d5508c1ce9dd5009028a76f4b2cc9370dc56551d2355fad8 + languageName: node + linkType: hard + +"diff-sequences@npm:^29.6.3": + version: 29.6.3 + resolution: "diff-sequences@npm:29.6.3" + checksum: 10c0/32e27ac7dbffdf2fb0eb5a84efd98a9ad084fbabd5ac9abb8757c6770d5320d2acd172830b28c4add29bb873d59420601dfc805ac4064330ce59b1adfd0593b2 + languageName: node + linkType: hard + +"dir-glob@npm:^3.0.1": + version: 3.0.1 + resolution: "dir-glob@npm:3.0.1" + dependencies: + path-type: "npm:^4.0.0" + checksum: 10c0/dcac00920a4d503e38bb64001acb19df4efc14536ada475725e12f52c16777afdee4db827f55f13a908ee7efc0cb282e2e3dbaeeb98c0993dd93d1802d3bf00c + languageName: node + linkType: hard + +"doctrine@npm:^3.0.0": + version: 3.0.0 + resolution: "doctrine@npm:3.0.0" + dependencies: + esutils: "npm:^2.0.2" + checksum: 10c0/c96bdccabe9d62ab6fea9399fdff04a66e6563c1d6fb3a3a063e8d53c3bb136ba63e84250bbf63d00086a769ad53aef92d2bd483f03f837fc97b71cbee6b2520 + languageName: node + linkType: hard + +"dotenv@npm:^10.0.0": + version: 10.0.0 + resolution: "dotenv@npm:10.0.0" + checksum: 10c0/2d8d4ba64bfaff7931402aa5e8cbb8eba0acbc99fe9ae442300199af021079eafa7171ce90e150821a5cb3d74f0057721fbe7ec201a6044b68c8a7615f8c123f + languageName: node + linkType: hard + +"dotenv@npm:^16.0.0": + version: 16.4.5 + resolution: "dotenv@npm:16.4.5" + checksum: 10c0/48d92870076832af0418b13acd6e5a5a3e83bb00df690d9812e94b24aff62b88ade955ac99a05501305b8dc8f1b0ee7638b18493deb6fe93d680e5220936292f + languageName: node + linkType: hard + +"eastasianwidth@npm:^0.2.0": + version: 0.2.0 + resolution: "eastasianwidth@npm:0.2.0" + checksum: 10c0/26f364ebcdb6395f95124fda411f63137a4bfb5d3a06453f7f23dfe52502905bd84e0488172e0f9ec295fdc45f05c23d5d91baf16bd26f0fe9acd777a188dc39 + languageName: node + linkType: hard + +"ecdsa-sig-formatter@npm:1.0.11": + version: 1.0.11 + resolution: "ecdsa-sig-formatter@npm:1.0.11" + dependencies: + safe-buffer: "npm:^5.0.1" + checksum: 10c0/ebfbf19d4b8be938f4dd4a83b8788385da353d63307ede301a9252f9f7f88672e76f2191618fd8edfc2f24679236064176fab0b78131b161ee73daa37125408c + languageName: node + linkType: hard + +"ee-first@npm:1.1.1": + version: 1.1.1 + resolution: "ee-first@npm:1.1.1" + checksum: 10c0/b5bb125ee93161bc16bfe6e56c6b04de5ad2aa44234d8f644813cc95d861a6910903132b05093706de2b706599367c4130eb6d170f6b46895686b95f87d017b7 + languageName: node + linkType: hard + +"electron-to-chromium@npm:^1.4.796": + version: 1.4.805 + resolution: "electron-to-chromium@npm:1.4.805" + checksum: 10c0/90594849ebe1152c1c302183be7bf51642e24626e6d0332f8c56c5ad18d9fb821135e0ed9d0fcf3ec69422d774e48e6c226362be0d8c8efe6b0849225a28d53e + languageName: node + linkType: hard + +"emittery@npm:^0.13.1": + version: 0.13.1 + resolution: "emittery@npm:0.13.1" + checksum: 10c0/1573d0ae29ab34661b6c63251ff8f5facd24ccf6a823f19417ae8ba8c88ea450325788c67f16c99edec8de4b52ce93a10fe441ece389fd156e88ee7dab9bfa35 + languageName: node + linkType: hard + +"emoji-regex@npm:^8.0.0": + version: 8.0.0 + resolution: "emoji-regex@npm:8.0.0" + checksum: 10c0/b6053ad39951c4cf338f9092d7bfba448cdfd46fe6a2a034700b149ac9ffbc137e361cbd3c442297f86bed2e5f7576c1b54cc0a6bf8ef5106cc62f496af35010 + languageName: node + linkType: hard + +"emoji-regex@npm:^9.2.2": + version: 9.2.2 + resolution: "emoji-regex@npm:9.2.2" + checksum: 10c0/af014e759a72064cf66e6e694a7fc6b0ed3d8db680427b021a89727689671cefe9d04151b2cad51dbaf85d5ba790d061cd167f1cf32eb7b281f6368b3c181639 + languageName: node + linkType: hard + +"enabled@npm:2.0.x": + version: 2.0.0 + resolution: "enabled@npm:2.0.0" + checksum: 10c0/3b2c2af9bc7f8b9e291610f2dde4a75cf6ee52a68f4dd585482fbdf9a55d65388940e024e56d40bb03e05ef6671f5f53021fa8b72a20e954d7066ec28166713f + languageName: node + linkType: hard + +"encodeurl@npm:~1.0.2": + version: 1.0.2 + resolution: "encodeurl@npm:1.0.2" + checksum: 10c0/f6c2387379a9e7c1156c1c3d4f9cb7bb11cf16dd4c1682e1f6746512564b053df5781029b6061296832b59fb22f459dbe250386d217c2f6e203601abb2ee0bec + languageName: node + linkType: hard + +"encodeurl@npm:~2.0.0": + version: 2.0.0 + resolution: "encodeurl@npm:2.0.0" + checksum: 10c0/5d317306acb13e6590e28e27924c754163946a2480de11865c991a3a7eed4315cd3fba378b543ca145829569eefe9b899f3d84bb09870f675ae60bc924b01ceb + languageName: node + linkType: hard + +"encoding@npm:^0.1.13": + version: 0.1.13 + resolution: "encoding@npm:0.1.13" + dependencies: + iconv-lite: "npm:^0.6.2" + checksum: 10c0/36d938712ff00fe1f4bac88b43bcffb5930c1efa57bbcdca9d67e1d9d6c57cfb1200fb01efe0f3109b2ce99b231f90779532814a81370a1bd3274a0f58585039 + languageName: node + linkType: hard + +"end-of-stream@npm:^1.1.0, end-of-stream@npm:^1.4.1": + version: 1.4.4 + resolution: "end-of-stream@npm:1.4.4" + dependencies: + once: "npm:^1.4.0" + checksum: 10c0/870b423afb2d54bb8d243c63e07c170409d41e20b47eeef0727547aea5740bd6717aca45597a9f2745525667a6b804c1e7bede41f856818faee5806dd9ff3975 + languageName: node + linkType: hard + +"env-paths@npm:^2.2.0": + version: 2.2.1 + resolution: "env-paths@npm:2.2.1" + checksum: 10c0/285325677bf00e30845e330eec32894f5105529db97496ee3f598478e50f008c5352a41a30e5e72ec9de8a542b5a570b85699cd63bd2bc646dbcb9f311d83bc4 + languageName: node + linkType: hard + +"err-code@npm:^2.0.2": + version: 2.0.3 + resolution: "err-code@npm:2.0.3" + checksum: 10c0/b642f7b4dd4a376e954947550a3065a9ece6733ab8e51ad80db727aaae0817c2e99b02a97a3d6cecc648a97848305e728289cf312d09af395403a90c9d4d8a66 + languageName: node + linkType: hard + +"error-ex@npm:^1.3.1": + version: 1.3.2 + resolution: "error-ex@npm:1.3.2" + dependencies: + is-arrayish: "npm:^0.2.1" + checksum: 10c0/ba827f89369b4c93382cfca5a264d059dfefdaa56ecc5e338ffa58a6471f5ed93b71a20add1d52290a4873d92381174382658c885ac1a2305f7baca363ce9cce + languageName: node + linkType: hard + +"es-define-property@npm:^1.0.0": + version: 1.0.0 + resolution: "es-define-property@npm:1.0.0" + dependencies: + get-intrinsic: "npm:^1.2.4" + checksum: 10c0/6bf3191feb7ea2ebda48b577f69bdfac7a2b3c9bcf97307f55fd6ef1bbca0b49f0c219a935aca506c993d8c5d8bddd937766cb760cd5e5a1071351f2df9f9aa4 + languageName: node + linkType: hard + +"es-errors@npm:^1.3.0": + version: 1.3.0 + resolution: "es-errors@npm:1.3.0" + checksum: 10c0/0a61325670072f98d8ae3b914edab3559b6caa980f08054a3b872052640d91da01d38df55df797fcc916389d77fc92b8d5906cf028f4db46d7e3003abecbca85 + languageName: node + linkType: hard + +"escalade@npm:^3.1.1, escalade@npm:^3.1.2": + version: 3.1.2 + resolution: "escalade@npm:3.1.2" + checksum: 10c0/6b4adafecd0682f3aa1cd1106b8fff30e492c7015b178bc81b2d2f75106dabea6c6d6e8508fc491bd58e597c74abb0e8e2368f943ecb9393d4162e3c2f3cf287 + languageName: node + linkType: hard + +"escape-html@npm:~1.0.3": + version: 1.0.3 + resolution: "escape-html@npm:1.0.3" + checksum: 10c0/524c739d776b36c3d29fa08a22e03e8824e3b2fd57500e5e44ecf3cc4707c34c60f9ca0781c0e33d191f2991161504c295e98f68c78fe7baa6e57081ec6ac0a3 + languageName: node + linkType: hard + +"escape-string-regexp@npm:^1.0.5": + version: 1.0.5 + resolution: "escape-string-regexp@npm:1.0.5" + checksum: 10c0/a968ad453dd0c2724e14a4f20e177aaf32bb384ab41b674a8454afe9a41c5e6fe8903323e0a1052f56289d04bd600f81278edf140b0fcc02f5cac98d0f5b5371 + languageName: node + linkType: hard + +"escape-string-regexp@npm:^2.0.0": + version: 2.0.0 + resolution: "escape-string-regexp@npm:2.0.0" + checksum: 10c0/2530479fe8db57eace5e8646c9c2a9c80fa279614986d16dcc6bcaceb63ae77f05a851ba6c43756d816c61d7f4534baf56e3c705e3e0d884818a46808811c507 + languageName: node + linkType: hard + +"escape-string-regexp@npm:^4.0.0": + version: 4.0.0 + resolution: "escape-string-regexp@npm:4.0.0" + checksum: 10c0/9497d4dd307d845bd7f75180d8188bb17ea8c151c1edbf6b6717c100e104d629dc2dfb687686181b0f4b7d732c7dfdc4d5e7a8ff72de1b0ca283a75bbb3a9cd9 + languageName: node + linkType: hard + +"eslint-plugin-prettier@npm:^4.2.1": + version: 4.2.1 + resolution: "eslint-plugin-prettier@npm:4.2.1" + dependencies: + prettier-linter-helpers: "npm:^1.0.0" + peerDependencies: + eslint: ">=7.28.0" + prettier: ">=2.0.0" + peerDependenciesMeta: + eslint-config-prettier: + optional: true + checksum: 10c0/c5e7316baeab9d96ac39c279f16686e837277e5c67a8006c6588bcff317edffdc1532fb580441eb598bc6770f6444006756b68a6575dff1cd85ebe227252d0b7 + languageName: node + linkType: hard + +"eslint-scope@npm:^5.1.1": + version: 5.1.1 + resolution: "eslint-scope@npm:5.1.1" + dependencies: + esrecurse: "npm:^4.3.0" + estraverse: "npm:^4.1.1" + checksum: 10c0/d30ef9dc1c1cbdece34db1539a4933fe3f9b14e1ffb27ecc85987902ee663ad7c9473bbd49a9a03195a373741e62e2f807c4938992e019b511993d163450e70a + languageName: node + linkType: hard + +"eslint-scope@npm:^7.2.2": + version: 7.2.2 + resolution: "eslint-scope@npm:7.2.2" + dependencies: + esrecurse: "npm:^4.3.0" + estraverse: "npm:^5.2.0" + checksum: 10c0/613c267aea34b5a6d6c00514e8545ef1f1433108097e857225fed40d397dd6b1809dffd11c2fde23b37ca53d7bf935fe04d2a18e6fc932b31837b6ad67e1c116 + languageName: node + linkType: hard + +"eslint-visitor-keys@npm:^3.3.0, eslint-visitor-keys@npm:^3.4.1, eslint-visitor-keys@npm:^3.4.3": + version: 3.4.3 + resolution: "eslint-visitor-keys@npm:3.4.3" + checksum: 10c0/92708e882c0a5ffd88c23c0b404ac1628cf20104a108c745f240a13c332a11aac54f49a22d5762efbffc18ecbc9a580d1b7ad034bf5f3cc3307e5cbff2ec9820 + languageName: node + linkType: hard + +"eslint@npm:^8.33.0": + version: 8.57.0 + resolution: "eslint@npm:8.57.0" + dependencies: + "@eslint-community/eslint-utils": "npm:^4.2.0" + "@eslint-community/regexpp": "npm:^4.6.1" + "@eslint/eslintrc": "npm:^2.1.4" + "@eslint/js": "npm:8.57.0" + "@humanwhocodes/config-array": "npm:^0.11.14" + "@humanwhocodes/module-importer": "npm:^1.0.1" + "@nodelib/fs.walk": "npm:^1.2.8" + "@ungap/structured-clone": "npm:^1.2.0" + ajv: "npm:^6.12.4" + chalk: "npm:^4.0.0" + cross-spawn: "npm:^7.0.2" + debug: "npm:^4.3.2" + doctrine: "npm:^3.0.0" + escape-string-regexp: "npm:^4.0.0" + eslint-scope: "npm:^7.2.2" + eslint-visitor-keys: "npm:^3.4.3" + espree: "npm:^9.6.1" + esquery: "npm:^1.4.2" + esutils: "npm:^2.0.2" + fast-deep-equal: "npm:^3.1.3" + file-entry-cache: "npm:^6.0.1" + find-up: "npm:^5.0.0" + glob-parent: "npm:^6.0.2" + globals: "npm:^13.19.0" + graphemer: "npm:^1.4.0" + ignore: "npm:^5.2.0" + imurmurhash: "npm:^0.1.4" + is-glob: "npm:^4.0.0" + is-path-inside: "npm:^3.0.3" + js-yaml: "npm:^4.1.0" + json-stable-stringify-without-jsonify: "npm:^1.0.1" + levn: "npm:^0.4.1" + lodash.merge: "npm:^4.6.2" + minimatch: "npm:^3.1.2" + natural-compare: "npm:^1.4.0" + optionator: "npm:^0.9.3" + strip-ansi: "npm:^6.0.1" + text-table: "npm:^0.2.0" + bin: + eslint: bin/eslint.js + checksum: 10c0/00bb96fd2471039a312435a6776fe1fd557c056755eaa2b96093ef3a8508c92c8775d5f754768be6b1dddd09fdd3379ddb231eeb9b6c579ee17ea7d68000a529 + languageName: node + linkType: hard + +"espree@npm:^9.6.0, espree@npm:^9.6.1": + version: 9.6.1 + resolution: "espree@npm:9.6.1" + dependencies: + acorn: "npm:^8.9.0" + acorn-jsx: "npm:^5.3.2" + eslint-visitor-keys: "npm:^3.4.1" + checksum: 10c0/1a2e9b4699b715347f62330bcc76aee224390c28bb02b31a3752e9d07549c473f5f986720483c6469cf3cfb3c9d05df612ffc69eb1ee94b54b739e67de9bb460 + languageName: node + linkType: hard + +"esprima@npm:^4.0.0": + version: 4.0.1 + resolution: "esprima@npm:4.0.1" + bin: + esparse: ./bin/esparse.js + esvalidate: ./bin/esvalidate.js + checksum: 10c0/ad4bab9ead0808cf56501750fd9d3fb276f6b105f987707d059005d57e182d18a7c9ec7f3a01794ebddcca676773e42ca48a32d67a250c9d35e009ca613caba3 + languageName: node + linkType: hard + +"esquery@npm:^1.4.2": + version: 1.5.0 + resolution: "esquery@npm:1.5.0" + dependencies: + estraverse: "npm:^5.1.0" + checksum: 10c0/a084bd049d954cc88ac69df30534043fb2aee5555b56246493f42f27d1e168f00d9e5d4192e46f10290d312dc30dc7d58994d61a609c579c1219d636996f9213 + languageName: node + linkType: hard + +"esrecurse@npm:^4.3.0": + version: 4.3.0 + resolution: "esrecurse@npm:4.3.0" + dependencies: + estraverse: "npm:^5.2.0" + checksum: 10c0/81a37116d1408ded88ada45b9fb16dbd26fba3aadc369ce50fcaf82a0bac12772ebd7b24cd7b91fc66786bf2c1ac7b5f196bc990a473efff972f5cb338877cf5 + languageName: node + linkType: hard + +"estraverse@npm:^4.1.1": + version: 4.3.0 + resolution: "estraverse@npm:4.3.0" + checksum: 10c0/9cb46463ef8a8a4905d3708a652d60122a0c20bb58dec7e0e12ab0e7235123d74214fc0141d743c381813e1b992767e2708194f6f6e0f9fd00c1b4e0887b8b6d + languageName: node + linkType: hard + +"estraverse@npm:^5.1.0, estraverse@npm:^5.2.0": + version: 5.3.0 + resolution: "estraverse@npm:5.3.0" + checksum: 10c0/1ff9447b96263dec95d6d67431c5e0771eb9776427421260a3e2f0fdd5d6bd4f8e37a7338f5ad2880c9f143450c9b1e4fc2069060724570a49cf9cf0312bd107 + languageName: node + linkType: hard + +"esutils@npm:^2.0.2": + version: 2.0.3 + resolution: "esutils@npm:2.0.3" + checksum: 10c0/9a2fe69a41bfdade834ba7c42de4723c97ec776e40656919c62cbd13607c45e127a003f05f724a1ea55e5029a4cf2de444b13009f2af71271e42d93a637137c7 + languageName: node + linkType: hard + +"etag@npm:~1.8.1": + version: 1.8.1 + resolution: "etag@npm:1.8.1" + checksum: 10c0/12be11ef62fb9817314d790089a0a49fae4e1b50594135dcb8076312b7d7e470884b5100d249b28c18581b7fd52f8b485689ffae22a11ed9ec17377a33a08f84 + languageName: node + linkType: hard + +"execa@npm:^5.0.0": + version: 5.1.1 + resolution: "execa@npm:5.1.1" + dependencies: + cross-spawn: "npm:^7.0.3" + get-stream: "npm:^6.0.0" + human-signals: "npm:^2.1.0" + is-stream: "npm:^2.0.0" + merge-stream: "npm:^2.0.0" + npm-run-path: "npm:^4.0.1" + onetime: "npm:^5.1.2" + signal-exit: "npm:^3.0.3" + strip-final-newline: "npm:^2.0.0" + checksum: 10c0/c8e615235e8de4c5addf2fa4c3da3e3aa59ce975a3e83533b4f6a71750fb816a2e79610dc5f1799b6e28976c9ae86747a36a606655bf8cb414a74d8d507b304f + languageName: node + linkType: hard + +"exit@npm:^0.1.2": + version: 0.1.2 + resolution: "exit@npm:0.1.2" + checksum: 10c0/71d2ad9b36bc25bb8b104b17e830b40a08989be7f7d100b13269aaae7c3784c3e6e1e88a797e9e87523993a25ba27c8958959a554535370672cfb4d824af8989 + languageName: node + linkType: hard + +"expand-template@npm:^2.0.3": + version: 2.0.3 + resolution: "expand-template@npm:2.0.3" + checksum: 10c0/1c9e7afe9acadf9d373301d27f6a47b34e89b3391b1ef38b7471d381812537ef2457e620ae7f819d2642ce9c43b189b3583813ec395e2938319abe356a9b2f51 + languageName: node + linkType: hard + +"expect@npm:^29.0.0, expect@npm:^29.7.0": + version: 29.7.0 + resolution: "expect@npm:29.7.0" + dependencies: + "@jest/expect-utils": "npm:^29.7.0" + jest-get-type: "npm:^29.6.3" + jest-matcher-utils: "npm:^29.7.0" + jest-message-util: "npm:^29.7.0" + jest-util: "npm:^29.7.0" + checksum: 10c0/2eddeace66e68b8d8ee5f7be57f3014b19770caaf6815c7a08d131821da527fb8c8cb7b3dcd7c883d2d3d8d184206a4268984618032d1e4b16dc8d6596475d41 + languageName: node + linkType: hard + +"exponential-backoff@npm:^3.1.1": + version: 3.1.1 + resolution: "exponential-backoff@npm:3.1.1" + checksum: 10c0/160456d2d647e6019640bd07111634d8c353038d9fa40176afb7cd49b0548bdae83b56d05e907c2cce2300b81cae35d800ef92fefb9d0208e190fa3b7d6bb579 + languageName: node + linkType: hard + +"express-actuator@npm:1.8.4": + version: 1.8.4 + resolution: "express-actuator@npm:1.8.4" + dependencies: + dayjs: "npm:^1.11.3" + properties-reader: "npm:^2.2.0" + checksum: 10c0/216264ba977b34d59de95721924354d7b4090b62801f225eac7f861fd8e40251e215826d7b462e57b86d02143eabdff2384b50d17d207e54a61b5aaaa726ad83 + languageName: node + linkType: hard + +"express-rate-limit@npm:^6.7.0": + version: 6.11.2 + resolution: "express-rate-limit@npm:6.11.2" + peerDependencies: + express: ^4 || ^5 + checksum: 10c0/1f92107fca92423b6311ca26cb79a3d5d79ac5f5eb81791c382c2323e331da0e993249225615003e2db91b617d69ba0d428ef60d212bfabb7a83244645f10e0a + languageName: node + linkType: hard + +"express-response-size@npm:^0.0.3": + version: 0.0.3 + resolution: "express-response-size@npm:0.0.3" + dependencies: + on-headers: "npm:1.0.1" + checksum: 10c0/9203d192c7d7b48b4ea8710aa90d78522020d469963f9e83355a140eb1b13e40c03caa752b8f9d0b6bb401fe89958c5a043f28278ff514c90af8dc7d51409bc7 + languageName: node + linkType: hard + +"express-winston@npm:^4.2.0": + version: 4.2.0 + resolution: "express-winston@npm:4.2.0" + dependencies: + chalk: "npm:^2.4.2" + lodash: "npm:^4.17.21" + peerDependencies: + winston: ">=3.x <4" + checksum: 10c0/8f80993e7d7696b22a12c68ffb72ea0cd3ad980d8394073fd972162cdca116b8f506974dd504987e350cddfdbf55402871762dcb9c69f2f7feaef2df6d93ef09 + languageName: node + linkType: hard + +"express@npm:4.20.0": + version: 4.20.0 + resolution: "express@npm:4.20.0" + dependencies: + accepts: "npm:~1.3.8" + array-flatten: "npm:1.1.1" + body-parser: "npm:1.20.3" + content-disposition: "npm:0.5.4" + content-type: "npm:~1.0.4" + cookie: "npm:0.6.0" + cookie-signature: "npm:1.0.6" + debug: "npm:2.6.9" + depd: "npm:2.0.0" + encodeurl: "npm:~2.0.0" + escape-html: "npm:~1.0.3" + etag: "npm:~1.8.1" + finalhandler: "npm:1.2.0" + fresh: "npm:0.5.2" + http-errors: "npm:2.0.0" + merge-descriptors: "npm:1.0.3" + methods: "npm:~1.1.2" + on-finished: "npm:2.4.1" + parseurl: "npm:~1.3.3" + path-to-regexp: "npm:0.1.10" + proxy-addr: "npm:~2.0.7" + qs: "npm:6.11.0" + range-parser: "npm:~1.2.1" + safe-buffer: "npm:5.2.1" + send: "npm:0.19.0" + serve-static: "npm:1.16.0" + setprototypeof: "npm:1.2.0" + statuses: "npm:2.0.1" + type-is: "npm:~1.6.18" + utils-merge: "npm:1.0.1" + vary: "npm:~1.1.2" + checksum: 10c0/626e440e9feffa3f82ebce5e7dc0ad7a74fa96079994f30048cce450f4855a258abbcabf021f691aeb72154867f0d28440a8498c62888805faf667a829fb65aa + languageName: node + linkType: hard + +"fast-deep-equal@npm:^3.1.1, fast-deep-equal@npm:^3.1.3": + version: 3.1.3 + resolution: "fast-deep-equal@npm:3.1.3" + checksum: 10c0/40dedc862eb8992c54579c66d914635afbec43350afbbe991235fdcb4e3a8d5af1b23ae7e79bef7d4882d0ecee06c3197488026998fb19f72dc95acff1d1b1d0 + languageName: node + linkType: hard + +"fast-diff@npm:^1.1.2": + version: 1.3.0 + resolution: "fast-diff@npm:1.3.0" + checksum: 10c0/5c19af237edb5d5effda008c891a18a585f74bf12953be57923f17a3a4d0979565fc64dbc73b9e20926b9d895f5b690c618cbb969af0cf022e3222471220ad29 + languageName: node + linkType: hard + +"fast-glob@npm:^3.2.9": + version: 3.3.2 + resolution: "fast-glob@npm:3.3.2" + dependencies: + "@nodelib/fs.stat": "npm:^2.0.2" + "@nodelib/fs.walk": "npm:^1.2.3" + glob-parent: "npm:^5.1.2" + merge2: "npm:^1.3.0" + micromatch: "npm:^4.0.4" + checksum: 10c0/42baad7b9cd40b63e42039132bde27ca2cb3a4950d0a0f9abe4639ea1aa9d3e3b40f98b1fe31cbc0cc17b664c9ea7447d911a152fa34ec5b72977b125a6fc845 + languageName: node + linkType: hard + +"fast-json-stable-stringify@npm:^2.0.0, fast-json-stable-stringify@npm:^2.1.0": + version: 2.1.0 + resolution: "fast-json-stable-stringify@npm:2.1.0" + checksum: 10c0/7f081eb0b8a64e0057b3bb03f974b3ef00135fbf36c1c710895cd9300f13c94ba809bb3a81cf4e1b03f6e5285610a61abbd7602d0652de423144dfee5a389c9b + languageName: node + linkType: hard + +"fast-levenshtein@npm:^2.0.6": + version: 2.0.6 + resolution: "fast-levenshtein@npm:2.0.6" + checksum: 10c0/111972b37338bcb88f7d9e2c5907862c280ebf4234433b95bc611e518d192ccb2d38119c4ac86e26b668d75f7f3894f4ff5c4982899afced7ca78633b08287c4 + languageName: node + linkType: hard + +"fast-safe-stringify@npm:^2.1.1": + version: 2.1.1 + resolution: "fast-safe-stringify@npm:2.1.1" + checksum: 10c0/d90ec1c963394919828872f21edaa3ad6f1dddd288d2bd4e977027afff09f5db40f94e39536d4646f7e01761d704d72d51dce5af1b93717f3489ef808f5f4e4d + languageName: node + linkType: hard + +"fastq@npm:^1.6.0": + version: 1.17.1 + resolution: "fastq@npm:1.17.1" + dependencies: + reusify: "npm:^1.0.4" + checksum: 10c0/1095f16cea45fb3beff558bb3afa74ca7a9250f5a670b65db7ed585f92b4b48381445cd328b3d87323da81e43232b5d5978a8201bde84e0cd514310f1ea6da34 + languageName: node + linkType: hard + +"fb-watchman@npm:^2.0.0": + version: 2.0.2 + resolution: "fb-watchman@npm:2.0.2" + dependencies: + bser: "npm:2.1.1" + checksum: 10c0/feae89ac148adb8f6ae8ccd87632e62b13563e6fb114cacb5265c51f585b17e2e268084519fb2edd133872f1d47a18e6bfd7e5e08625c0d41b93149694187581 + languageName: node + linkType: hard + +"fecha@npm:^4.2.0": + version: 4.2.3 + resolution: "fecha@npm:4.2.3" + checksum: 10c0/0e895965959cf6a22bb7b00f0bf546f2783836310f510ddf63f463e1518d4c96dec61ab33fdfd8e79a71b4856a7c865478ce2ee8498d560fe125947703c9b1cf + languageName: node + linkType: hard + +"file-entry-cache@npm:^6.0.1": + version: 6.0.1 + resolution: "file-entry-cache@npm:6.0.1" + dependencies: + flat-cache: "npm:^3.0.4" + checksum: 10c0/58473e8a82794d01b38e5e435f6feaf648e3f36fdb3a56e98f417f4efae71ad1c0d4ebd8a9a7c50c3ad085820a93fc7494ad721e0e4ebc1da3573f4e1c3c7cdd + languageName: node + linkType: hard + +"file-uri-to-path@npm:1.0.0": + version: 1.0.0 + resolution: "file-uri-to-path@npm:1.0.0" + checksum: 10c0/3b545e3a341d322d368e880e1c204ef55f1d45cdea65f7efc6c6ce9e0c4d22d802d5629320eb779d006fe59624ac17b0e848d83cc5af7cd101f206cb704f5519 + languageName: node + linkType: hard + +"fill-range@npm:^7.1.1": + version: 7.1.1 + resolution: "fill-range@npm:7.1.1" + dependencies: + to-regex-range: "npm:^5.0.1" + checksum: 10c0/b75b691bbe065472f38824f694c2f7449d7f5004aa950426a2c28f0306c60db9b880c0b0e4ed819997ffb882d1da02cfcfc819bddc94d71627f5269682edf018 + languageName: node + linkType: hard + +"finalhandler@npm:1.2.0": + version: 1.2.0 + resolution: "finalhandler@npm:1.2.0" + dependencies: + debug: "npm:2.6.9" + encodeurl: "npm:~1.0.2" + escape-html: "npm:~1.0.3" + on-finished: "npm:2.4.1" + parseurl: "npm:~1.3.3" + statuses: "npm:2.0.1" + unpipe: "npm:~1.0.0" + checksum: 10c0/64b7e5ff2ad1fcb14931cd012651631b721ce657da24aedb5650ddde9378bf8e95daa451da43398123f5de161a81e79ff5affe4f9f2a6d2df4a813d6d3e254b7 + languageName: node + linkType: hard + +"find-up@npm:^4.0.0, find-up@npm:^4.1.0": + version: 4.1.0 + resolution: "find-up@npm:4.1.0" + dependencies: + locate-path: "npm:^5.0.0" + path-exists: "npm:^4.0.0" + checksum: 10c0/0406ee89ebeefa2d507feb07ec366bebd8a6167ae74aa4e34fb4c4abd06cf782a3ce26ae4194d70706f72182841733f00551c209fe575cb00bd92104056e78c1 + languageName: node + linkType: hard + +"find-up@npm:^5.0.0": + version: 5.0.0 + resolution: "find-up@npm:5.0.0" + dependencies: + locate-path: "npm:^6.0.0" + path-exists: "npm:^4.0.0" + checksum: 10c0/062c5a83a9c02f53cdd6d175a37ecf8f87ea5bbff1fdfb828f04bfa021441bc7583e8ebc0872a4c1baab96221fb8a8a275a19809fb93fbc40bd69ec35634069a + languageName: node + linkType: hard + +"flat-cache@npm:^3.0.4": + version: 3.2.0 + resolution: "flat-cache@npm:3.2.0" + dependencies: + flatted: "npm:^3.2.9" + keyv: "npm:^4.5.3" + rimraf: "npm:^3.0.2" + checksum: 10c0/b76f611bd5f5d68f7ae632e3ae503e678d205cf97a17c6ab5b12f6ca61188b5f1f7464503efae6dc18683ed8f0b41460beb48ac4b9ac63fe6201296a91ba2f75 + languageName: node + linkType: hard + +"flatted@npm:^3.2.9": + version: 3.3.1 + resolution: "flatted@npm:3.3.1" + checksum: 10c0/324166b125ee07d4ca9bcf3a5f98d915d5db4f39d711fba640a3178b959919aae1f7cfd8aabcfef5826ed8aa8a2aa14cc85b2d7d18ff638ddf4ae3df39573eaf + languageName: node + linkType: hard + +"fn.name@npm:1.x.x": + version: 1.1.0 + resolution: "fn.name@npm:1.1.0" + checksum: 10c0/8ad62aa2d4f0b2a76d09dba36cfec61c540c13a0fd72e5d94164e430f987a7ce6a743112bbeb14877c810ef500d1f73d7f56e76d029d2e3413f20d79e3460a9a + languageName: node + linkType: hard + +"follow-redirects@npm:^1.15.6": + version: 1.15.6 + resolution: "follow-redirects@npm:1.15.6" + peerDependenciesMeta: + debug: + optional: true + checksum: 10c0/9ff767f0d7be6aa6870c82ac79cf0368cd73e01bbc00e9eb1c2a16fbb198ec105e3c9b6628bb98e9f3ac66fe29a957b9645bcb9a490bb7aa0d35f908b6b85071 + languageName: node + linkType: hard + +"foreground-child@npm:^3.1.0": + version: 3.2.1 + resolution: "foreground-child@npm:3.2.1" + dependencies: + cross-spawn: "npm:^7.0.0" + signal-exit: "npm:^4.0.1" + checksum: 10c0/9a53a33dbd87090e9576bef65fb4a71de60f6863a8062a7b11bc1cbe3cc86d428677d7c0b9ef61cdac11007ac580006f78bd5638618d564cfd5e6fd713d6878f + languageName: node + linkType: hard + +"form-data@npm:^4.0.0": + version: 4.0.0 + resolution: "form-data@npm:4.0.0" + dependencies: + asynckit: "npm:^0.4.0" + combined-stream: "npm:^1.0.8" + mime-types: "npm:^2.1.12" + checksum: 10c0/cb6f3ac49180be03ff07ba3ff125f9eba2ff0b277fb33c7fc47569fc5e616882c5b1c69b9904c4c4187e97dd0419dd03b134174756f296dec62041e6527e2c6e + languageName: node + linkType: hard + +"formidable@npm:^2.1.2": + version: 2.1.2 + resolution: "formidable@npm:2.1.2" + dependencies: + dezalgo: "npm:^1.0.4" + hexoid: "npm:^1.0.0" + once: "npm:^1.4.0" + qs: "npm:^6.11.0" + checksum: 10c0/efba03d11127098daa6ef54c3c0fad25693973eb902fa88ccaaa203baebe8c74d12ba0fe1e113eccf79b9172510fa337e4e107330b124fb3a8c74697b4aa2ce3 + languageName: node + linkType: hard + +"forwarded@npm:0.2.0": + version: 0.2.0 + resolution: "forwarded@npm:0.2.0" + checksum: 10c0/9b67c3fac86acdbc9ae47ba1ddd5f2f81526fa4c8226863ede5600a3f7c7416ef451f6f1e240a3cc32d0fd79fcfe6beb08fd0da454f360032bde70bf80afbb33 + languageName: node + linkType: hard + +"fresh@npm:0.5.2": + version: 0.5.2 + resolution: "fresh@npm:0.5.2" + checksum: 10c0/c6d27f3ed86cc5b601404822f31c900dd165ba63fff8152a3ef714e2012e7535027063bc67ded4cb5b3a49fa596495d46cacd9f47d6328459cf570f08b7d9e5a + languageName: node + linkType: hard + +"fs-constants@npm:^1.0.0": + version: 1.0.0 + resolution: "fs-constants@npm:1.0.0" + checksum: 10c0/a0cde99085f0872f4d244e83e03a46aa387b74f5a5af750896c6b05e9077fac00e9932fdf5aef84f2f16634cd473c63037d7a512576da7d5c2b9163d1909f3a8 + languageName: node + linkType: hard + +"fs-minipass@npm:^2.0.0": + version: 2.1.0 + resolution: "fs-minipass@npm:2.1.0" + dependencies: + minipass: "npm:^3.0.0" + checksum: 10c0/703d16522b8282d7299337539c3ed6edddd1afe82435e4f5b76e34a79cd74e488a8a0e26a636afc2440e1a23b03878e2122e3a2cfe375a5cf63c37d92b86a004 + languageName: node + linkType: hard + +"fs-minipass@npm:^3.0.0": + version: 3.0.3 + resolution: "fs-minipass@npm:3.0.3" + dependencies: + minipass: "npm:^7.0.3" + checksum: 10c0/63e80da2ff9b621e2cb1596abcb9207f1cf82b968b116ccd7b959e3323144cce7fb141462200971c38bbf2ecca51695069db45265705bed09a7cd93ae5b89f94 + languageName: node + linkType: hard + +"fs.realpath@npm:^1.0.0": + version: 1.0.0 + resolution: "fs.realpath@npm:1.0.0" + checksum: 10c0/444cf1291d997165dfd4c0d58b69f0e4782bfd9149fd72faa4fe299e68e0e93d6db941660b37dd29153bf7186672ececa3b50b7e7249477b03fdf850f287c948 + languageName: node + linkType: hard + +"fsevents@npm:^2.3.2": + version: 2.3.3 + resolution: "fsevents@npm:2.3.3" + dependencies: + node-gyp: "npm:latest" + checksum: 10c0/a1f0c44595123ed717febbc478aa952e47adfc28e2092be66b8ab1635147254ca6cfe1df792a8997f22716d4cbafc73309899ff7bfac2ac3ad8cf2e4ecc3ec60 + conditions: os=darwin + languageName: node + linkType: hard + +"fsevents@patch:fsevents@npm%3A^2.3.2#optional!builtin<compat/fsevents>": + version: 2.3.3 + resolution: "fsevents@patch:fsevents@npm%3A2.3.3#optional!builtin<compat/fsevents>::version=2.3.3&hash=df0bf1" + dependencies: + node-gyp: "npm:latest" + conditions: os=darwin + languageName: node + linkType: hard + +"function-bind@npm:^1.1.2": + version: 1.1.2 + resolution: "function-bind@npm:1.1.2" + checksum: 10c0/d8680ee1e5fcd4c197e4ac33b2b4dce03c71f4d91717292785703db200f5c21f977c568d28061226f9b5900cbcd2c84463646134fd5337e7925e0942bc3f46d5 + languageName: node + linkType: hard + +"gauge@npm:^3.0.0": + version: 3.0.2 + resolution: "gauge@npm:3.0.2" + dependencies: + aproba: "npm:^1.0.3 || ^2.0.0" + color-support: "npm:^1.1.2" + console-control-strings: "npm:^1.0.0" + has-unicode: "npm:^2.0.1" + object-assign: "npm:^4.1.1" + signal-exit: "npm:^3.0.0" + string-width: "npm:^4.2.3" + strip-ansi: "npm:^6.0.1" + wide-align: "npm:^1.1.2" + checksum: 10c0/75230ccaf216471e31025c7d5fcea1629596ca20792de50c596eb18ffb14d8404f927cd55535aab2eeecd18d1e11bd6f23ec3c2e9878d2dda1dc74bccc34b913 + languageName: node + linkType: hard + +"gensync@npm:^1.0.0-beta.2": + version: 1.0.0-beta.2 + resolution: "gensync@npm:1.0.0-beta.2" + checksum: 10c0/782aba6cba65b1bb5af3b095d96249d20edbe8df32dbf4696fd49be2583faf676173bf4809386588828e4dd76a3354fcbeb577bab1c833ccd9fc4577f26103f8 + languageName: node + linkType: hard + +"get-caller-file@npm:^2.0.5": + version: 2.0.5 + resolution: "get-caller-file@npm:2.0.5" + checksum: 10c0/c6c7b60271931fa752aeb92f2b47e355eac1af3a2673f47c9589e8f8a41adc74d45551c1bc57b5e66a80609f10ffb72b6f575e4370d61cc3f7f3aaff01757cde + languageName: node + linkType: hard + +"get-intrinsic@npm:^1.1.3, get-intrinsic@npm:^1.2.4": + version: 1.2.4 + resolution: "get-intrinsic@npm:1.2.4" + dependencies: + es-errors: "npm:^1.3.0" + function-bind: "npm:^1.1.2" + has-proto: "npm:^1.0.1" + has-symbols: "npm:^1.0.3" + hasown: "npm:^2.0.0" + checksum: 10c0/0a9b82c16696ed6da5e39b1267104475c47e3a9bdbe8b509dfe1710946e38a87be70d759f4bb3cda042d76a41ef47fe769660f3b7c0d1f68750299344ffb15b7 + languageName: node + linkType: hard + +"get-package-type@npm:^0.1.0": + version: 0.1.0 + resolution: "get-package-type@npm:0.1.0" + checksum: 10c0/e34cdf447fdf1902a1f6d5af737eaadf606d2ee3518287abde8910e04159368c268568174b2e71102b87b26c2020486f126bfca9c4fb1ceb986ff99b52ecd1be + languageName: node + linkType: hard + +"get-stream@npm:^6.0.0": + version: 6.0.1 + resolution: "get-stream@npm:6.0.1" + checksum: 10c0/49825d57d3fd6964228e6200a58169464b8e8970489b3acdc24906c782fb7f01f9f56f8e6653c4a50713771d6658f7cfe051e5eb8c12e334138c9c918b296341 + languageName: node + linkType: hard + +"github-from-package@npm:0.0.0": + version: 0.0.0 + resolution: "github-from-package@npm:0.0.0" + checksum: 10c0/737ee3f52d0a27e26332cde85b533c21fcdc0b09fb716c3f8e522cfaa9c600d4a631dec9fcde179ec9d47cca89017b7848ed4d6ae6b6b78f936c06825b1fcc12 + languageName: node + linkType: hard + +"glob-parent@npm:^5.1.2": + version: 5.1.2 + resolution: "glob-parent@npm:5.1.2" + dependencies: + is-glob: "npm:^4.0.1" + checksum: 10c0/cab87638e2112bee3f839ef5f6e0765057163d39c66be8ec1602f3823da4692297ad4e972de876ea17c44d652978638d2fd583c6713d0eb6591706825020c9ee + languageName: node + linkType: hard + +"glob-parent@npm:^6.0.2": + version: 6.0.2 + resolution: "glob-parent@npm:6.0.2" + dependencies: + is-glob: "npm:^4.0.3" + checksum: 10c0/317034d88654730230b3f43bb7ad4f7c90257a426e872ea0bf157473ac61c99bf5d205fad8f0185f989be8d2fa6d3c7dce1645d99d545b6ea9089c39f838e7f8 + languageName: node + linkType: hard + +"glob@npm:^10.2.2, glob@npm:^10.3.10": + version: 10.4.1 + resolution: "glob@npm:10.4.1" + dependencies: + foreground-child: "npm:^3.1.0" + jackspeak: "npm:^3.1.2" + minimatch: "npm:^9.0.4" + minipass: "npm:^7.1.2" + path-scurry: "npm:^1.11.1" + bin: + glob: dist/esm/bin.mjs + checksum: 10c0/77f2900ed98b9cc2a0e1901ee5e476d664dae3cd0f1b662b8bfd4ccf00d0edc31a11595807706a274ca10e1e251411bbf2e8e976c82bed0d879a9b89343ed379 + languageName: node + linkType: hard + +"glob@npm:^7.1.3, glob@npm:^7.1.4": + version: 7.2.3 + resolution: "glob@npm:7.2.3" + dependencies: + fs.realpath: "npm:^1.0.0" + inflight: "npm:^1.0.4" + inherits: "npm:2" + minimatch: "npm:^3.1.1" + once: "npm:^1.3.0" + path-is-absolute: "npm:^1.0.0" + checksum: 10c0/65676153e2b0c9095100fe7f25a778bf45608eeb32c6048cf307f579649bcc30353277b3b898a3792602c65764e5baa4f643714dfbdfd64ea271d210c7a425fe + languageName: node + linkType: hard + +"globals@npm:^11.1.0": + version: 11.12.0 + resolution: "globals@npm:11.12.0" + checksum: 10c0/758f9f258e7b19226bd8d4af5d3b0dcf7038780fb23d82e6f98932c44e239f884847f1766e8fa9cc5635ccb3204f7fa7314d4408dd4002a5e8ea827b4018f0a1 + languageName: node + linkType: hard + +"globals@npm:^13.19.0": + version: 13.24.0 + resolution: "globals@npm:13.24.0" + dependencies: + type-fest: "npm:^0.20.2" + checksum: 10c0/d3c11aeea898eb83d5ec7a99508600fbe8f83d2cf00cbb77f873dbf2bcb39428eff1b538e4915c993d8a3b3473fa71eeebfe22c9bb3a3003d1e26b1f2c8a42cd + languageName: node + linkType: hard + +"globby@npm:^11.1.0": + version: 11.1.0 + resolution: "globby@npm:11.1.0" + dependencies: + array-union: "npm:^2.1.0" + dir-glob: "npm:^3.0.1" + fast-glob: "npm:^3.2.9" + ignore: "npm:^5.2.0" + merge2: "npm:^1.4.1" + slash: "npm:^3.0.0" + checksum: 10c0/b39511b4afe4bd8a7aead3a27c4ade2b9968649abab0a6c28b1a90141b96ca68ca5db1302f7c7bd29eab66bf51e13916b8e0a3d0ac08f75e1e84a39b35691189 + languageName: node + linkType: hard + +"google-protobuf@npm:^3.12.0-rc.1": + version: 3.21.2 + resolution: "google-protobuf@npm:3.21.2" + checksum: 10c0/df20b41aad9eba4d842d69c717a4d73ac6d321084c12f524ad5eb79a47ad185323bd1b477c19565a15fd08b6eef29e475c8ac281dbc6fe547b81d8b6b99974f5 + languageName: node + linkType: hard + +"gopd@npm:^1.0.1": + version: 1.0.1 + resolution: "gopd@npm:1.0.1" + dependencies: + get-intrinsic: "npm:^1.1.3" + checksum: 10c0/505c05487f7944c552cee72087bf1567debb470d4355b1335f2c262d218ebbff805cd3715448fe29b4b380bae6912561d0467233e4165830efd28da241418c63 + languageName: node + linkType: hard + +"graceful-fs@npm:^4.2.6, graceful-fs@npm:^4.2.9": + version: 4.2.11 + resolution: "graceful-fs@npm:4.2.11" + checksum: 10c0/386d011a553e02bc594ac2ca0bd6d9e4c22d7fa8cfbfc448a6d148c59ea881b092db9dbe3547ae4b88e55f1b01f7c4a2ecc53b310c042793e63aa44cf6c257f2 + languageName: node + linkType: hard + +"graphemer@npm:^1.4.0": + version: 1.4.0 + resolution: "graphemer@npm:1.4.0" + checksum: 10c0/e951259d8cd2e0d196c72ec711add7115d42eb9a8146c8eeda5b8d3ac91e5dd816b9cd68920726d9fd4490368e7ed86e9c423f40db87e2d8dfafa00fa17c3a31 + languageName: node + linkType: hard + +"has-flag@npm:^3.0.0": + version: 3.0.0 + resolution: "has-flag@npm:3.0.0" + checksum: 10c0/1c6c83b14b8b1b3c25b0727b8ba3e3b647f99e9e6e13eb7322107261de07a4c1be56fc0d45678fc376e09772a3a1642ccdaf8fc69bdf123b6c086598397ce473 + languageName: node + linkType: hard + +"has-flag@npm:^4.0.0": + version: 4.0.0 + resolution: "has-flag@npm:4.0.0" + checksum: 10c0/2e789c61b7888d66993e14e8331449e525ef42aac53c627cc53d1c3334e768bcb6abdc4f5f0de1478a25beec6f0bd62c7549058b7ac53e924040d4f301f02fd1 + languageName: node + linkType: hard + +"has-property-descriptors@npm:^1.0.2": + version: 1.0.2 + resolution: "has-property-descriptors@npm:1.0.2" + dependencies: + es-define-property: "npm:^1.0.0" + checksum: 10c0/253c1f59e80bb476cf0dde8ff5284505d90c3bdb762983c3514d36414290475fe3fd6f574929d84de2a8eec00d35cf07cb6776205ff32efd7c50719125f00236 + languageName: node + linkType: hard + +"has-proto@npm:^1.0.1": + version: 1.0.3 + resolution: "has-proto@npm:1.0.3" + checksum: 10c0/35a6989f81e9f8022c2f4027f8b48a552de714938765d019dbea6bb547bd49ce5010a3c7c32ec6ddac6e48fc546166a3583b128f5a7add8b058a6d8b4afec205 + languageName: node + linkType: hard + +"has-symbols@npm:^1.0.3": + version: 1.0.3 + resolution: "has-symbols@npm:1.0.3" + checksum: 10c0/e6922b4345a3f37069cdfe8600febbca791c94988c01af3394d86ca3360b4b93928bbf395859158f88099cb10b19d98e3bbab7c9ff2c1bd09cf665ee90afa2c3 + languageName: node + linkType: hard + +"has-unicode@npm:^2.0.1": + version: 2.0.1 + resolution: "has-unicode@npm:2.0.1" + checksum: 10c0/ebdb2f4895c26bb08a8a100b62d362e49b2190bcfd84b76bc4be1a3bd4d254ec52d0dd9f2fbcc093fc5eb878b20c52146f9dfd33e2686ed28982187be593b47c + languageName: node + linkType: hard + +"hasown@npm:^2.0.0": + version: 2.0.2 + resolution: "hasown@npm:2.0.2" + dependencies: + function-bind: "npm:^1.1.2" + checksum: 10c0/3769d434703b8ac66b209a4cca0737519925bbdb61dd887f93a16372b14694c63ff4e797686d87c90f08168e81082248b9b028bad60d4da9e0d1148766f56eb9 + languageName: node + linkType: hard + +"hexoid@npm:^1.0.0": + version: 1.0.0 + resolution: "hexoid@npm:1.0.0" + checksum: 10c0/9c45e8ba676b9eb88455631ebceec4c829a8374a583410dc735472ab9808bf11339fcd074633c3fa30e420901b894d8a92ffd5e2e21eddd41149546e05a91f69 + languageName: node + linkType: hard + +"html-escaper@npm:^2.0.0": + version: 2.0.2 + resolution: "html-escaper@npm:2.0.2" + checksum: 10c0/208e8a12de1a6569edbb14544f4567e6ce8ecc30b9394fcaa4e7bb1e60c12a7c9a1ed27e31290817157e8626f3a4f29e76c8747030822eb84a6abb15c255f0a0 + languageName: node + linkType: hard + +"http-cache-semantics@npm:^4.1.1": + version: 4.1.1 + resolution: "http-cache-semantics@npm:4.1.1" + checksum: 10c0/ce1319b8a382eb3cbb4a37c19f6bfe14e5bb5be3d09079e885e8c513ab2d3cd9214902f8a31c9dc4e37022633ceabfc2d697405deeaf1b8f3552bb4ed996fdfc + languageName: node + linkType: hard + +"http-errors@npm:2.0.0": + version: 2.0.0 + resolution: "http-errors@npm:2.0.0" + dependencies: + depd: "npm:2.0.0" + inherits: "npm:2.0.4" + setprototypeof: "npm:1.2.0" + statuses: "npm:2.0.1" + toidentifier: "npm:1.0.1" + checksum: 10c0/fc6f2715fe188d091274b5ffc8b3657bd85c63e969daa68ccb77afb05b071a4b62841acb7a21e417b5539014dff2ebf9550f0b14a9ff126f2734a7c1387f8e19 + languageName: node + linkType: hard + +"http-proxy-agent@npm:^7.0.0": + version: 7.0.2 + resolution: "http-proxy-agent@npm:7.0.2" + dependencies: + agent-base: "npm:^7.1.0" + debug: "npm:^4.3.4" + checksum: 10c0/4207b06a4580fb85dd6dff521f0abf6db517489e70863dca1a0291daa7f2d3d2d6015a57bd702af068ea5cf9f1f6ff72314f5f5b4228d299c0904135d2aef921 + languageName: node + linkType: hard + +"https-proxy-agent@npm:^5.0.0": + version: 5.0.1 + resolution: "https-proxy-agent@npm:5.0.1" + dependencies: + agent-base: "npm:6" + debug: "npm:4" + checksum: 10c0/6dd639f03434003577c62b27cafdb864784ef19b2de430d8ae2a1d45e31c4fd60719e5637b44db1a88a046934307da7089e03d6089ec3ddacc1189d8de8897d1 + languageName: node + linkType: hard + +"https-proxy-agent@npm:^7.0.1": + version: 7.0.4 + resolution: "https-proxy-agent@npm:7.0.4" + dependencies: + agent-base: "npm:^7.0.2" + debug: "npm:4" + checksum: 10c0/bc4f7c38da32a5fc622450b6cb49a24ff596f9bd48dcedb52d2da3fa1c1a80e100fb506bd59b326c012f21c863c69b275c23de1a01d0b84db396822fdf25e52b + languageName: node + linkType: hard + +"human-signals@npm:^2.1.0": + version: 2.1.0 + resolution: "human-signals@npm:2.1.0" + checksum: 10c0/695edb3edfcfe9c8b52a76926cd31b36978782062c0ed9b1192b36bebc75c4c87c82e178dfcb0ed0fc27ca59d434198aac0bd0be18f5781ded775604db22304a + languageName: node + linkType: hard + +"iconv-lite@npm:0.4.24": + version: 0.4.24 + resolution: "iconv-lite@npm:0.4.24" + dependencies: + safer-buffer: "npm:>= 2.1.2 < 3" + checksum: 10c0/c6886a24cc00f2a059767440ec1bc00d334a89f250db8e0f7feb4961c8727118457e27c495ba94d082e51d3baca378726cd110aaf7ded8b9bbfd6a44760cf1d4 + languageName: node + linkType: hard + +"iconv-lite@npm:^0.6.2": + version: 0.6.3 + resolution: "iconv-lite@npm:0.6.3" + dependencies: + safer-buffer: "npm:>= 2.1.2 < 3.0.0" + checksum: 10c0/98102bc66b33fcf5ac044099d1257ba0b7ad5e3ccd3221f34dd508ab4070edff183276221684e1e0555b145fce0850c9f7d2b60a9fcac50fbb4ea0d6e845a3b1 + languageName: node + linkType: hard + +"ieee754@npm:^1.1.13": + version: 1.2.1 + resolution: "ieee754@npm:1.2.1" + checksum: 10c0/b0782ef5e0935b9f12883a2e2aa37baa75da6e66ce6515c168697b42160807d9330de9a32ec1ed73149aea02e0d822e572bca6f1e22bdcbd2149e13b050b17bb + languageName: node + linkType: hard + +"ignore@npm:^5.2.0": + version: 5.3.1 + resolution: "ignore@npm:5.3.1" + checksum: 10c0/703f7f45ffb2a27fb2c5a8db0c32e7dee66b33a225d28e8db4e1be6474795f606686a6e3bcc50e1aa12f2042db4c9d4a7d60af3250511de74620fbed052ea4cd + languageName: node + linkType: hard + +"import-fresh@npm:^3.2.1": + version: 3.3.0 + resolution: "import-fresh@npm:3.3.0" + dependencies: + parent-module: "npm:^1.0.0" + resolve-from: "npm:^4.0.0" + checksum: 10c0/7f882953aa6b740d1f0e384d0547158bc86efbf2eea0f1483b8900a6f65c5a5123c2cf09b0d542cc419d0b98a759ecaeb394237e97ea427f2da221dc3cd80cc3 + languageName: node + linkType: hard + +"import-local@npm:^3.0.2": + version: 3.1.0 + resolution: "import-local@npm:3.1.0" + dependencies: + pkg-dir: "npm:^4.2.0" + resolve-cwd: "npm:^3.0.0" + bin: + import-local-fixture: fixtures/cli.js + checksum: 10c0/c67ecea72f775fe8684ca3d057e54bdb2ae28c14bf261d2607c269c18ea0da7b730924c06262eca9aed4b8ab31e31d65bc60b50e7296c85908a56e2f7d41ecd2 + languageName: node + linkType: hard + +"imurmurhash@npm:^0.1.4": + version: 0.1.4 + resolution: "imurmurhash@npm:0.1.4" + checksum: 10c0/8b51313850dd33605c6c9d3fd9638b714f4c4c40250cff658209f30d40da60f78992fb2df5dabee4acf589a6a82bbc79ad5486550754bd9ec4e3fc0d4a57d6a6 + languageName: node + linkType: hard + +"indent-string@npm:^4.0.0": + version: 4.0.0 + resolution: "indent-string@npm:4.0.0" + checksum: 10c0/1e1904ddb0cb3d6cce7cd09e27a90184908b7a5d5c21b92e232c93579d314f0b83c246ffb035493d0504b1e9147ba2c9b21df0030f48673fba0496ecd698161f + languageName: node + linkType: hard + +"inflight@npm:^1.0.4": + version: 1.0.6 + resolution: "inflight@npm:1.0.6" + dependencies: + once: "npm:^1.3.0" + wrappy: "npm:1" + checksum: 10c0/7faca22584600a9dc5b9fca2cd5feb7135ac8c935449837b315676b4c90aa4f391ec4f42240178244b5a34e8bede1948627fda392ca3191522fc46b34e985ab2 + languageName: node + linkType: hard + +"inherits@npm:2, inherits@npm:2.0.4, inherits@npm:^2.0.3, inherits@npm:^2.0.4": + version: 2.0.4 + resolution: "inherits@npm:2.0.4" + checksum: 10c0/4e531f648b29039fb7426fb94075e6545faa1eb9fe83c29f0b6d9e7263aceb4289d2d4557db0d428188eeb449cc7c5e77b0a0b2c4e248ff2a65933a0dee49ef2 + languageName: node + linkType: hard + +"ini@npm:~1.3.0": + version: 1.3.8 + resolution: "ini@npm:1.3.8" + checksum: 10c0/ec93838d2328b619532e4f1ff05df7909760b6f66d9c9e2ded11e5c1897d6f2f9980c54dd638f88654b00919ce31e827040631eab0a3969e4d1abefa0719516a + languageName: node + linkType: hard + +"ip-address@npm:^9.0.5": + version: 9.0.5 + resolution: "ip-address@npm:9.0.5" + dependencies: + jsbn: "npm:1.1.0" + sprintf-js: "npm:^1.1.3" + checksum: 10c0/331cd07fafcb3b24100613e4b53e1a2b4feab11e671e655d46dc09ee233da5011284d09ca40c4ecbdfe1d0004f462958675c224a804259f2f78d2465a87824bc + languageName: node + linkType: hard + +"ipaddr.js@npm:1.9.1": + version: 1.9.1 + resolution: "ipaddr.js@npm:1.9.1" + checksum: 10c0/0486e775047971d3fdb5fb4f063829bac45af299ae0b82dcf3afa2145338e08290563a2a70f34b732d795ecc8311902e541a8530eeb30d75860a78ff4e94ce2a + languageName: node + linkType: hard + +"is-arrayish@npm:^0.2.1": + version: 0.2.1 + resolution: "is-arrayish@npm:0.2.1" + checksum: 10c0/e7fb686a739068bb70f860b39b67afc62acc62e36bb61c5f965768abce1873b379c563e61dd2adad96ebb7edf6651111b385e490cf508378959b0ed4cac4e729 + languageName: node + linkType: hard + +"is-arrayish@npm:^0.3.1": + version: 0.3.2 + resolution: "is-arrayish@npm:0.3.2" + checksum: 10c0/f59b43dc1d129edb6f0e282595e56477f98c40278a2acdc8b0a5c57097c9eff8fe55470493df5775478cf32a4dc8eaf6d3a749f07ceee5bc263a78b2434f6a54 + languageName: node + linkType: hard + +"is-core-module@npm:^2.13.0": + version: 2.13.1 + resolution: "is-core-module@npm:2.13.1" + dependencies: + hasown: "npm:^2.0.0" + checksum: 10c0/2cba9903aaa52718f11c4896dabc189bab980870aae86a62dc0d5cedb546896770ee946fb14c84b7adf0735f5eaea4277243f1b95f5cefa90054f92fbcac2518 + languageName: node + linkType: hard + +"is-extglob@npm:^2.1.1": + version: 2.1.1 + resolution: "is-extglob@npm:2.1.1" + checksum: 10c0/5487da35691fbc339700bbb2730430b07777a3c21b9ebaecb3072512dfd7b4ba78ac2381a87e8d78d20ea08affb3f1971b4af629173a6bf435ff8a4c47747912 + languageName: node + linkType: hard + +"is-fullwidth-code-point@npm:^3.0.0": + version: 3.0.0 + resolution: "is-fullwidth-code-point@npm:3.0.0" + checksum: 10c0/bb11d825e049f38e04c06373a8d72782eee0205bda9d908cc550ccb3c59b99d750ff9537982e01733c1c94a58e35400661f57042158ff5e8f3e90cf936daf0fc + languageName: node + linkType: hard + +"is-generator-fn@npm:^2.0.0": + version: 2.1.0 + resolution: "is-generator-fn@npm:2.1.0" + checksum: 10c0/2957cab387997a466cd0bf5c1b6047bd21ecb32bdcfd8996b15747aa01002c1c88731802f1b3d34ac99f4f6874b626418bd118658cf39380fe5fff32a3af9c4d + languageName: node + linkType: hard + +"is-glob@npm:^4.0.0, is-glob@npm:^4.0.1, is-glob@npm:^4.0.3": + version: 4.0.3 + resolution: "is-glob@npm:4.0.3" + dependencies: + is-extglob: "npm:^2.1.1" + checksum: 10c0/17fb4014e22be3bbecea9b2e3a76e9e34ff645466be702f1693e8f1ee1adac84710d0be0bd9f967d6354036fd51ab7c2741d954d6e91dae6bb69714de92c197a + languageName: node + linkType: hard + +"is-lambda@npm:^1.0.1": + version: 1.0.1 + resolution: "is-lambda@npm:1.0.1" + checksum: 10c0/85fee098ae62ba6f1e24cf22678805473c7afd0fb3978a3aa260e354cb7bcb3a5806cf0a98403188465efedec41ab4348e8e4e79305d409601323855b3839d4d + languageName: node + linkType: hard + +"is-number@npm:^7.0.0": + version: 7.0.0 + resolution: "is-number@npm:7.0.0" + checksum: 10c0/b4686d0d3053146095ccd45346461bc8e53b80aeb7671cc52a4de02dbbf7dc0d1d2a986e2fe4ae206984b4d34ef37e8b795ebc4f4295c978373e6575e295d811 + languageName: node + linkType: hard + +"is-path-inside@npm:^3.0.3": + version: 3.0.3 + resolution: "is-path-inside@npm:3.0.3" + checksum: 10c0/cf7d4ac35fb96bab6a1d2c3598fe5ebb29aafb52c0aaa482b5a3ed9d8ba3edc11631e3ec2637660c44b3ce0e61a08d54946e8af30dec0b60a7c27296c68ffd05 + languageName: node + linkType: hard + +"is-stream@npm:^2.0.0": + version: 2.0.1 + resolution: "is-stream@npm:2.0.1" + checksum: 10c0/7c284241313fc6efc329b8d7f08e16c0efeb6baab1b4cd0ba579eb78e5af1aa5da11e68559896a2067cd6c526bd29241dda4eb1225e627d5aa1a89a76d4635a5 + languageName: node + linkType: hard + +"isexe@npm:^2.0.0": + version: 2.0.0 + resolution: "isexe@npm:2.0.0" + checksum: 10c0/228cfa503fadc2c31596ab06ed6aa82c9976eec2bfd83397e7eaf06d0ccf42cd1dfd6743bf9aeb01aebd4156d009994c5f76ea898d2832c1fe342da923ca457d + languageName: node + linkType: hard + +"isexe@npm:^3.1.1": + version: 3.1.1 + resolution: "isexe@npm:3.1.1" + checksum: 10c0/9ec257654093443eb0a528a9c8cbba9c0ca7616ccb40abd6dde7202734d96bb86e4ac0d764f0f8cd965856aacbff2f4ce23e730dc19dfb41e3b0d865ca6fdcc7 + languageName: node + linkType: hard + +"istanbul-lib-coverage@npm:^3.0.0, istanbul-lib-coverage@npm:^3.2.0": + version: 3.2.2 + resolution: "istanbul-lib-coverage@npm:3.2.2" + checksum: 10c0/6c7ff2106769e5f592ded1fb418f9f73b4411fd5a084387a5410538332b6567cd1763ff6b6cadca9b9eb2c443cce2f7ea7d7f1b8d315f9ce58539793b1e0922b + languageName: node + linkType: hard + +"istanbul-lib-instrument@npm:^5.0.4": + version: 5.2.1 + resolution: "istanbul-lib-instrument@npm:5.2.1" + dependencies: + "@babel/core": "npm:^7.12.3" + "@babel/parser": "npm:^7.14.7" + "@istanbuljs/schema": "npm:^0.1.2" + istanbul-lib-coverage: "npm:^3.2.0" + semver: "npm:^6.3.0" + checksum: 10c0/8a1bdf3e377dcc0d33ec32fe2b6ecacdb1e4358fd0eb923d4326bb11c67622c0ceb99600a680f3dad5d29c66fc1991306081e339b4d43d0b8a2ab2e1d910a6ee + languageName: node + linkType: hard + +"istanbul-lib-instrument@npm:^6.0.0": + version: 6.0.2 + resolution: "istanbul-lib-instrument@npm:6.0.2" + dependencies: + "@babel/core": "npm:^7.23.9" + "@babel/parser": "npm:^7.23.9" + "@istanbuljs/schema": "npm:^0.1.3" + istanbul-lib-coverage: "npm:^3.2.0" + semver: "npm:^7.5.4" + checksum: 10c0/405c6ac037bf8c7ee7495980b0cd5544b2c53078c10534d0c9ceeb92a9ea7dcf8510f58ccfce31336458a8fa6ccef27b570bbb602abaa8c1650f5496a807477c + languageName: node + linkType: hard + +"istanbul-lib-report@npm:^3.0.0": + version: 3.0.1 + resolution: "istanbul-lib-report@npm:3.0.1" + dependencies: + istanbul-lib-coverage: "npm:^3.0.0" + make-dir: "npm:^4.0.0" + supports-color: "npm:^7.1.0" + checksum: 10c0/84323afb14392de8b6a5714bd7e9af845cfbd56cfe71ed276cda2f5f1201aea673c7111901227ee33e68e4364e288d73861eb2ed48f6679d1e69a43b6d9b3ba7 + languageName: node + linkType: hard + +"istanbul-lib-source-maps@npm:^4.0.0": + version: 4.0.1 + resolution: "istanbul-lib-source-maps@npm:4.0.1" + dependencies: + debug: "npm:^4.1.1" + istanbul-lib-coverage: "npm:^3.0.0" + source-map: "npm:^0.6.1" + checksum: 10c0/19e4cc405016f2c906dff271a76715b3e881fa9faeb3f09a86cb99b8512b3a5ed19cadfe0b54c17ca0e54c1142c9c6de9330d65506e35873994e06634eebeb66 + languageName: node + linkType: hard + +"istanbul-reports@npm:^3.1.3": + version: 3.1.7 + resolution: "istanbul-reports@npm:3.1.7" + dependencies: + html-escaper: "npm:^2.0.0" + istanbul-lib-report: "npm:^3.0.0" + checksum: 10c0/a379fadf9cf8dc5dfe25568115721d4a7eb82fbd50b005a6672aff9c6989b20cc9312d7865814e0859cd8df58cbf664482e1d3604be0afde1f7fc3ccc1394a51 + languageName: node + linkType: hard + +"jackspeak@npm:^3.1.2": + version: 3.4.0 + resolution: "jackspeak@npm:3.4.0" + dependencies: + "@isaacs/cliui": "npm:^8.0.2" + "@pkgjs/parseargs": "npm:^0.11.0" + dependenciesMeta: + "@pkgjs/parseargs": + optional: true + checksum: 10c0/7e42d1ea411b4d57d43ea8a6afbca9224382804359cb72626d0fc45bb8db1de5ad0248283c3db45fe73e77210750d4fcc7c2b4fe5d24fda94aaa24d658295c5f + languageName: node + linkType: hard + +"jest-changed-files@npm:^29.7.0": + version: 29.7.0 + resolution: "jest-changed-files@npm:29.7.0" + dependencies: + execa: "npm:^5.0.0" + jest-util: "npm:^29.7.0" + p-limit: "npm:^3.1.0" + checksum: 10c0/e071384d9e2f6bb462231ac53f29bff86f0e12394c1b49ccafbad225ce2ab7da226279a8a94f421949920bef9be7ef574fd86aee22e8adfa149be73554ab828b + languageName: node + linkType: hard + +"jest-circus@npm:^29.7.0": + version: 29.7.0 + resolution: "jest-circus@npm:29.7.0" + dependencies: + "@jest/environment": "npm:^29.7.0" + "@jest/expect": "npm:^29.7.0" + "@jest/test-result": "npm:^29.7.0" + "@jest/types": "npm:^29.6.3" + "@types/node": "npm:*" + chalk: "npm:^4.0.0" + co: "npm:^4.6.0" + dedent: "npm:^1.0.0" + is-generator-fn: "npm:^2.0.0" + jest-each: "npm:^29.7.0" + jest-matcher-utils: "npm:^29.7.0" + jest-message-util: "npm:^29.7.0" + jest-runtime: "npm:^29.7.0" + jest-snapshot: "npm:^29.7.0" + jest-util: "npm:^29.7.0" + p-limit: "npm:^3.1.0" + pretty-format: "npm:^29.7.0" + pure-rand: "npm:^6.0.0" + slash: "npm:^3.0.0" + stack-utils: "npm:^2.0.3" + checksum: 10c0/8d15344cf7a9f14e926f0deed64ed190c7a4fa1ed1acfcd81e4cc094d3cc5bf7902ebb7b874edc98ada4185688f90c91e1747e0dfd7ac12463b097968ae74b5e + languageName: node + linkType: hard + +"jest-cli@npm:^29.7.0": + version: 29.7.0 + resolution: "jest-cli@npm:29.7.0" + dependencies: + "@jest/core": "npm:^29.7.0" + "@jest/test-result": "npm:^29.7.0" + "@jest/types": "npm:^29.6.3" + chalk: "npm:^4.0.0" + create-jest: "npm:^29.7.0" + exit: "npm:^0.1.2" + import-local: "npm:^3.0.2" + jest-config: "npm:^29.7.0" + jest-util: "npm:^29.7.0" + jest-validate: "npm:^29.7.0" + yargs: "npm:^17.3.1" + peerDependencies: + node-notifier: ^8.0.1 || ^9.0.0 || ^10.0.0 + peerDependenciesMeta: + node-notifier: + optional: true + bin: + jest: bin/jest.js + checksum: 10c0/a658fd55050d4075d65c1066364595962ead7661711495cfa1dfeecf3d6d0a8ffec532f3dbd8afbb3e172dd5fd2fb2e813c5e10256e7cf2fea766314942fb43a + languageName: node + linkType: hard + +"jest-config@npm:^29.7.0": + version: 29.7.0 + resolution: "jest-config@npm:29.7.0" + dependencies: + "@babel/core": "npm:^7.11.6" + "@jest/test-sequencer": "npm:^29.7.0" + "@jest/types": "npm:^29.6.3" + babel-jest: "npm:^29.7.0" + chalk: "npm:^4.0.0" + ci-info: "npm:^3.2.0" + deepmerge: "npm:^4.2.2" + glob: "npm:^7.1.3" + graceful-fs: "npm:^4.2.9" + jest-circus: "npm:^29.7.0" + jest-environment-node: "npm:^29.7.0" + jest-get-type: "npm:^29.6.3" + jest-regex-util: "npm:^29.6.3" + jest-resolve: "npm:^29.7.0" + jest-runner: "npm:^29.7.0" + jest-util: "npm:^29.7.0" + jest-validate: "npm:^29.7.0" + micromatch: "npm:^4.0.4" + parse-json: "npm:^5.2.0" + pretty-format: "npm:^29.7.0" + slash: "npm:^3.0.0" + strip-json-comments: "npm:^3.1.1" + peerDependencies: + "@types/node": "*" + ts-node: ">=9.0.0" + peerDependenciesMeta: + "@types/node": + optional: true + ts-node: + optional: true + checksum: 10c0/bab23c2eda1fff06e0d104b00d6adfb1d1aabb7128441899c9bff2247bd26710b050a5364281ce8d52b46b499153bf7e3ee88b19831a8f3451f1477a0246a0f1 + languageName: node + linkType: hard + +"jest-diff@npm:^29.7.0": + version: 29.7.0 + resolution: "jest-diff@npm:29.7.0" + dependencies: + chalk: "npm:^4.0.0" + diff-sequences: "npm:^29.6.3" + jest-get-type: "npm:^29.6.3" + pretty-format: "npm:^29.7.0" + checksum: 10c0/89a4a7f182590f56f526443dde69acefb1f2f0c9e59253c61d319569856c4931eae66b8a3790c443f529267a0ddba5ba80431c585deed81827032b2b2a1fc999 + languageName: node + linkType: hard + +"jest-docblock@npm:^29.7.0": + version: 29.7.0 + resolution: "jest-docblock@npm:29.7.0" + dependencies: + detect-newline: "npm:^3.0.0" + checksum: 10c0/d932a8272345cf6b6142bb70a2bb63e0856cc0093f082821577ea5bdf4643916a98744dfc992189d2b1417c38a11fa42466f6111526bc1fb81366f56410f3be9 + languageName: node + linkType: hard + +"jest-each@npm:^29.7.0": + version: 29.7.0 + resolution: "jest-each@npm:29.7.0" + dependencies: + "@jest/types": "npm:^29.6.3" + chalk: "npm:^4.0.0" + jest-get-type: "npm:^29.6.3" + jest-util: "npm:^29.7.0" + pretty-format: "npm:^29.7.0" + checksum: 10c0/f7f9a90ebee80cc688e825feceb2613627826ac41ea76a366fa58e669c3b2403d364c7c0a74d862d469b103c843154f8456d3b1c02b487509a12afa8b59edbb4 + languageName: node + linkType: hard + +"jest-environment-node@npm:^29.7.0": + version: 29.7.0 + resolution: "jest-environment-node@npm:29.7.0" + dependencies: + "@jest/environment": "npm:^29.7.0" + "@jest/fake-timers": "npm:^29.7.0" + "@jest/types": "npm:^29.6.3" + "@types/node": "npm:*" + jest-mock: "npm:^29.7.0" + jest-util: "npm:^29.7.0" + checksum: 10c0/61f04fec077f8b1b5c1a633e3612fc0c9aa79a0ab7b05600683428f1e01a4d35346c474bde6f439f9fcc1a4aa9a2861ff852d079a43ab64b02105d1004b2592b + languageName: node + linkType: hard + +"jest-get-type@npm:^29.6.3": + version: 29.6.3 + resolution: "jest-get-type@npm:29.6.3" + checksum: 10c0/552e7a97a983d3c2d4e412a44eb7de0430ff773dd99f7500962c268d6dfbfa431d7d08f919c9d960530e5f7f78eb47f267ad9b318265e5092b3ff9ede0db7c2b + languageName: node + linkType: hard + +"jest-haste-map@npm:^29.7.0": + version: 29.7.0 + resolution: "jest-haste-map@npm:29.7.0" + dependencies: + "@jest/types": "npm:^29.6.3" + "@types/graceful-fs": "npm:^4.1.3" + "@types/node": "npm:*" + anymatch: "npm:^3.0.3" + fb-watchman: "npm:^2.0.0" + fsevents: "npm:^2.3.2" + graceful-fs: "npm:^4.2.9" + jest-regex-util: "npm:^29.6.3" + jest-util: "npm:^29.7.0" + jest-worker: "npm:^29.7.0" + micromatch: "npm:^4.0.4" + walker: "npm:^1.0.8" + dependenciesMeta: + fsevents: + optional: true + checksum: 10c0/2683a8f29793c75a4728787662972fedd9267704c8f7ef9d84f2beed9a977f1cf5e998c07b6f36ba5603f53cb010c911fe8cd0ac9886e073fe28ca66beefd30c + languageName: node + linkType: hard + +"jest-leak-detector@npm:^29.7.0": + version: 29.7.0 + resolution: "jest-leak-detector@npm:29.7.0" + dependencies: + jest-get-type: "npm:^29.6.3" + pretty-format: "npm:^29.7.0" + checksum: 10c0/71bb9f77fc489acb842a5c7be030f2b9acb18574dc9fb98b3100fc57d422b1abc55f08040884bd6e6dbf455047a62f7eaff12aa4058f7cbdc11558718ca6a395 + languageName: node + linkType: hard + +"jest-matcher-utils@npm:^29.7.0": + version: 29.7.0 + resolution: "jest-matcher-utils@npm:29.7.0" + dependencies: + chalk: "npm:^4.0.0" + jest-diff: "npm:^29.7.0" + jest-get-type: "npm:^29.6.3" + pretty-format: "npm:^29.7.0" + checksum: 10c0/0d0e70b28fa5c7d4dce701dc1f46ae0922102aadc24ed45d594dd9b7ae0a8a6ef8b216718d1ab79e451291217e05d4d49a82666e1a3cc2b428b75cd9c933244e + languageName: node + linkType: hard + +"jest-message-util@npm:^29.7.0": + version: 29.7.0 + resolution: "jest-message-util@npm:29.7.0" + dependencies: + "@babel/code-frame": "npm:^7.12.13" + "@jest/types": "npm:^29.6.3" + "@types/stack-utils": "npm:^2.0.0" + chalk: "npm:^4.0.0" + graceful-fs: "npm:^4.2.9" + micromatch: "npm:^4.0.4" + pretty-format: "npm:^29.7.0" + slash: "npm:^3.0.0" + stack-utils: "npm:^2.0.3" + checksum: 10c0/850ae35477f59f3e6f27efac5215f706296e2104af39232bb14e5403e067992afb5c015e87a9243ec4d9df38525ef1ca663af9f2f4766aa116f127247008bd22 + languageName: node + linkType: hard + +"jest-mock@npm:^29.7.0": + version: 29.7.0 + resolution: "jest-mock@npm:29.7.0" + dependencies: + "@jest/types": "npm:^29.6.3" + "@types/node": "npm:*" + jest-util: "npm:^29.7.0" + checksum: 10c0/7b9f8349ee87695a309fe15c46a74ab04c853369e5c40952d68061d9dc3159a0f0ed73e215f81b07ee97a9faaf10aebe5877a9d6255068a0977eae6a9ff1d5ac + languageName: node + linkType: hard + +"jest-pnp-resolver@npm:^1.2.2": + version: 1.2.3 + resolution: "jest-pnp-resolver@npm:1.2.3" + peerDependencies: + jest-resolve: "*" + peerDependenciesMeta: + jest-resolve: + optional: true + checksum: 10c0/86eec0c78449a2de733a6d3e316d49461af6a858070e113c97f75fb742a48c2396ea94150cbca44159ffd4a959f743a47a8b37a792ef6fdad2cf0a5cba973fac + languageName: node + linkType: hard + +"jest-regex-util@npm:^29.6.3": + version: 29.6.3 + resolution: "jest-regex-util@npm:29.6.3" + checksum: 10c0/4e33fb16c4f42111159cafe26397118dcfc4cf08bc178a67149fb05f45546a91928b820894572679d62559839d0992e21080a1527faad65daaae8743a5705a3b + languageName: node + linkType: hard + +"jest-resolve-dependencies@npm:^29.7.0": + version: 29.7.0 + resolution: "jest-resolve-dependencies@npm:29.7.0" + dependencies: + jest-regex-util: "npm:^29.6.3" + jest-snapshot: "npm:^29.7.0" + checksum: 10c0/b6e9ad8ae5b6049474118ea6441dfddd385b6d1fc471db0136f7c8fbcfe97137a9665e4f837a9f49f15a29a1deb95a14439b7aec812f3f99d08f228464930f0d + languageName: node + linkType: hard + +"jest-resolve@npm:^29.7.0": + version: 29.7.0 + resolution: "jest-resolve@npm:29.7.0" + dependencies: + chalk: "npm:^4.0.0" + graceful-fs: "npm:^4.2.9" + jest-haste-map: "npm:^29.7.0" + jest-pnp-resolver: "npm:^1.2.2" + jest-util: "npm:^29.7.0" + jest-validate: "npm:^29.7.0" + resolve: "npm:^1.20.0" + resolve.exports: "npm:^2.0.0" + slash: "npm:^3.0.0" + checksum: 10c0/59da5c9c5b50563e959a45e09e2eace783d7f9ac0b5dcc6375dea4c0db938d2ebda97124c8161310082760e8ebbeff9f6b177c15ca2f57fb424f637a5d2adb47 + languageName: node + linkType: hard + +"jest-runner@npm:^29.7.0": + version: 29.7.0 + resolution: "jest-runner@npm:29.7.0" + dependencies: + "@jest/console": "npm:^29.7.0" + "@jest/environment": "npm:^29.7.0" + "@jest/test-result": "npm:^29.7.0" + "@jest/transform": "npm:^29.7.0" + "@jest/types": "npm:^29.6.3" + "@types/node": "npm:*" + chalk: "npm:^4.0.0" + emittery: "npm:^0.13.1" + graceful-fs: "npm:^4.2.9" + jest-docblock: "npm:^29.7.0" + jest-environment-node: "npm:^29.7.0" + jest-haste-map: "npm:^29.7.0" + jest-leak-detector: "npm:^29.7.0" + jest-message-util: "npm:^29.7.0" + jest-resolve: "npm:^29.7.0" + jest-runtime: "npm:^29.7.0" + jest-util: "npm:^29.7.0" + jest-watcher: "npm:^29.7.0" + jest-worker: "npm:^29.7.0" + p-limit: "npm:^3.1.0" + source-map-support: "npm:0.5.13" + checksum: 10c0/2194b4531068d939f14c8d3274fe5938b77fa73126aedf9c09ec9dec57d13f22c72a3b5af01ac04f5c1cf2e28d0ac0b4a54212a61b05f10b5d6b47f2a1097bb4 + languageName: node + linkType: hard + +"jest-runtime@npm:^29.7.0": + version: 29.7.0 + resolution: "jest-runtime@npm:29.7.0" + dependencies: + "@jest/environment": "npm:^29.7.0" + "@jest/fake-timers": "npm:^29.7.0" + "@jest/globals": "npm:^29.7.0" + "@jest/source-map": "npm:^29.6.3" + "@jest/test-result": "npm:^29.7.0" + "@jest/transform": "npm:^29.7.0" + "@jest/types": "npm:^29.6.3" + "@types/node": "npm:*" + chalk: "npm:^4.0.0" + cjs-module-lexer: "npm:^1.0.0" + collect-v8-coverage: "npm:^1.0.0" + glob: "npm:^7.1.3" + graceful-fs: "npm:^4.2.9" + jest-haste-map: "npm:^29.7.0" + jest-message-util: "npm:^29.7.0" + jest-mock: "npm:^29.7.0" + jest-regex-util: "npm:^29.6.3" + jest-resolve: "npm:^29.7.0" + jest-snapshot: "npm:^29.7.0" + jest-util: "npm:^29.7.0" + slash: "npm:^3.0.0" + strip-bom: "npm:^4.0.0" + checksum: 10c0/7cd89a1deda0bda7d0941835434e44f9d6b7bd50b5c5d9b0fc9a6c990b2d4d2cab59685ab3cb2850ed4cc37059f6de903af5a50565d7f7f1192a77d3fd6dd2a6 + languageName: node + linkType: hard + +"jest-snapshot@npm:^29.7.0": + version: 29.7.0 + resolution: "jest-snapshot@npm:29.7.0" + dependencies: + "@babel/core": "npm:^7.11.6" + "@babel/generator": "npm:^7.7.2" + "@babel/plugin-syntax-jsx": "npm:^7.7.2" + "@babel/plugin-syntax-typescript": "npm:^7.7.2" + "@babel/types": "npm:^7.3.3" + "@jest/expect-utils": "npm:^29.7.0" + "@jest/transform": "npm:^29.7.0" + "@jest/types": "npm:^29.6.3" + babel-preset-current-node-syntax: "npm:^1.0.0" + chalk: "npm:^4.0.0" + expect: "npm:^29.7.0" + graceful-fs: "npm:^4.2.9" + jest-diff: "npm:^29.7.0" + jest-get-type: "npm:^29.6.3" + jest-matcher-utils: "npm:^29.7.0" + jest-message-util: "npm:^29.7.0" + jest-util: "npm:^29.7.0" + natural-compare: "npm:^1.4.0" + pretty-format: "npm:^29.7.0" + semver: "npm:^7.5.3" + checksum: 10c0/6e9003c94ec58172b4a62864a91c0146513207bedf4e0a06e1e2ac70a4484088a2683e3a0538d8ea913bcfd53dc54a9b98a98cdfa562e7fe1d1339aeae1da570 + languageName: node + linkType: hard + +"jest-util@npm:^29.7.0": + version: 29.7.0 + resolution: "jest-util@npm:29.7.0" + dependencies: + "@jest/types": "npm:^29.6.3" + "@types/node": "npm:*" + chalk: "npm:^4.0.0" + ci-info: "npm:^3.2.0" + graceful-fs: "npm:^4.2.9" + picomatch: "npm:^2.2.3" + checksum: 10c0/bc55a8f49fdbb8f51baf31d2a4f312fb66c9db1483b82f602c9c990e659cdd7ec529c8e916d5a89452ecbcfae4949b21b40a7a59d4ffc0cd813a973ab08c8150 + languageName: node + linkType: hard + +"jest-validate@npm:^29.7.0": + version: 29.7.0 + resolution: "jest-validate@npm:29.7.0" + dependencies: + "@jest/types": "npm:^29.6.3" + camelcase: "npm:^6.2.0" + chalk: "npm:^4.0.0" + jest-get-type: "npm:^29.6.3" + leven: "npm:^3.1.0" + pretty-format: "npm:^29.7.0" + checksum: 10c0/a20b930480c1ed68778c739f4739dce39423131bc070cd2505ddede762a5570a256212e9c2401b7ae9ba4d7b7c0803f03c5b8f1561c62348213aba18d9dbece2 + languageName: node + linkType: hard + +"jest-watcher@npm:^29.7.0": + version: 29.7.0 + resolution: "jest-watcher@npm:29.7.0" + dependencies: + "@jest/test-result": "npm:^29.7.0" + "@jest/types": "npm:^29.6.3" + "@types/node": "npm:*" + ansi-escapes: "npm:^4.2.1" + chalk: "npm:^4.0.0" + emittery: "npm:^0.13.1" + jest-util: "npm:^29.7.0" + string-length: "npm:^4.0.1" + checksum: 10c0/ec6c75030562fc8f8c727cb8f3b94e75d831fc718785abfc196e1f2a2ebc9a2e38744a15147170039628a853d77a3b695561ce850375ede3a4ee6037a2574567 + languageName: node + linkType: hard + +"jest-worker@npm:^29.7.0": + version: 29.7.0 + resolution: "jest-worker@npm:29.7.0" + dependencies: + "@types/node": "npm:*" + jest-util: "npm:^29.7.0" + merge-stream: "npm:^2.0.0" + supports-color: "npm:^8.0.0" + checksum: 10c0/5570a3a005b16f46c131968b8a5b56d291f9bbb85ff4217e31c80bd8a02e7de799e59a54b95ca28d5c302f248b54cbffde2d177c2f0f52ffcee7504c6eabf660 + languageName: node + linkType: hard + +"jest@npm:^29.3.1": + version: 29.7.0 + resolution: "jest@npm:29.7.0" + dependencies: + "@jest/core": "npm:^29.7.0" + "@jest/types": "npm:^29.6.3" + import-local: "npm:^3.0.2" + jest-cli: "npm:^29.7.0" + peerDependencies: + node-notifier: ^8.0.1 || ^9.0.0 || ^10.0.0 + peerDependenciesMeta: + node-notifier: + optional: true + bin: + jest: bin/jest.js + checksum: 10c0/f40eb8171cf147c617cc6ada49d062fbb03b4da666cb8d39cdbfb739a7d75eea4c3ca150fb072d0d273dce0c753db4d0467d54906ad0293f59c54f9db4a09d8b + languageName: node + linkType: hard + +"jose@npm:^4.15.5": + version: 4.15.9 + resolution: "jose@npm:4.15.9" + checksum: 10c0/4ed4ddf4a029db04bd167f2215f65d7245e4dc5f36d7ac3c0126aab38d66309a9e692f52df88975d99429e357e5fd8bab340ff20baab544d17684dd1d940a0f4 + languageName: node + linkType: hard + +"js-tokens@npm:^4.0.0": + version: 4.0.0 + resolution: "js-tokens@npm:4.0.0" + checksum: 10c0/e248708d377aa058eacf2037b07ded847790e6de892bbad3dac0abba2e759cb9f121b00099a65195616badcb6eca8d14d975cb3e89eb1cfda644756402c8aeed + languageName: node + linkType: hard + +"js-yaml@npm:^3.13.1": + version: 3.14.1 + resolution: "js-yaml@npm:3.14.1" + dependencies: + argparse: "npm:^1.0.7" + esprima: "npm:^4.0.0" + bin: + js-yaml: bin/js-yaml.js + checksum: 10c0/6746baaaeac312c4db8e75fa22331d9a04cccb7792d126ed8ce6a0bbcfef0cedaddd0c5098fade53db067c09fe00aa1c957674b4765610a8b06a5a189e46433b + languageName: node + linkType: hard + +"js-yaml@npm:^4.1.0": + version: 4.1.0 + resolution: "js-yaml@npm:4.1.0" + dependencies: + argparse: "npm:^2.0.1" + bin: + js-yaml: bin/js-yaml.js + checksum: 10c0/184a24b4eaacfce40ad9074c64fd42ac83cf74d8c8cd137718d456ced75051229e5061b8633c3366b8aada17945a7a356b337828c19da92b51ae62126575018f + languageName: node + linkType: hard + +"jsbn@npm:1.1.0": + version: 1.1.0 + resolution: "jsbn@npm:1.1.0" + checksum: 10c0/4f907fb78d7b712e11dea8c165fe0921f81a657d3443dde75359ed52eb2b5d33ce6773d97985a089f09a65edd80b11cb75c767b57ba47391fee4c969f7215c96 + languageName: node + linkType: hard + +"jsesc@npm:^2.5.1": + version: 2.5.2 + resolution: "jsesc@npm:2.5.2" + bin: + jsesc: bin/jsesc + checksum: 10c0/dbf59312e0ebf2b4405ef413ec2b25abb5f8f4d9bc5fb8d9f90381622ebca5f2af6a6aa9a8578f65903f9e33990a6dc798edd0ce5586894bf0e9e31803a1de88 + languageName: node + linkType: hard + +"json-buffer@npm:3.0.1": + version: 3.0.1 + resolution: "json-buffer@npm:3.0.1" + checksum: 10c0/0d1c91569d9588e7eef2b49b59851f297f3ab93c7b35c7c221e288099322be6b562767d11e4821da500f3219542b9afd2e54c5dc573107c1126ed1080f8e96d7 + languageName: node + linkType: hard + +"json-parse-even-better-errors@npm:^2.3.0": + version: 2.3.1 + resolution: "json-parse-even-better-errors@npm:2.3.1" + checksum: 10c0/140932564c8f0b88455432e0f33c4cb4086b8868e37524e07e723f4eaedb9425bdc2bafd71bd1d9765bd15fd1e2d126972bc83990f55c467168c228c24d665f3 + languageName: node + linkType: hard + +"json-schema-traverse@npm:^0.4.1": + version: 0.4.1 + resolution: "json-schema-traverse@npm:0.4.1" + checksum: 10c0/108fa90d4cc6f08243aedc6da16c408daf81793bf903e9fd5ab21983cda433d5d2da49e40711da016289465ec2e62e0324dcdfbc06275a607fe3233fde4942ce + languageName: node + linkType: hard + +"json-stable-stringify-without-jsonify@npm:^1.0.1": + version: 1.0.1 + resolution: "json-stable-stringify-without-jsonify@npm:1.0.1" + checksum: 10c0/cb168b61fd4de83e58d09aaa6425ef71001bae30d260e2c57e7d09a5fd82223e2f22a042dedaab8db23b7d9ae46854b08bb1f91675a8be11c5cffebef5fb66a5 + languageName: node + linkType: hard + +"json5@npm:^2.2.3": + version: 2.2.3 + resolution: "json5@npm:2.2.3" + bin: + json5: lib/cli.js + checksum: 10c0/5a04eed94810fa55c5ea138b2f7a5c12b97c3750bc63d11e511dcecbfef758003861522a070c2272764ee0f4e3e323862f386945aeb5b85b87ee43f084ba586c + languageName: node + linkType: hard + +"jwa@npm:^2.0.0": + version: 2.0.0 + resolution: "jwa@npm:2.0.0" + dependencies: + buffer-equal-constant-time: "npm:1.0.1" + ecdsa-sig-formatter: "npm:1.0.11" + safe-buffer: "npm:^5.0.1" + checksum: 10c0/6baab823b93c038ba1d2a9e531984dcadbc04e9eb98d171f4901b7a40d2be15961a359335de1671d78cb6d987f07cbe5d350d8143255977a889160c4d90fcc3c + languageName: node + linkType: hard + +"jws@npm:^4.0.0": + version: 4.0.0 + resolution: "jws@npm:4.0.0" + dependencies: + jwa: "npm:^2.0.0" + safe-buffer: "npm:^5.0.1" + checksum: 10c0/f1ca77ea5451e8dc5ee219cb7053b8a4f1254a79cb22417a2e1043c1eb8a569ae118c68f24d72a589e8a3dd1824697f47d6bd4fb4bebb93a3bdf53545e721661 + languageName: node + linkType: hard + +"keyv@npm:^4.5.3": + version: 4.5.4 + resolution: "keyv@npm:4.5.4" + dependencies: + json-buffer: "npm:3.0.1" + checksum: 10c0/aa52f3c5e18e16bb6324876bb8b59dd02acf782a4b789c7b2ae21107fab95fab3890ed448d4f8dba80ce05391eeac4bfabb4f02a20221342982f806fa2cf271e + languageName: node + linkType: hard + +"kleur@npm:^3.0.3": + version: 3.0.3 + resolution: "kleur@npm:3.0.3" + checksum: 10c0/cd3a0b8878e7d6d3799e54340efe3591ca787d9f95f109f28129bdd2915e37807bf8918bb295ab86afb8c82196beec5a1adcaf29042ce3f2bd932b038fe3aa4b + languageName: node + linkType: hard + +"kuler@npm:^2.0.0": + version: 2.0.0 + resolution: "kuler@npm:2.0.0" + checksum: 10c0/0a4e99d92ca373f8f74d1dc37931909c4d0d82aebc94cf2ba265771160fc12c8df34eaaac80805efbda367e2795cb1f1dd4c3d404b6b1cf38aec94035b503d2d + languageName: node + linkType: hard + +"leven@npm:^3.1.0": + version: 3.1.0 + resolution: "leven@npm:3.1.0" + checksum: 10c0/cd778ba3fbab0f4d0500b7e87d1f6e1f041507c56fdcd47e8256a3012c98aaee371d4c15e0a76e0386107af2d42e2b7466160a2d80688aaa03e66e49949f42df + languageName: node + linkType: hard + +"levn@npm:^0.4.1": + version: 0.4.1 + resolution: "levn@npm:0.4.1" + dependencies: + prelude-ls: "npm:^1.2.1" + type-check: "npm:~0.4.0" + checksum: 10c0/effb03cad7c89dfa5bd4f6989364bfc79994c2042ec5966cb9b95990e2edee5cd8969ddf42616a0373ac49fac1403437deaf6e9050fbbaa3546093a59b9ac94e + languageName: node + linkType: hard + +"lines-and-columns@npm:^1.1.6": + version: 1.2.4 + resolution: "lines-and-columns@npm:1.2.4" + checksum: 10c0/3da6ee62d4cd9f03f5dc90b4df2540fb85b352081bee77fe4bbcd12c9000ead7f35e0a38b8d09a9bb99b13223446dd8689ff3c4959807620726d788701a83d2d + languageName: node + linkType: hard + +"locate-path@npm:^5.0.0": + version: 5.0.0 + resolution: "locate-path@npm:5.0.0" + dependencies: + p-locate: "npm:^4.1.0" + checksum: 10c0/33a1c5247e87e022f9713e6213a744557a3e9ec32c5d0b5efb10aa3a38177615bf90221a5592674857039c1a0fd2063b82f285702d37b792d973e9e72ace6c59 + languageName: node + linkType: hard + +"locate-path@npm:^6.0.0": + version: 6.0.0 + resolution: "locate-path@npm:6.0.0" + dependencies: + p-locate: "npm:^5.0.0" + checksum: 10c0/d3972ab70dfe58ce620e64265f90162d247e87159b6126b01314dd67be43d50e96a50b517bce2d9452a79409c7614054c277b5232377de50416564a77ac7aad3 + languageName: node + linkType: hard + +"lodash.merge@npm:^4.6.2": + version: 4.6.2 + resolution: "lodash.merge@npm:4.6.2" + checksum: 10c0/402fa16a1edd7538de5b5903a90228aa48eb5533986ba7fa26606a49db2572bf414ff73a2c9f5d5fd36b31c46a5d5c7e1527749c07cbcf965ccff5fbdf32c506 + languageName: node + linkType: hard + +"lodash@npm:^4.17.21": + version: 4.17.21 + resolution: "lodash@npm:4.17.21" + checksum: 10c0/d8cbea072bb08655bb4c989da418994b073a608dffa608b09ac04b43a791b12aeae7cd7ad919aa4c925f33b48490b5cfe6c1f71d827956071dae2e7bb3a6b74c + languageName: node + linkType: hard + +"logform@npm:^2.6.0, logform@npm:^2.6.1": + version: 2.6.1 + resolution: "logform@npm:2.6.1" + dependencies: + "@colors/colors": "npm:1.6.0" + "@types/triple-beam": "npm:^1.3.2" + fecha: "npm:^4.2.0" + ms: "npm:^2.1.1" + safe-stable-stringify: "npm:^2.3.1" + triple-beam: "npm:^1.3.0" + checksum: 10c0/c20019336b1da8c08adea67dd7de2b0effdc6e35289c0156722924b571df94ba9f900ef55620c56bceb07cae7cc46057c9859accdee37a131251ba34d6789bce + languageName: node + linkType: hard + +"lru-cache@npm:^10.0.1, lru-cache@npm:^10.2.0": + version: 10.2.2 + resolution: "lru-cache@npm:10.2.2" + checksum: 10c0/402d31094335851220d0b00985084288136136992979d0e015f0f1697e15d1c86052d7d53ae86b614e5b058425606efffc6969a31a091085d7a2b80a8a1e26d6 + languageName: node + linkType: hard + +"lru-cache@npm:^5.1.1": + version: 5.1.1 + resolution: "lru-cache@npm:5.1.1" + dependencies: + yallist: "npm:^3.0.2" + checksum: 10c0/89b2ef2ef45f543011e38737b8a8622a2f8998cddf0e5437174ef8f1f70a8b9d14a918ab3e232cb3ba343b7abddffa667f0b59075b2b80e6b4d63c3de6127482 + languageName: node + linkType: hard + +"lru-cache@npm:^6.0.0": + version: 6.0.0 + resolution: "lru-cache@npm:6.0.0" + dependencies: + yallist: "npm:^4.0.0" + checksum: 10c0/cb53e582785c48187d7a188d3379c181b5ca2a9c78d2bce3e7dee36f32761d1c42983da3fe12b55cb74e1779fa94cdc2e5367c028a9b35317184ede0c07a30a9 + languageName: node + linkType: hard + +"make-dir@npm:^3.1.0": + version: 3.1.0 + resolution: "make-dir@npm:3.1.0" + dependencies: + semver: "npm:^6.0.0" + checksum: 10c0/56aaafefc49c2dfef02c5c95f9b196c4eb6988040cf2c712185c7fe5c99b4091591a7fc4d4eafaaefa70ff763a26f6ab8c3ff60b9e75ea19876f49b18667ecaa + languageName: node + linkType: hard + +"make-dir@npm:^4.0.0": + version: 4.0.0 + resolution: "make-dir@npm:4.0.0" + dependencies: + semver: "npm:^7.5.3" + checksum: 10c0/69b98a6c0b8e5c4fe9acb61608a9fbcfca1756d910f51e5dbe7a9e5cfb74fca9b8a0c8a0ffdf1294a740826c1ab4871d5bf3f62f72a3049e5eac6541ddffed68 + languageName: node + linkType: hard + +"make-fetch-happen@npm:^13.0.0": + version: 13.0.1 + resolution: "make-fetch-happen@npm:13.0.1" + dependencies: + "@npmcli/agent": "npm:^2.0.0" + cacache: "npm:^18.0.0" + http-cache-semantics: "npm:^4.1.1" + is-lambda: "npm:^1.0.1" + minipass: "npm:^7.0.2" + minipass-fetch: "npm:^3.0.0" + minipass-flush: "npm:^1.0.5" + minipass-pipeline: "npm:^1.2.4" + negotiator: "npm:^0.6.3" + proc-log: "npm:^4.2.0" + promise-retry: "npm:^2.0.1" + ssri: "npm:^10.0.0" + checksum: 10c0/df5f4dbb6d98153b751bccf4dc4cc500de85a96a9331db9805596c46aa9f99d9555983954e6c1266d9f981ae37a9e4647f42b9a4bb5466f867f4012e582c9e7e + languageName: node + linkType: hard + +"makeerror@npm:1.0.12": + version: 1.0.12 + resolution: "makeerror@npm:1.0.12" + dependencies: + tmpl: "npm:1.0.5" + checksum: 10c0/b0e6e599780ce6bab49cc413eba822f7d1f0dfebd1c103eaa3785c59e43e22c59018323cf9e1708f0ef5329e94a745d163fcbb6bff8e4c6742f9be9e86f3500c + languageName: node + linkType: hard + +"media-typer@npm:0.3.0": + version: 0.3.0 + resolution: "media-typer@npm:0.3.0" + checksum: 10c0/d160f31246907e79fed398470285f21bafb45a62869dc469b1c8877f3f064f5eabc4bcc122f9479b8b605bc5c76187d7871cf84c4ee3ecd3e487da1993279928 + languageName: node + linkType: hard + +"merge-descriptors@npm:1.0.3": + version: 1.0.3 + resolution: "merge-descriptors@npm:1.0.3" + checksum: 10c0/866b7094afd9293b5ea5dcd82d71f80e51514bed33b4c4e9f516795dc366612a4cbb4dc94356e943a8a6914889a914530badff27f397191b9b75cda20b6bae93 + languageName: node + linkType: hard + +"merge-stream@npm:^2.0.0": + version: 2.0.0 + resolution: "merge-stream@npm:2.0.0" + checksum: 10c0/867fdbb30a6d58b011449b8885601ec1690c3e41c759ecd5a9d609094f7aed0096c37823ff4a7190ef0b8f22cc86beb7049196ff68c016e3b3c671d0dac91ce5 + languageName: node + linkType: hard + +"merge2@npm:^1.3.0, merge2@npm:^1.4.1": + version: 1.4.1 + resolution: "merge2@npm:1.4.1" + checksum: 10c0/254a8a4605b58f450308fc474c82ac9a094848081bf4c06778200207820e5193726dc563a0d2c16468810516a5c97d9d3ea0ca6585d23c58ccfff2403e8dbbeb + languageName: node + linkType: hard + +"methods@npm:^1.1.2, methods@npm:~1.1.2": + version: 1.1.2 + resolution: "methods@npm:1.1.2" + checksum: 10c0/bdf7cc72ff0a33e3eede03708c08983c4d7a173f91348b4b1e4f47d4cdbf734433ad971e7d1e8c77247d9e5cd8adb81ea4c67b0a2db526b758b2233d7814b8b2 + languageName: node + linkType: hard + +"micromatch@npm:^4.0.4": + version: 4.0.8 + resolution: "micromatch@npm:4.0.8" + dependencies: + braces: "npm:^3.0.3" + picomatch: "npm:^2.3.1" + checksum: 10c0/166fa6eb926b9553f32ef81f5f531d27b4ce7da60e5baf8c021d043b27a388fb95e46a8038d5045877881e673f8134122b59624d5cecbd16eb50a42e7a6b5ca8 + languageName: node + linkType: hard + +"migrate@npm:^2.0.1": + version: 2.1.0 + resolution: "migrate@npm:2.1.0" + dependencies: + chalk: "npm:^4.1.2" + commander: "npm:^2.20.3" + dateformat: "npm:^4.6.3" + dotenv: "npm:^16.0.0" + inherits: "npm:^2.0.3" + minimatch: "npm:^9.0.1" + mkdirp: "npm:^3.0.1" + slug: "npm:^8.2.2" + bin: + migrate: bin/migrate + migrate-create: bin/migrate-create + migrate-down: bin/migrate-down + migrate-init: bin/migrate-init + migrate-list: bin/migrate-list + migrate-up: bin/migrate-up + checksum: 10c0/15a3ccd14e95f6c1eed87860860ec3195910e96fa23702867ad6ddbfa802a8c93ea94672192f1c64f868cf441449ac9aaaed629a89fb09d7e139c9502ae9b5f8 + languageName: node + linkType: hard + +"mime-db@npm:1.52.0": + version: 1.52.0 + resolution: "mime-db@npm:1.52.0" + checksum: 10c0/0557a01deebf45ac5f5777fe7740b2a5c309c6d62d40ceab4e23da9f821899ce7a900b7ac8157d4548ddbb7beffe9abc621250e6d182b0397ec7f10c7b91a5aa + languageName: node + linkType: hard + +"mime-types@npm:^2.1.12, mime-types@npm:~2.1.24, mime-types@npm:~2.1.34": + version: 2.1.35 + resolution: "mime-types@npm:2.1.35" + dependencies: + mime-db: "npm:1.52.0" + checksum: 10c0/82fb07ec56d8ff1fc999a84f2f217aa46cb6ed1033fefaabd5785b9a974ed225c90dc72fff460259e66b95b73648596dbcc50d51ed69cdf464af2d237d3149b2 + languageName: node + linkType: hard + +"mime@npm:1.6.0": + version: 1.6.0 + resolution: "mime@npm:1.6.0" + bin: + mime: cli.js + checksum: 10c0/b92cd0adc44888c7135a185bfd0dddc42c32606401c72896a842ae15da71eb88858f17669af41e498b463cd7eb998f7b48939a25b08374c7924a9c8a6f8a81b0 + languageName: node + linkType: hard + +"mime@npm:2.6.0": + version: 2.6.0 + resolution: "mime@npm:2.6.0" + bin: + mime: cli.js + checksum: 10c0/a7f2589900d9c16e3bdf7672d16a6274df903da958c1643c9c45771f0478f3846dcb1097f31eb9178452570271361e2149310931ec705c037210fc69639c8e6c + languageName: node + linkType: hard + +"mimic-fn@npm:^2.1.0": + version: 2.1.0 + resolution: "mimic-fn@npm:2.1.0" + checksum: 10c0/b26f5479d7ec6cc2bce275a08f146cf78f5e7b661b18114e2506dd91ec7ec47e7a25bf4360e5438094db0560bcc868079fb3b1fb3892b833c1ecbf63f80c95a4 + languageName: node + linkType: hard + +"mimic-response@npm:^3.1.0": + version: 3.1.0 + resolution: "mimic-response@npm:3.1.0" + checksum: 10c0/0d6f07ce6e03e9e4445bee655202153bdb8a98d67ee8dc965ac140900d7a2688343e6b4c9a72cfc9ef2f7944dfd76eef4ab2482eb7b293a68b84916bac735362 + languageName: node + linkType: hard + +"minimatch@npm:^3.0.4, minimatch@npm:^3.0.5, minimatch@npm:^3.1.1, minimatch@npm:^3.1.2": + version: 3.1.2 + resolution: "minimatch@npm:3.1.2" + dependencies: + brace-expansion: "npm:^1.1.7" + checksum: 10c0/0262810a8fc2e72cca45d6fd86bd349eee435eb95ac6aa45c9ea2180e7ee875ef44c32b55b5973ceabe95ea12682f6e3725cbb63d7a2d1da3ae1163c8b210311 + languageName: node + linkType: hard + +"minimatch@npm:^9.0.1, minimatch@npm:^9.0.4": + version: 9.0.4 + resolution: "minimatch@npm:9.0.4" + dependencies: + brace-expansion: "npm:^2.0.1" + checksum: 10c0/2c16f21f50e64922864e560ff97c587d15fd491f65d92a677a344e970fe62aafdbeafe648965fa96d33c061b4d0eabfe0213466203dd793367e7f28658cf6414 + languageName: node + linkType: hard + +"minimist@npm:^1.2.0, minimist@npm:^1.2.3": + version: 1.2.8 + resolution: "minimist@npm:1.2.8" + checksum: 10c0/19d3fcdca050087b84c2029841a093691a91259a47def2f18222f41e7645a0b7c44ef4b40e88a1e58a40c84d2ef0ee6047c55594d298146d0eb3f6b737c20ce6 + languageName: node + linkType: hard + +"minipass-collect@npm:^2.0.1": + version: 2.0.1 + resolution: "minipass-collect@npm:2.0.1" + dependencies: + minipass: "npm:^7.0.3" + checksum: 10c0/5167e73f62bb74cc5019594709c77e6a742051a647fe9499abf03c71dca75515b7959d67a764bdc4f8b361cf897fbf25e2d9869ee039203ed45240f48b9aa06e + languageName: node + linkType: hard + +"minipass-fetch@npm:^3.0.0": + version: 3.0.5 + resolution: "minipass-fetch@npm:3.0.5" + dependencies: + encoding: "npm:^0.1.13" + minipass: "npm:^7.0.3" + minipass-sized: "npm:^1.0.3" + minizlib: "npm:^2.1.2" + dependenciesMeta: + encoding: + optional: true + checksum: 10c0/9d702d57f556274286fdd97e406fc38a2f5c8d15e158b498d7393b1105974b21249289ec571fa2b51e038a4872bfc82710111cf75fae98c662f3d6f95e72152b + languageName: node + linkType: hard + +"minipass-flush@npm:^1.0.5": + version: 1.0.5 + resolution: "minipass-flush@npm:1.0.5" + dependencies: + minipass: "npm:^3.0.0" + checksum: 10c0/2a51b63feb799d2bb34669205eee7c0eaf9dce01883261a5b77410c9408aa447e478efd191b4de6fc1101e796ff5892f8443ef20d9544385819093dbb32d36bd + languageName: node + linkType: hard + +"minipass-pipeline@npm:^1.2.4": + version: 1.2.4 + resolution: "minipass-pipeline@npm:1.2.4" + dependencies: + minipass: "npm:^3.0.0" + checksum: 10c0/cbda57cea20b140b797505dc2cac71581a70b3247b84480c1fed5ca5ba46c25ecc25f68bfc9e6dcb1a6e9017dab5c7ada5eab73ad4f0a49d84e35093e0c643f2 + languageName: node + linkType: hard + +"minipass-sized@npm:^1.0.3": + version: 1.0.3 + resolution: "minipass-sized@npm:1.0.3" + dependencies: + minipass: "npm:^3.0.0" + checksum: 10c0/298f124753efdc745cfe0f2bdfdd81ba25b9f4e753ca4a2066eb17c821f25d48acea607dfc997633ee5bf7b6dfffb4eee4f2051eb168663f0b99fad2fa4829cb + languageName: node + linkType: hard + +"minipass@npm:^3.0.0": + version: 3.3.6 + resolution: "minipass@npm:3.3.6" + dependencies: + yallist: "npm:^4.0.0" + checksum: 10c0/a114746943afa1dbbca8249e706d1d38b85ed1298b530f5808ce51f8e9e941962e2a5ad2e00eae7dd21d8a4aae6586a66d4216d1a259385e9d0358f0c1eba16c + languageName: node + linkType: hard + +"minipass@npm:^5.0.0": + version: 5.0.0 + resolution: "minipass@npm:5.0.0" + checksum: 10c0/a91d8043f691796a8ac88df039da19933ef0f633e3d7f0d35dcd5373af49131cf2399bfc355f41515dc495e3990369c3858cd319e5c2722b4753c90bf3152462 + languageName: node + linkType: hard + +"minipass@npm:^5.0.0 || ^6.0.2 || ^7.0.0, minipass@npm:^7.0.2, minipass@npm:^7.0.3, minipass@npm:^7.1.2": + version: 7.1.2 + resolution: "minipass@npm:7.1.2" + checksum: 10c0/b0fd20bb9fb56e5fa9a8bfac539e8915ae07430a619e4b86ff71f5fc757ef3924b23b2c4230393af1eda647ed3d75739e4e0acb250a6b1eb277cf7f8fe449557 + languageName: node + linkType: hard + +"minizlib@npm:^2.1.1, minizlib@npm:^2.1.2": + version: 2.1.2 + resolution: "minizlib@npm:2.1.2" + dependencies: + minipass: "npm:^3.0.0" + yallist: "npm:^4.0.0" + checksum: 10c0/64fae024e1a7d0346a1102bb670085b17b7f95bf6cfdf5b128772ec8faf9ea211464ea4add406a3a6384a7d87a0cd1a96263692134323477b4fb43659a6cab78 + languageName: node + linkType: hard + +"mkdirp-classic@npm:^0.5.2, mkdirp-classic@npm:^0.5.3": + version: 0.5.3 + resolution: "mkdirp-classic@npm:0.5.3" + checksum: 10c0/95371d831d196960ddc3833cc6907e6b8f67ac5501a6582f47dfae5eb0f092e9f8ce88e0d83afcae95d6e2b61a01741ba03714eeafb6f7a6e9dcc158ac85b168 + languageName: node + linkType: hard + +"mkdirp@npm:^1.0.3, mkdirp@npm:^1.0.4": + version: 1.0.4 + resolution: "mkdirp@npm:1.0.4" + bin: + mkdirp: bin/cmd.js + checksum: 10c0/46ea0f3ffa8bc6a5bc0c7081ffc3907777f0ed6516888d40a518c5111f8366d97d2678911ad1a6882bf592fa9de6c784fea32e1687bb94e1f4944170af48a5cf + languageName: node + linkType: hard + +"mkdirp@npm:^3.0.1": + version: 3.0.1 + resolution: "mkdirp@npm:3.0.1" + bin: + mkdirp: dist/cjs/src/bin.js + checksum: 10c0/9f2b975e9246351f5e3a40dcfac99fcd0baa31fbfab615fe059fb11e51f10e4803c63de1f384c54d656e4db31d000e4767e9ef076a22e12a641357602e31d57d + languageName: node + linkType: hard + +"ms@npm:2.0.0": + version: 2.0.0 + resolution: "ms@npm:2.0.0" + checksum: 10c0/f8fda810b39fd7255bbdc451c46286e549794fcc700dc9cd1d25658bbc4dc2563a5de6fe7c60f798a16a60c6ceb53f033cb353f493f0cf63e5199b702943159d + languageName: node + linkType: hard + +"ms@npm:2.1.2": + version: 2.1.2 + resolution: "ms@npm:2.1.2" + checksum: 10c0/a437714e2f90dbf881b5191d35a6db792efbca5badf112f87b9e1c712aace4b4b9b742dd6537f3edf90fd6f684de897cec230abde57e87883766712ddda297cc + languageName: node + linkType: hard + +"ms@npm:2.1.3, ms@npm:^2.1.1": + version: 2.1.3 + resolution: "ms@npm:2.1.3" + checksum: 10c0/d924b57e7312b3b63ad21fc5b3dc0af5e78d61a1fc7cfb5457edaf26326bf62be5307cc87ffb6862ef1c2b33b0233cdb5d4f01c4c958cc0d660948b65a287a48 + languageName: node + linkType: hard + +"murmurhash@npm:^2.0.1": + version: 2.0.1 + resolution: "murmurhash@npm:2.0.1" + checksum: 10c0/f6c7cb12d6ebc9c1cfd232fe9406089e1ceb128d24245e852866ba28967271925d915140f77fef7c92ee29b13165f4537ce80a85c3d0550b1b5cdb9f8bcaa19f + languageName: node + linkType: hard + +"napi-build-utils@npm:^1.0.1": + version: 1.0.2 + resolution: "napi-build-utils@npm:1.0.2" + checksum: 10c0/37fd2cd0ff2ad20073ce78d83fd718a740d568b225924e753ae51cb69d68f330c80544d487e5e5bd18e28702ed2ca469c2424ad948becd1862c1b0209542b2e9 + languageName: node + linkType: hard + +"natural-compare-lite@npm:^1.4.0": + version: 1.4.0 + resolution: "natural-compare-lite@npm:1.4.0" + checksum: 10c0/f6cef26f5044515754802c0fc475d81426f3b90fe88c20fabe08771ce1f736ce46e0397c10acb569a4dd0acb84c7f1ee70676122f95d5bfdd747af3a6c6bbaa8 + languageName: node + linkType: hard + +"natural-compare@npm:^1.4.0": + version: 1.4.0 + resolution: "natural-compare@npm:1.4.0" + checksum: 10c0/f5f9a7974bfb28a91afafa254b197f0f22c684d4a1731763dda960d2c8e375b36c7d690e0d9dc8fba774c537af14a7e979129bca23d88d052fbeb9466955e447 + languageName: node + linkType: hard + +"negotiator@npm:0.6.3, negotiator@npm:^0.6.3": + version: 0.6.3 + resolution: "negotiator@npm:0.6.3" + checksum: 10c0/3ec9fd413e7bf071c937ae60d572bc67155262068ed522cf4b3be5edbe6ddf67d095ec03a3a14ebf8fc8e95f8e1d61be4869db0dbb0de696f6b837358bd43fc2 + languageName: node + linkType: hard + +"node-abi@npm:^3.3.0": + version: 3.65.0 + resolution: "node-abi@npm:3.65.0" + dependencies: + semver: "npm:^7.3.5" + checksum: 10c0/112672015d8f27d6be2f18d64569f28f5d6a15a94cc510da513c69c3e3ab5df6dac196ef13ff115a8fadb69b554974c47ef89b4f6350a2b02de2bca5c23db1e5 + languageName: node + linkType: hard + +"node-addon-api@npm:^5.0.0": + version: 5.1.0 + resolution: "node-addon-api@npm:5.1.0" + dependencies: + node-gyp: "npm:latest" + checksum: 10c0/0eb269786124ba6fad9df8007a149e03c199b3e5a3038125dfb3e747c2d5113d406a4e33f4de1ea600aa2339be1f137d55eba1a73ee34e5fff06c52a5c296d1d + languageName: node + linkType: hard + +"node-fetch@npm:^2.6.7": + version: 2.7.0 + resolution: "node-fetch@npm:2.7.0" + dependencies: + whatwg-url: "npm:^5.0.0" + peerDependencies: + encoding: ^0.1.0 + peerDependenciesMeta: + encoding: + optional: true + checksum: 10c0/b55786b6028208e6fbe594ccccc213cab67a72899c9234eb59dba51062a299ea853210fcf526998eaa2867b0963ad72338824450905679ff0fa304b8c5093ae8 + languageName: node + linkType: hard + +"node-gyp@npm:latest": + version: 10.1.0 + resolution: "node-gyp@npm:10.1.0" + dependencies: + env-paths: "npm:^2.2.0" + exponential-backoff: "npm:^3.1.1" + glob: "npm:^10.3.10" + graceful-fs: "npm:^4.2.6" + make-fetch-happen: "npm:^13.0.0" + nopt: "npm:^7.0.0" + proc-log: "npm:^3.0.0" + semver: "npm:^7.3.5" + tar: "npm:^6.1.2" + which: "npm:^4.0.0" + bin: + node-gyp: bin/node-gyp.js + checksum: 10c0/9cc821111ca244a01fb7f054db7523ab0a0cd837f665267eb962eb87695d71fb1e681f9e21464cc2fd7c05530dc4c81b810bca1a88f7d7186909b74477491a3c + languageName: node + linkType: hard + +"node-int64@npm:^0.4.0": + version: 0.4.0 + resolution: "node-int64@npm:0.4.0" + checksum: 10c0/a6a4d8369e2f2720e9c645255ffde909c0fbd41c92ea92a5607fc17055955daac99c1ff589d421eee12a0d24e99f7bfc2aabfeb1a4c14742f6c099a51863f31a + languageName: node + linkType: hard + +"node-releases@npm:^2.0.14": + version: 2.0.14 + resolution: "node-releases@npm:2.0.14" + checksum: 10c0/199fc93773ae70ec9969bc6d5ac5b2bbd6eb986ed1907d751f411fef3ede0e4bfdb45ceb43711f8078bea237b6036db8b1bf208f6ff2b70c7d615afd157f3ab9 + languageName: node + linkType: hard + +"nopt@npm:^5.0.0": + version: 5.0.0 + resolution: "nopt@npm:5.0.0" + dependencies: + abbrev: "npm:1" + bin: + nopt: bin/nopt.js + checksum: 10c0/fc5c4f07155cb455bf5fc3dd149fac421c1a40fd83c6bfe83aa82b52f02c17c5e88301321318adaa27611c8a6811423d51d29deaceab5fa158b585a61a551061 + languageName: node + linkType: hard + +"nopt@npm:^7.0.0": + version: 7.2.1 + resolution: "nopt@npm:7.2.1" + dependencies: + abbrev: "npm:^2.0.0" + bin: + nopt: bin/nopt.js + checksum: 10c0/a069c7c736767121242037a22a788863accfa932ab285a1eb569eb8cd534b09d17206f68c37f096ae785647435e0c5a5a0a67b42ec743e481a455e5ae6a6df81 + languageName: node + linkType: hard + +"nordigen-node@npm:^1.4.0": + version: 1.4.0 + resolution: "nordigen-node@npm:1.4.0" + dependencies: + axios: "npm:^1.2.1" + dotenv: "npm:^10.0.0" + checksum: 10c0/a04ec90480e4e65b2169d909ac9ea3044f764d59283162420d287a6b229808754dc78c758637724d63c87aa77f2237bc47543f521b0b4057ed3980d6db137e1a + languageName: node + linkType: hard + +"normalize-path@npm:^3.0.0": + version: 3.0.0 + resolution: "normalize-path@npm:3.0.0" + checksum: 10c0/e008c8142bcc335b5e38cf0d63cfd39d6cf2d97480af9abdbe9a439221fd4d749763bab492a8ee708ce7a194bb00c9da6d0a115018672310850489137b3da046 + languageName: node + linkType: hard + +"npm-run-path@npm:^4.0.1": + version: 4.0.1 + resolution: "npm-run-path@npm:4.0.1" + dependencies: + path-key: "npm:^3.0.0" + checksum: 10c0/6f9353a95288f8455cf64cbeb707b28826a7f29690244c1e4bb61ec573256e021b6ad6651b394eb1ccfd00d6ec50147253aba2c5fe58a57ceb111fad62c519ac + languageName: node + linkType: hard + +"npmlog@npm:^5.0.1": + version: 5.0.1 + resolution: "npmlog@npm:5.0.1" + dependencies: + are-we-there-yet: "npm:^2.0.0" + console-control-strings: "npm:^1.1.0" + gauge: "npm:^3.0.0" + set-blocking: "npm:^2.0.0" + checksum: 10c0/489ba519031013001135c463406f55491a17fc7da295c18a04937fe3a4d523fd65e88dd418a28b967ab743d913fdeba1e29838ce0ad8c75557057c481f7d49fa + languageName: node + linkType: hard + +"object-assign@npm:^4, object-assign@npm:^4.1.1": + version: 4.1.1 + resolution: "object-assign@npm:4.1.1" + checksum: 10c0/1f4df9945120325d041ccf7b86f31e8bcc14e73d29171e37a7903050e96b81323784ec59f93f102ec635bcf6fa8034ba3ea0a8c7e69fa202b87ae3b6cec5a414 + languageName: node + linkType: hard + +"object-hash@npm:^2.2.0": + version: 2.2.0 + resolution: "object-hash@npm:2.2.0" + checksum: 10c0/1527de843926c5442ed61f8bdddfc7dc181b6497f725b0e89fcf50a55d9c803088763ed447cac85a5aa65345f1e99c2469ba679a54349ef3c4c0aeaa396a3eb9 + languageName: node + linkType: hard + +"object-inspect@npm:^1.13.1": + version: 1.13.1 + resolution: "object-inspect@npm:1.13.1" + checksum: 10c0/fad603f408e345c82e946abdf4bfd774260a5ed3e5997a0b057c44153ac32c7271ff19e3a5ae39c858da683ba045ccac2f65245c12763ce4e8594f818f4a648d + languageName: node + linkType: hard + +"oidc-token-hash@npm:^5.0.3": + version: 5.0.3 + resolution: "oidc-token-hash@npm:5.0.3" + checksum: 10c0/d0dc0551406f09577874155cc83cf69c39e4b826293d50bb6c37936698aeca17d4bcee356ab910c859e53e83f2728a2acbd041020165191353b29de51fbca615 + languageName: node + linkType: hard + +"on-finished@npm:2.4.1": + version: 2.4.1 + resolution: "on-finished@npm:2.4.1" + dependencies: + ee-first: "npm:1.1.1" + checksum: 10c0/46fb11b9063782f2d9968863d9cbba33d77aa13c17f895f56129c274318b86500b22af3a160fe9995aa41317efcd22941b6eba747f718ced08d9a73afdb087b4 + languageName: node + linkType: hard + +"on-headers@npm:1.0.1": + version: 1.0.1 + resolution: "on-headers@npm:1.0.1" + checksum: 10c0/060229267db33d0f56b03f59f4a1edf50130bf51498599b1f4b1a1bcf364bd8a9e05c3a984f0d0d384e2cdcdacdf176379a17e67bf5041e84c8c5f4a1938ec82 + languageName: node + linkType: hard + +"once@npm:^1.3.0, once@npm:^1.3.1, once@npm:^1.4.0": + version: 1.4.0 + resolution: "once@npm:1.4.0" + dependencies: + wrappy: "npm:1" + checksum: 10c0/5d48aca287dfefabd756621c5dfce5c91a549a93e9fdb7b8246bc4c4790aa2ec17b34a260530474635147aeb631a2dcc8b32c613df0675f96041cbb8244517d0 + languageName: node + linkType: hard + +"one-time@npm:^1.0.0": + version: 1.0.0 + resolution: "one-time@npm:1.0.0" + dependencies: + fn.name: "npm:1.x.x" + checksum: 10c0/6e4887b331edbb954f4e915831cbec0a7b9956c36f4feb5f6de98c448ac02ff881fd8d9b55a6b1b55030af184c6b648f340a76eb211812f4ad8c9b4b8692fdaa + languageName: node + linkType: hard + +"onetime@npm:^5.1.2": + version: 5.1.2 + resolution: "onetime@npm:5.1.2" + dependencies: + mimic-fn: "npm:^2.1.0" + checksum: 10c0/ffcef6fbb2692c3c40749f31ea2e22677a876daea92959b8a80b521d95cca7a668c884d8b2045d1d8ee7d56796aa405c405462af112a1477594cc63531baeb8f + languageName: node + linkType: hard + +"openid-client@npm:^5.4.2": + version: 5.6.5 + resolution: "openid-client@npm:5.6.5" + dependencies: + jose: "npm:^4.15.5" + lru-cache: "npm:^6.0.0" + object-hash: "npm:^2.2.0" + oidc-token-hash: "npm:^5.0.3" + checksum: 10c0/4308dcd37a9ffb1efc2ede0bc556ae42ccc2569e71baa52a03ddfa44407bf403d4534286f6f571381c5eaa1845c609ed699a5eb0d350acfb8c3bacb72c2a6890 + languageName: node + linkType: hard + +"optionator@npm:^0.9.3": + version: 0.9.4 + resolution: "optionator@npm:0.9.4" + dependencies: + deep-is: "npm:^0.1.3" + fast-levenshtein: "npm:^2.0.6" + levn: "npm:^0.4.1" + prelude-ls: "npm:^1.2.1" + type-check: "npm:^0.4.0" + word-wrap: "npm:^1.2.5" + checksum: 10c0/4afb687a059ee65b61df74dfe87d8d6815cd6883cb8b3d5883a910df72d0f5d029821f37025e4bccf4048873dbdb09acc6d303d27b8f76b1a80dd5a7d5334675 + languageName: node + linkType: hard + +"p-limit@npm:^2.2.0": + version: 2.3.0 + resolution: "p-limit@npm:2.3.0" + dependencies: + p-try: "npm:^2.0.0" + checksum: 10c0/8da01ac53efe6a627080fafc127c873da40c18d87b3f5d5492d465bb85ec7207e153948df6b9cbaeb130be70152f874229b8242ee2be84c0794082510af97f12 + languageName: node + linkType: hard + +"p-limit@npm:^3.0.2, p-limit@npm:^3.1.0": + version: 3.1.0 + resolution: "p-limit@npm:3.1.0" + dependencies: + yocto-queue: "npm:^0.1.0" + checksum: 10c0/9db675949dbdc9c3763c89e748d0ef8bdad0afbb24d49ceaf4c46c02c77d30db4e0652ed36d0a0a7a95154335fab810d95c86153105bb73b3a90448e2bb14e1a + languageName: node + linkType: hard + +"p-locate@npm:^4.1.0": + version: 4.1.0 + resolution: "p-locate@npm:4.1.0" + dependencies: + p-limit: "npm:^2.2.0" + checksum: 10c0/1b476ad69ad7f6059744f343b26d51ce091508935c1dbb80c4e0a2f397ffce0ca3a1f9f5cd3c7ce19d7929a09719d5c65fe70d8ee289c3f267cd36f2881813e9 + languageName: node + linkType: hard + +"p-locate@npm:^5.0.0": + version: 5.0.0 + resolution: "p-locate@npm:5.0.0" + dependencies: + p-limit: "npm:^3.0.2" + checksum: 10c0/2290d627ab7903b8b70d11d384fee714b797f6040d9278932754a6860845c4d3190603a0772a663c8cb5a7b21d1b16acb3a6487ebcafa9773094edc3dfe6009a + languageName: node + linkType: hard + +"p-map@npm:^4.0.0": + version: 4.0.0 + resolution: "p-map@npm:4.0.0" + dependencies: + aggregate-error: "npm:^3.0.0" + checksum: 10c0/592c05bd6262c466ce269ff172bb8de7c6975afca9b50c975135b974e9bdaafbfe80e61aaaf5be6d1200ba08b30ead04b88cfa7e25ff1e3b93ab28c9f62a2c75 + languageName: node + linkType: hard + +"p-try@npm:^2.0.0": + version: 2.2.0 + resolution: "p-try@npm:2.2.0" + checksum: 10c0/c36c19907734c904b16994e6535b02c36c2224d433e01a2f1ab777237f4d86e6289fd5fd464850491e940379d4606ed850c03e0f9ab600b0ebddb511312e177f + languageName: node + linkType: hard + +"parent-module@npm:^1.0.0": + version: 1.0.1 + resolution: "parent-module@npm:1.0.1" + dependencies: + callsites: "npm:^3.0.0" + checksum: 10c0/c63d6e80000d4babd11978e0d3fee386ca7752a02b035fd2435960ffaa7219dc42146f07069fb65e6e8bf1caef89daf9af7535a39bddf354d78bf50d8294f556 + languageName: node + linkType: hard + +"parse-json@npm:^5.2.0": + version: 5.2.0 + resolution: "parse-json@npm:5.2.0" + dependencies: + "@babel/code-frame": "npm:^7.0.0" + error-ex: "npm:^1.3.1" + json-parse-even-better-errors: "npm:^2.3.0" + lines-and-columns: "npm:^1.1.6" + checksum: 10c0/77947f2253005be7a12d858aedbafa09c9ae39eb4863adf330f7b416ca4f4a08132e453e08de2db46459256fb66afaac5ee758b44fe6541b7cdaf9d252e59585 + languageName: node + linkType: hard + +"parseurl@npm:~1.3.3": + version: 1.3.3 + resolution: "parseurl@npm:1.3.3" + checksum: 10c0/90dd4760d6f6174adb9f20cf0965ae12e23879b5f5464f38e92fce8073354341e4b3b76fa3d878351efe7d01e617121955284cfd002ab087fba1a0726ec0b4f5 + languageName: node + linkType: hard + +"path-exists@npm:^4.0.0": + version: 4.0.0 + resolution: "path-exists@npm:4.0.0" + checksum: 10c0/8c0bd3f5238188197dc78dced15207a4716c51cc4e3624c44fc97acf69558f5ebb9a2afff486fe1b4ee148e0c133e96c5e11a9aa5c48a3006e3467da070e5e1b + languageName: node + linkType: hard + +"path-is-absolute@npm:^1.0.0": + version: 1.0.1 + resolution: "path-is-absolute@npm:1.0.1" + checksum: 10c0/127da03c82172a2a50099cddbf02510c1791fc2cc5f7713ddb613a56838db1e8168b121a920079d052e0936c23005562059756d653b7c544c53185efe53be078 + languageName: node + linkType: hard + +"path-key@npm:^3.0.0, path-key@npm:^3.1.0": + version: 3.1.1 + resolution: "path-key@npm:3.1.1" + checksum: 10c0/748c43efd5a569c039d7a00a03b58eecd1d75f3999f5a28303d75f521288df4823bc057d8784eb72358b2895a05f29a070bc9f1f17d28226cc4e62494cc58c4c + languageName: node + linkType: hard + +"path-parse@npm:^1.0.7": + version: 1.0.7 + resolution: "path-parse@npm:1.0.7" + checksum: 10c0/11ce261f9d294cc7a58d6a574b7f1b935842355ec66fba3c3fd79e0f036462eaf07d0aa95bb74ff432f9afef97ce1926c720988c6a7451d8a584930ae7de86e1 + languageName: node + linkType: hard + +"path-scurry@npm:^1.11.1": + version: 1.11.1 + resolution: "path-scurry@npm:1.11.1" + dependencies: + lru-cache: "npm:^10.2.0" + minipass: "npm:^5.0.0 || ^6.0.2 || ^7.0.0" + checksum: 10c0/32a13711a2a505616ae1cc1b5076801e453e7aae6ac40ab55b388bb91b9d0547a52f5aaceff710ea400205f18691120d4431e520afbe4266b836fadede15872d + languageName: node + linkType: hard + +"path-to-regexp@npm:0.1.10": + version: 0.1.10 + resolution: "path-to-regexp@npm:0.1.10" + checksum: 10c0/34196775b9113ca6df88e94c8d83ba82c0e1a2063dd33bfe2803a980da8d49b91db8104f49d5191b44ea780d46b8670ce2b7f4a5e349b0c48c6779b653f1afe4 + languageName: node + linkType: hard + +"path-type@npm:^4.0.0": + version: 4.0.0 + resolution: "path-type@npm:4.0.0" + checksum: 10c0/666f6973f332f27581371efaf303fd6c272cc43c2057b37aa99e3643158c7e4b2626549555d88626e99ea9e046f82f32e41bbde5f1508547e9a11b149b52387c + languageName: node + linkType: hard + +"picocolors@npm:^1.0.0, picocolors@npm:^1.0.1": + version: 1.0.1 + resolution: "picocolors@npm:1.0.1" + checksum: 10c0/c63cdad2bf812ef0d66c8db29583802355d4ca67b9285d846f390cc15c2f6ccb94e8cb7eb6a6e97fc5990a6d3ad4ae42d86c84d3146e667c739a4234ed50d400 + languageName: node + linkType: hard + +"picomatch@npm:^2.0.4, picomatch@npm:^2.2.3, picomatch@npm:^2.3.1": + version: 2.3.1 + resolution: "picomatch@npm:2.3.1" + checksum: 10c0/26c02b8d06f03206fc2ab8d16f19960f2ff9e81a658f831ecb656d8f17d9edc799e8364b1f4a7873e89d9702dff96204be0fa26fe4181f6843f040f819dac4be + languageName: node + linkType: hard + +"pirates@npm:^4.0.4": + version: 4.0.6 + resolution: "pirates@npm:4.0.6" + checksum: 10c0/00d5fa51f8dded94d7429700fb91a0c1ead00ae2c7fd27089f0c5b63e6eca36197fe46384631872690a66f390c5e27198e99006ab77ae472692ab9c2ca903f36 + languageName: node + linkType: hard + +"pkg-dir@npm:^4.2.0": + version: 4.2.0 + resolution: "pkg-dir@npm:4.2.0" + dependencies: + find-up: "npm:^4.0.0" + checksum: 10c0/c56bda7769e04907a88423feb320babaed0711af8c436ce3e56763ab1021ba107c7b0cafb11cde7529f669cfc22bffcaebffb573645cbd63842ea9fb17cd7728 + languageName: node + linkType: hard + +"prebuild-install@npm:^7.1.1": + version: 7.1.2 + resolution: "prebuild-install@npm:7.1.2" + dependencies: + detect-libc: "npm:^2.0.0" + expand-template: "npm:^2.0.3" + github-from-package: "npm:0.0.0" + minimist: "npm:^1.2.3" + mkdirp-classic: "npm:^0.5.3" + napi-build-utils: "npm:^1.0.1" + node-abi: "npm:^3.3.0" + pump: "npm:^3.0.0" + rc: "npm:^1.2.7" + simple-get: "npm:^4.0.0" + tar-fs: "npm:^2.0.0" + tunnel-agent: "npm:^0.6.0" + bin: + prebuild-install: bin.js + checksum: 10c0/e64868ba9ef2068fd7264f5b03e5298a901e02a450acdb1f56258d88c09dea601eefdb3d1dfdff8513fdd230a92961712be0676192626a3b4d01ba154d48bdd3 + languageName: node + linkType: hard + +"prelude-ls@npm:^1.2.1": + version: 1.2.1 + resolution: "prelude-ls@npm:1.2.1" + checksum: 10c0/b00d617431e7886c520a6f498a2e14c75ec58f6d93ba48c3b639cf241b54232d90daa05d83a9e9b9fef6baa63cb7e1e4602c2372fea5bc169668401eb127d0cd + languageName: node + linkType: hard + +"prettier-linter-helpers@npm:^1.0.0": + version: 1.0.0 + resolution: "prettier-linter-helpers@npm:1.0.0" + dependencies: + fast-diff: "npm:^1.1.2" + checksum: 10c0/81e0027d731b7b3697ccd2129470ed9913ecb111e4ec175a12f0fcfab0096516373bf0af2fef132af50cafb0a905b74ff57996d615f59512bb9ac7378fcc64ab + languageName: node + linkType: hard + +"prettier@npm:^2.8.3": + version: 2.8.8 + resolution: "prettier@npm:2.8.8" + bin: + prettier: bin-prettier.js + checksum: 10c0/463ea8f9a0946cd5b828d8cf27bd8b567345cf02f56562d5ecde198b91f47a76b7ac9eae0facd247ace70e927143af6135e8cf411986b8cb8478784a4d6d724a + languageName: node + linkType: hard + +"pretty-format@npm:^29.0.0, pretty-format@npm:^29.7.0": + version: 29.7.0 + resolution: "pretty-format@npm:29.7.0" + dependencies: + "@jest/schemas": "npm:^29.6.3" + ansi-styles: "npm:^5.0.0" + react-is: "npm:^18.0.0" + checksum: 10c0/edc5ff89f51916f036c62ed433506b55446ff739358de77207e63e88a28ca2894caac6e73dcb68166a606e51c8087d32d400473e6a9fdd2dbe743f46c9c0276f + languageName: node + linkType: hard + +"proc-log@npm:^3.0.0": + version: 3.0.0 + resolution: "proc-log@npm:3.0.0" + checksum: 10c0/f66430e4ff947dbb996058f6fd22de2c66612ae1a89b097744e17fb18a4e8e7a86db99eda52ccf15e53f00b63f4ec0b0911581ff2aac0355b625c8eac509b0dc + languageName: node + linkType: hard + +"proc-log@npm:^4.2.0": + version: 4.2.0 + resolution: "proc-log@npm:4.2.0" + checksum: 10c0/17db4757c2a5c44c1e545170e6c70a26f7de58feb985091fb1763f5081cab3d01b181fb2dd240c9f4a4255a1d9227d163d5771b7e69c9e49a561692db865efb9 + languageName: node + linkType: hard + +"promise-retry@npm:^2.0.1": + version: 2.0.1 + resolution: "promise-retry@npm:2.0.1" + dependencies: + err-code: "npm:^2.0.2" + retry: "npm:^0.12.0" + checksum: 10c0/9c7045a1a2928094b5b9b15336dcd2a7b1c052f674550df63cc3f36cd44028e5080448175b6f6ca32b642de81150f5e7b1a98b728f15cb069f2dd60ac2616b96 + languageName: node + linkType: hard + +"prompts@npm:^2.0.1": + version: 2.4.2 + resolution: "prompts@npm:2.4.2" + dependencies: + kleur: "npm:^3.0.3" + sisteransi: "npm:^1.0.5" + checksum: 10c0/16f1ac2977b19fe2cf53f8411cc98db7a3c8b115c479b2ca5c82b5527cd937aa405fa04f9a5960abeb9daef53191b53b4d13e35c1f5d50e8718c76917c5f1ea4 + languageName: node + linkType: hard + +"properties-reader@npm:^2.2.0": + version: 2.3.0 + resolution: "properties-reader@npm:2.3.0" + dependencies: + mkdirp: "npm:^1.0.4" + checksum: 10c0/f665057e3a9076c643ba1198afcc71703eda227a59913252f7ff9467ece8d29c0cf8bf14bf1abcaef71570840c32a4e257e6c39b7550451bbff1a777efcf5667 + languageName: node + linkType: hard + +"proxy-addr@npm:~2.0.7": + version: 2.0.7 + resolution: "proxy-addr@npm:2.0.7" + dependencies: + forwarded: "npm:0.2.0" + ipaddr.js: "npm:1.9.1" + checksum: 10c0/c3eed999781a35f7fd935f398b6d8920b6fb00bbc14287bc6de78128ccc1a02c89b95b56742bf7cf0362cc333c61d138532049c7dedc7a328ef13343eff81210 + languageName: node + linkType: hard + +"proxy-from-env@npm:^1.1.0": + version: 1.1.0 + resolution: "proxy-from-env@npm:1.1.0" + checksum: 10c0/fe7dd8b1bdbbbea18d1459107729c3e4a2243ca870d26d34c2c1bcd3e4425b7bcc5112362df2d93cc7fb9746f6142b5e272fd1cc5c86ddf8580175186f6ad42b + languageName: node + linkType: hard + +"pump@npm:^3.0.0": + version: 3.0.0 + resolution: "pump@npm:3.0.0" + dependencies: + end-of-stream: "npm:^1.1.0" + once: "npm:^1.3.1" + checksum: 10c0/bbdeda4f747cdf47db97428f3a135728669e56a0ae5f354a9ac5b74556556f5446a46f720a8f14ca2ece5be9b4d5d23c346db02b555f46739934cc6c093a5478 + languageName: node + linkType: hard + +"punycode@npm:^2.1.0": + version: 2.3.1 + resolution: "punycode@npm:2.3.1" + checksum: 10c0/14f76a8206bc3464f794fb2e3d3cc665ae416c01893ad7a02b23766eb07159144ee612ad67af5e84fa4479ccfe67678c4feb126b0485651b302babf66f04f9e9 + languageName: node + linkType: hard + +"pure-rand@npm:^6.0.0": + version: 6.1.0 + resolution: "pure-rand@npm:6.1.0" + checksum: 10c0/1abe217897bf74dcb3a0c9aba3555fe975023147b48db540aa2faf507aee91c03bf54f6aef0eb2bf59cc259a16d06b28eca37f0dc426d94f4692aeff02fb0e65 + languageName: node + linkType: hard + +"qs@npm:6.11.0": + version: 6.11.0 + resolution: "qs@npm:6.11.0" + dependencies: + side-channel: "npm:^1.0.4" + checksum: 10c0/4e4875e4d7c7c31c233d07a448e7e4650f456178b9dd3766b7cfa13158fdb24ecb8c4f059fa91e820dc6ab9f2d243721d071c9c0378892dcdad86e9e9a27c68f + languageName: node + linkType: hard + +"qs@npm:6.13.0": + version: 6.13.0 + resolution: "qs@npm:6.13.0" + dependencies: + side-channel: "npm:^1.0.6" + checksum: 10c0/62372cdeec24dc83a9fb240b7533c0fdcf0c5f7e0b83343edd7310f0ab4c8205a5e7c56406531f2e47e1b4878a3821d652be4192c841de5b032ca83619d8f860 + languageName: node + linkType: hard + +"qs@npm:^6.11.0": + version: 6.12.1 + resolution: "qs@npm:6.12.1" + dependencies: + side-channel: "npm:^1.0.6" + checksum: 10c0/439e6d7c6583e7c69f2cab2c39c55b97db7ce576e4c7c469082b938b7fc8746e8d547baacb69b4cd2b6666484776c3f4840ad7163a4c5326300b0afa0acdd84b + languageName: node + linkType: hard + +"queue-microtask@npm:^1.2.2": + version: 1.2.3 + resolution: "queue-microtask@npm:1.2.3" + checksum: 10c0/900a93d3cdae3acd7d16f642c29a642aea32c2026446151f0778c62ac089d4b8e6c986811076e1ae180a694cedf077d453a11b58ff0a865629a4f82ab558e102 + languageName: node + linkType: hard + +"range-parser@npm:~1.2.1": + version: 1.2.1 + resolution: "range-parser@npm:1.2.1" + checksum: 10c0/96c032ac2475c8027b7a4e9fe22dc0dfe0f6d90b85e496e0f016fbdb99d6d066de0112e680805075bd989905e2123b3b3d002765149294dce0c1f7f01fcc2ea0 + languageName: node + linkType: hard + +"raw-body@npm:2.5.2": + version: 2.5.2 + resolution: "raw-body@npm:2.5.2" + dependencies: + bytes: "npm:3.1.2" + http-errors: "npm:2.0.0" + iconv-lite: "npm:0.4.24" + unpipe: "npm:1.0.0" + checksum: 10c0/b201c4b66049369a60e766318caff5cb3cc5a900efd89bdac431463822d976ad0670912c931fdbdcf5543207daf6f6833bca57aa116e1661d2ea91e12ca692c4 + languageName: node + linkType: hard + +"rc@npm:^1.2.7": + version: 1.2.8 + resolution: "rc@npm:1.2.8" + dependencies: + deep-extend: "npm:^0.6.0" + ini: "npm:~1.3.0" + minimist: "npm:^1.2.0" + strip-json-comments: "npm:~2.0.1" + bin: + rc: ./cli.js + checksum: 10c0/24a07653150f0d9ac7168e52943cc3cb4b7a22c0e43c7dff3219977c2fdca5a2760a304a029c20811a0e79d351f57d46c9bde216193a0f73978496afc2b85b15 + languageName: node + linkType: hard + +"react-is@npm:^18.0.0": + version: 18.3.1 + resolution: "react-is@npm:18.3.1" + checksum: 10c0/f2f1e60010c683479e74c63f96b09fb41603527cd131a9959e2aee1e5a8b0caf270b365e5ca77d4a6b18aae659b60a86150bb3979073528877029b35aecd2072 + languageName: node + linkType: hard + +"readable-stream@npm:^3.1.1, readable-stream@npm:^3.4.0, readable-stream@npm:^3.6.0, readable-stream@npm:^3.6.2": + version: 3.6.2 + resolution: "readable-stream@npm:3.6.2" + dependencies: + inherits: "npm:^2.0.3" + string_decoder: "npm:^1.1.1" + util-deprecate: "npm:^1.0.1" + checksum: 10c0/e37be5c79c376fdd088a45fa31ea2e423e5d48854be7a22a58869b4e84d25047b193f6acb54f1012331e1bcd667ffb569c01b99d36b0bd59658fb33f513511b7 + languageName: node + linkType: hard + +"regenerator-runtime@npm:^0.14.0": + version: 0.14.1 + resolution: "regenerator-runtime@npm:0.14.1" + checksum: 10c0/1b16eb2c4bceb1665c89de70dcb64126a22bc8eb958feef3cd68fe11ac6d2a4899b5cd1b80b0774c7c03591dc57d16631a7f69d2daa2ec98100e2f29f7ec4cc4 + languageName: node + linkType: hard + +"require-directory@npm:^2.1.1": + version: 2.1.1 + resolution: "require-directory@npm:2.1.1" + checksum: 10c0/83aa76a7bc1531f68d92c75a2ca2f54f1b01463cb566cf3fbc787d0de8be30c9dbc211d1d46be3497dac5785fe296f2dd11d531945ac29730643357978966e99 + languageName: node + linkType: hard + +"resolve-cwd@npm:^3.0.0": + version: 3.0.0 + resolution: "resolve-cwd@npm:3.0.0" + dependencies: + resolve-from: "npm:^5.0.0" + checksum: 10c0/e608a3ebd15356264653c32d7ecbc8fd702f94c6703ea4ac2fb81d9c359180cba0ae2e6b71faa446631ed6145454d5a56b227efc33a2d40638ac13f8beb20ee4 + languageName: node + linkType: hard + +"resolve-from@npm:^4.0.0": + version: 4.0.0 + resolution: "resolve-from@npm:4.0.0" + checksum: 10c0/8408eec31a3112ef96e3746c37be7d64020cda07c03a920f5024e77290a218ea758b26ca9529fd7b1ad283947f34b2291c1c0f6aa0ed34acfdda9c6014c8d190 + languageName: node + linkType: hard + +"resolve-from@npm:^5.0.0": + version: 5.0.0 + resolution: "resolve-from@npm:5.0.0" + checksum: 10c0/b21cb7f1fb746de8107b9febab60095187781137fd803e6a59a76d421444b1531b641bba5857f5dc011974d8a5c635d61cec49e6bd3b7fc20e01f0fafc4efbf2 + languageName: node + linkType: hard + +"resolve.exports@npm:^2.0.0": + version: 2.0.2 + resolution: "resolve.exports@npm:2.0.2" + checksum: 10c0/cc4cffdc25447cf34730f388dca5021156ba9302a3bad3d7f168e790dc74b2827dff603f1bc6ad3d299bac269828dca96dd77e036dc9fba6a2a1807c47ab5c98 + languageName: node + linkType: hard + +"resolve@npm:^1.20.0": + version: 1.22.8 + resolution: "resolve@npm:1.22.8" + dependencies: + is-core-module: "npm:^2.13.0" + path-parse: "npm:^1.0.7" + supports-preserve-symlinks-flag: "npm:^1.0.0" + bin: + resolve: bin/resolve + checksum: 10c0/07e179f4375e1fd072cfb72ad66d78547f86e6196c4014b31cb0b8bb1db5f7ca871f922d08da0fbc05b94e9fd42206f819648fa3b5b873ebbc8e1dc68fec433a + languageName: node + linkType: hard + +"resolve@patch:resolve@npm%3A^1.20.0#optional!builtin<compat/resolve>": + version: 1.22.8 + resolution: "resolve@patch:resolve@npm%3A1.22.8#optional!builtin<compat/resolve>::version=1.22.8&hash=c3c19d" + dependencies: + is-core-module: "npm:^2.13.0" + path-parse: "npm:^1.0.7" + supports-preserve-symlinks-flag: "npm:^1.0.0" + bin: + resolve: bin/resolve + checksum: 10c0/0446f024439cd2e50c6c8fa8ba77eaa8370b4180f401a96abf3d1ebc770ac51c1955e12764cde449fde3fff480a61f84388e3505ecdbab778f4bef5f8212c729 + languageName: node + linkType: hard + +"retry@npm:^0.12.0": + version: 0.12.0 + resolution: "retry@npm:0.12.0" + checksum: 10c0/59933e8501727ba13ad73ef4a04d5280b3717fd650408460c987392efe9d7be2040778ed8ebe933c5cbd63da3dcc37919c141ef8af0a54a6e4fca5a2af177bfe + languageName: node + linkType: hard + +"reusify@npm:^1.0.4": + version: 1.0.4 + resolution: "reusify@npm:1.0.4" + checksum: 10c0/c19ef26e4e188f408922c46f7ff480d38e8dfc55d448310dfb518736b23ed2c4f547fb64a6ed5bdba92cd7e7ddc889d36ff78f794816d5e71498d645ef476107 + languageName: node + linkType: hard + +"rimraf@npm:^3.0.2": + version: 3.0.2 + resolution: "rimraf@npm:3.0.2" + dependencies: + glob: "npm:^7.1.3" + bin: + rimraf: bin.js + checksum: 10c0/9cb7757acb489bd83757ba1a274ab545eafd75598a9d817e0c3f8b164238dd90eba50d6b848bd4dcc5f3040912e882dc7ba71653e35af660d77b25c381d402e8 + languageName: node + linkType: hard + +"run-parallel@npm:^1.1.9": + version: 1.2.0 + resolution: "run-parallel@npm:1.2.0" + dependencies: + queue-microtask: "npm:^1.2.2" + checksum: 10c0/200b5ab25b5b8b7113f9901bfe3afc347e19bb7475b267d55ad0eb86a62a46d77510cb0f232507c9e5d497ebda569a08a9867d0d14f57a82ad5564d991588b39 + languageName: node + linkType: hard + +"safe-buffer@npm:5.2.1, safe-buffer@npm:^5.0.1, safe-buffer@npm:~5.2.0": + version: 5.2.1 + resolution: "safe-buffer@npm:5.2.1" + checksum: 10c0/6501914237c0a86e9675d4e51d89ca3c21ffd6a31642efeba25ad65720bce6921c9e7e974e5be91a786b25aa058b5303285d3c15dbabf983a919f5f630d349f3 + languageName: node + linkType: hard + +"safe-stable-stringify@npm:^2.3.1": + version: 2.4.3 + resolution: "safe-stable-stringify@npm:2.4.3" + checksum: 10c0/81dede06b8f2ae794efd868b1e281e3c9000e57b39801c6c162267eb9efda17bd7a9eafa7379e1f1cacd528d4ced7c80d7460ad26f62ada7c9e01dec61b2e768 + languageName: node + linkType: hard + +"safer-buffer@npm:>= 2.1.2 < 3, safer-buffer@npm:>= 2.1.2 < 3.0.0": + version: 2.1.2 + resolution: "safer-buffer@npm:2.1.2" + checksum: 10c0/7e3c8b2e88a1841c9671094bbaeebd94448111dd90a81a1f606f3f67708a6ec57763b3b47f06da09fc6054193e0e6709e77325415dc8422b04497a8070fa02d4 + languageName: node + linkType: hard + +"semver@npm:^6.0.0, semver@npm:^6.3.0, semver@npm:^6.3.1": + version: 6.3.1 + resolution: "semver@npm:6.3.1" + bin: + semver: bin/semver.js + checksum: 10c0/e3d79b609071caa78bcb6ce2ad81c7966a46a7431d9d58b8800cfa9cb6a63699b3899a0e4bcce36167a284578212d9ae6942b6929ba4aa5015c079a67751d42d + languageName: node + linkType: hard + +"semver@npm:^7.3.5, semver@npm:^7.3.7, semver@npm:^7.3.8, semver@npm:^7.5.3, semver@npm:^7.5.4": + version: 7.6.2 + resolution: "semver@npm:7.6.2" + bin: + semver: bin/semver.js + checksum: 10c0/97d3441e97ace8be4b1976433d1c32658f6afaff09f143e52c593bae7eef33de19e3e369c88bd985ce1042c6f441c80c6803078d1de2a9988080b66684cbb30c + languageName: node + linkType: hard + +"send@npm:0.18.0": + version: 0.18.0 + resolution: "send@npm:0.18.0" + dependencies: + debug: "npm:2.6.9" + depd: "npm:2.0.0" + destroy: "npm:1.2.0" + encodeurl: "npm:~1.0.2" + escape-html: "npm:~1.0.3" + etag: "npm:~1.8.1" + fresh: "npm:0.5.2" + http-errors: "npm:2.0.0" + mime: "npm:1.6.0" + ms: "npm:2.1.3" + on-finished: "npm:2.4.1" + range-parser: "npm:~1.2.1" + statuses: "npm:2.0.1" + checksum: 10c0/0eb134d6a51fc13bbcb976a1f4214ea1e33f242fae046efc311e80aff66c7a43603e26a79d9d06670283a13000e51be6e0a2cb80ff0942eaf9f1cd30b7ae736a + languageName: node + linkType: hard + +"send@npm:0.19.0": + version: 0.19.0 + resolution: "send@npm:0.19.0" + dependencies: + debug: "npm:2.6.9" + depd: "npm:2.0.0" + destroy: "npm:1.2.0" + encodeurl: "npm:~1.0.2" + escape-html: "npm:~1.0.3" + etag: "npm:~1.8.1" + fresh: "npm:0.5.2" + http-errors: "npm:2.0.0" + mime: "npm:1.6.0" + ms: "npm:2.1.3" + on-finished: "npm:2.4.1" + range-parser: "npm:~1.2.1" + statuses: "npm:2.0.1" + checksum: 10c0/ea3f8a67a8f0be3d6bf9080f0baed6d2c51d11d4f7b4470de96a5029c598a7011c497511ccc28968b70ef05508675cebff27da9151dd2ceadd60be4e6cf845e3 + languageName: node + linkType: hard + +"serve-static@npm:1.16.0": + version: 1.16.0 + resolution: "serve-static@npm:1.16.0" + dependencies: + encodeurl: "npm:~1.0.2" + escape-html: "npm:~1.0.3" + parseurl: "npm:~1.3.3" + send: "npm:0.18.0" + checksum: 10c0/d7a5beca08cc55f92998d8b87c111dd842d642404231c90c11f504f9650935da4599c13256747b0a988442a59851343271fe8e1946e03e92cd79c447b5f3ae01 + languageName: node + linkType: hard + +"set-blocking@npm:^2.0.0": + version: 2.0.0 + resolution: "set-blocking@npm:2.0.0" + checksum: 10c0/9f8c1b2d800800d0b589de1477c753492de5c1548d4ade52f57f1d1f5e04af5481554d75ce5e5c43d4004b80a3eb714398d6907027dc0534177b7539119f4454 + languageName: node + linkType: hard + +"set-function-length@npm:^1.2.1": + version: 1.2.2 + resolution: "set-function-length@npm:1.2.2" + dependencies: + define-data-property: "npm:^1.1.4" + es-errors: "npm:^1.3.0" + function-bind: "npm:^1.1.2" + get-intrinsic: "npm:^1.2.4" + gopd: "npm:^1.0.1" + has-property-descriptors: "npm:^1.0.2" + checksum: 10c0/82850e62f412a258b71e123d4ed3873fa9377c216809551192bb6769329340176f109c2eeae8c22a8d386c76739855f78e8716515c818bcaef384b51110f0f3c + languageName: node + linkType: hard + +"setprototypeof@npm:1.2.0": + version: 1.2.0 + resolution: "setprototypeof@npm:1.2.0" + checksum: 10c0/68733173026766fa0d9ecaeb07f0483f4c2dc70ca376b3b7c40b7cda909f94b0918f6c5ad5ce27a9160bdfb475efaa9d5e705a11d8eaae18f9835d20976028bc + languageName: node + linkType: hard + +"shebang-command@npm:^2.0.0": + version: 2.0.0 + resolution: "shebang-command@npm:2.0.0" + dependencies: + shebang-regex: "npm:^3.0.0" + checksum: 10c0/a41692e7d89a553ef21d324a5cceb5f686d1f3c040759c50aab69688634688c5c327f26f3ecf7001ebfd78c01f3c7c0a11a7c8bfd0a8bc9f6240d4f40b224e4e + languageName: node + linkType: hard + +"shebang-regex@npm:^3.0.0": + version: 3.0.0 + resolution: "shebang-regex@npm:3.0.0" + checksum: 10c0/1dbed0726dd0e1152a92696c76c7f06084eb32a90f0528d11acd764043aacf76994b2fb30aa1291a21bd019d6699164d048286309a278855ee7bec06cf6fb690 + languageName: node + linkType: hard + +"side-channel@npm:^1.0.4, side-channel@npm:^1.0.6": + version: 1.0.6 + resolution: "side-channel@npm:1.0.6" + dependencies: + call-bind: "npm:^1.0.7" + es-errors: "npm:^1.3.0" + get-intrinsic: "npm:^1.2.4" + object-inspect: "npm:^1.13.1" + checksum: 10c0/d2afd163dc733cc0a39aa6f7e39bf0c436293510dbccbff446733daeaf295857dbccf94297092ec8c53e2503acac30f0b78830876f0485991d62a90e9cad305f + languageName: node + linkType: hard + +"signal-exit@npm:^3.0.0, signal-exit@npm:^3.0.3, signal-exit@npm:^3.0.7": + version: 3.0.7 + resolution: "signal-exit@npm:3.0.7" + checksum: 10c0/25d272fa73e146048565e08f3309d5b942c1979a6f4a58a8c59d5fa299728e9c2fcd1a759ec870863b1fd38653670240cd420dad2ad9330c71f36608a6a1c912 + languageName: node + linkType: hard + +"signal-exit@npm:^4.0.1": + version: 4.1.0 + resolution: "signal-exit@npm:4.1.0" + checksum: 10c0/41602dce540e46d599edba9d9860193398d135f7ff72cab629db5171516cfae628d21e7bfccde1bbfdf11c48726bc2a6d1a8fb8701125852fbfda7cf19c6aa83 + languageName: node + linkType: hard + +"simple-concat@npm:^1.0.0": + version: 1.0.1 + resolution: "simple-concat@npm:1.0.1" + checksum: 10c0/62f7508e674414008910b5397c1811941d457dfa0db4fd5aa7fa0409eb02c3609608dfcd7508cace75b3a0bf67a2a77990711e32cd213d2c76f4fd12ee86d776 + languageName: node + linkType: hard + +"simple-get@npm:^4.0.0": + version: 4.0.1 + resolution: "simple-get@npm:4.0.1" + dependencies: + decompress-response: "npm:^6.0.0" + once: "npm:^1.3.1" + simple-concat: "npm:^1.0.0" + checksum: 10c0/b0649a581dbca741babb960423248899203165769747142033479a7dc5e77d7b0fced0253c731cd57cf21e31e4d77c9157c3069f4448d558ebc96cf9e1eebcf0 + languageName: node + linkType: hard + +"simple-swizzle@npm:^0.2.2": + version: 0.2.2 + resolution: "simple-swizzle@npm:0.2.2" + dependencies: + is-arrayish: "npm:^0.3.1" + checksum: 10c0/df5e4662a8c750bdba69af4e8263c5d96fe4cd0f9fe4bdfa3cbdeb45d2e869dff640beaaeb1ef0e99db4d8d2ec92f85508c269f50c972174851bc1ae5bd64308 + languageName: node + linkType: hard + +"sisteransi@npm:^1.0.5": + version: 1.0.5 + resolution: "sisteransi@npm:1.0.5" + checksum: 10c0/230ac975cca485b7f6fe2b96a711aa62a6a26ead3e6fb8ba17c5a00d61b8bed0d7adc21f5626b70d7c33c62ff4e63933017a6462942c719d1980bb0b1207ad46 + languageName: node + linkType: hard + +"slash@npm:^3.0.0": + version: 3.0.0 + resolution: "slash@npm:3.0.0" + checksum: 10c0/e18488c6a42bdfd4ac5be85b2ced3ccd0224773baae6ad42cfbb9ec74fc07f9fa8396bd35ee638084ead7a2a0818eb5e7151111544d4731ce843019dab4be47b + languageName: node + linkType: hard + +"slug@npm:^8.2.2": + version: 8.2.3 + resolution: "slug@npm:8.2.3" + bin: + slug: cli.js + checksum: 10c0/82499b57e5d2f8425e5fa366da86cf911b0d60c2d3143cadd89039fb6c5b8a3d340cd2fe26f4c85b62a6bdaa64327da5e7554ddbda176b58ee56ad932bfa3708 + languageName: node + linkType: hard + +"smart-buffer@npm:^4.2.0": + version: 4.2.0 + resolution: "smart-buffer@npm:4.2.0" + checksum: 10c0/a16775323e1404dd43fabafe7460be13a471e021637bc7889468eb45ce6a6b207261f454e4e530a19500cc962c4cc5348583520843b363f4193cee5c00e1e539 + languageName: node + linkType: hard + +"socks-proxy-agent@npm:^8.0.3": + version: 8.0.3 + resolution: "socks-proxy-agent@npm:8.0.3" + dependencies: + agent-base: "npm:^7.1.1" + debug: "npm:^4.3.4" + socks: "npm:^2.7.1" + checksum: 10c0/4950529affd8ccd6951575e21c1b7be8531b24d924aa4df3ee32df506af34b618c4e50d261f4cc603f1bfd8d426915b7d629966c8ce45b05fb5ad8c8b9a6459d + languageName: node + linkType: hard + +"socks@npm:^2.7.1": + version: 2.8.3 + resolution: "socks@npm:2.8.3" + dependencies: + ip-address: "npm:^9.0.5" + smart-buffer: "npm:^4.2.0" + checksum: 10c0/d54a52bf9325165770b674a67241143a3d8b4e4c8884560c4e0e078aace2a728dffc7f70150660f51b85797c4e1a3b82f9b7aa25e0a0ceae1a243365da5c51a7 + languageName: node + linkType: hard + +"source-map-support@npm:0.5.13": + version: 0.5.13 + resolution: "source-map-support@npm:0.5.13" + dependencies: + buffer-from: "npm:^1.0.0" + source-map: "npm:^0.6.0" + checksum: 10c0/137539f8c453fa0f496ea42049ab5da4569f96781f6ac8e5bfda26937be9494f4e8891f523c5f98f0e85f71b35d74127a00c46f83f6a4f54672b58d53202565e + languageName: node + linkType: hard + +"source-map@npm:^0.6.0, source-map@npm:^0.6.1": + version: 0.6.1 + resolution: "source-map@npm:0.6.1" + checksum: 10c0/ab55398007c5e5532957cb0beee2368529618ac0ab372d789806f5718123cc4367d57de3904b4e6a4170eb5a0b0f41373066d02ca0735a0c4d75c7d328d3e011 + languageName: node + linkType: hard + +"sprintf-js@npm:^1.1.3": + version: 1.1.3 + resolution: "sprintf-js@npm:1.1.3" + checksum: 10c0/09270dc4f30d479e666aee820eacd9e464215cdff53848b443964202bf4051490538e5dd1b42e1a65cf7296916ca17640aebf63dae9812749c7542ee5f288dec + languageName: node + linkType: hard + +"sprintf-js@npm:~1.0.2": + version: 1.0.3 + resolution: "sprintf-js@npm:1.0.3" + checksum: 10c0/ecadcfe4c771890140da5023d43e190b7566d9cf8b2d238600f31bec0fc653f328da4450eb04bd59a431771a8e9cc0e118f0aa3974b683a4981b4e07abc2a5bb + languageName: node + linkType: hard + +"ssri@npm:^10.0.0": + version: 10.0.6 + resolution: "ssri@npm:10.0.6" + dependencies: + minipass: "npm:^7.0.3" + checksum: 10c0/e5a1e23a4057a86a97971465418f22ea89bd439ac36ade88812dd920e4e61873e8abd6a9b72a03a67ef50faa00a2daf1ab745c5a15b46d03e0544a0296354227 + languageName: node + linkType: hard + +"stack-trace@npm:0.0.x": + version: 0.0.10 + resolution: "stack-trace@npm:0.0.10" + checksum: 10c0/9ff3dabfad4049b635a85456f927a075c9d0c210e3ea336412d18220b2a86cbb9b13ec46d6c37b70a302a4ea4d49e30e5d4944dd60ae784073f1cde778ac8f4b + languageName: node + linkType: hard + +"stack-utils@npm:^2.0.3": + version: 2.0.6 + resolution: "stack-utils@npm:2.0.6" + dependencies: + escape-string-regexp: "npm:^2.0.0" + checksum: 10c0/651c9f87667e077584bbe848acaecc6049bc71979f1e9a46c7b920cad4431c388df0f51b8ad7cfd6eed3db97a2878d0fc8b3122979439ea8bac29c61c95eec8a + languageName: node + linkType: hard + +"statuses@npm:2.0.1": + version: 2.0.1 + resolution: "statuses@npm:2.0.1" + checksum: 10c0/34378b207a1620a24804ce8b5d230fea0c279f00b18a7209646d5d47e419d1cc23e7cbf33a25a1e51ac38973dc2ac2e1e9c647a8e481ef365f77668d72becfd0 + languageName: node + linkType: hard + +"string-length@npm:^4.0.1": + version: 4.0.2 + resolution: "string-length@npm:4.0.2" + dependencies: + char-regex: "npm:^1.0.2" + strip-ansi: "npm:^6.0.0" + checksum: 10c0/1cd77409c3d7db7bc59406f6bcc9ef0783671dcbabb23597a1177c166906ef2ee7c8290f78cae73a8aec858768f189d2cb417797df5e15ec4eb5e16b3346340c + languageName: node + linkType: hard + +"string-width-cjs@npm:string-width@^4.2.0, string-width@npm:^1.0.2 || 2 || 3 || 4, string-width@npm:^4.1.0, string-width@npm:^4.2.0, string-width@npm:^4.2.3": + version: 4.2.3 + resolution: "string-width@npm:4.2.3" + dependencies: + emoji-regex: "npm:^8.0.0" + is-fullwidth-code-point: "npm:^3.0.0" + strip-ansi: "npm:^6.0.1" + checksum: 10c0/1e525e92e5eae0afd7454086eed9c818ee84374bb80328fc41217ae72ff5f065ef1c9d7f72da41de40c75fa8bb3dee63d92373fd492c84260a552c636392a47b + languageName: node + linkType: hard + +"string-width@npm:^5.0.1, string-width@npm:^5.1.2": + version: 5.1.2 + resolution: "string-width@npm:5.1.2" + dependencies: + eastasianwidth: "npm:^0.2.0" + emoji-regex: "npm:^9.2.2" + strip-ansi: "npm:^7.0.1" + checksum: 10c0/ab9c4264443d35b8b923cbdd513a089a60de339216d3b0ed3be3ba57d6880e1a192b70ae17225f764d7adbf5994e9bb8df253a944736c15a0240eff553c678ca + languageName: node + linkType: hard + +"string_decoder@npm:^1.1.1": + version: 1.3.0 + resolution: "string_decoder@npm:1.3.0" + dependencies: + safe-buffer: "npm:~5.2.0" + checksum: 10c0/810614ddb030e271cd591935dcd5956b2410dd079d64ff92a1844d6b7588bf992b3e1b69b0f4d34a3e06e0bd73046ac646b5264c1987b20d0601f81ef35d731d + languageName: node + linkType: hard + +"strip-ansi-cjs@npm:strip-ansi@^6.0.1, strip-ansi@npm:^6.0.0, strip-ansi@npm:^6.0.1": + version: 6.0.1 + resolution: "strip-ansi@npm:6.0.1" + dependencies: + ansi-regex: "npm:^5.0.1" + checksum: 10c0/1ae5f212a126fe5b167707f716942490e3933085a5ff6c008ab97ab2f272c8025d3aa218b7bd6ab25729ca20cc81cddb252102f8751e13482a5199e873680952 + languageName: node + linkType: hard + +"strip-ansi@npm:^7.0.1": + version: 7.1.0 + resolution: "strip-ansi@npm:7.1.0" + dependencies: + ansi-regex: "npm:^6.0.1" + checksum: 10c0/a198c3762e8832505328cbf9e8c8381de14a4fa50a4f9b2160138158ea88c0f5549fb50cb13c651c3088f47e63a108b34622ec18c0499b6c8c3a5ddf6b305ac4 + languageName: node + linkType: hard + +"strip-bom@npm:^4.0.0": + version: 4.0.0 + resolution: "strip-bom@npm:4.0.0" + checksum: 10c0/26abad1172d6bc48985ab9a5f96c21e440f6e7e476686de49be813b5a59b3566dccb5c525b831ec54fe348283b47f3ffb8e080bc3f965fde12e84df23f6bb7ef + languageName: node + linkType: hard + +"strip-final-newline@npm:^2.0.0": + version: 2.0.0 + resolution: "strip-final-newline@npm:2.0.0" + checksum: 10c0/bddf8ccd47acd85c0e09ad7375409d81653f645fda13227a9d459642277c253d877b68f2e5e4d819fe75733b0e626bac7e954c04f3236f6d196f79c94fa4a96f + languageName: node + linkType: hard + +"strip-json-comments@npm:^3.1.1": + version: 3.1.1 + resolution: "strip-json-comments@npm:3.1.1" + checksum: 10c0/9681a6257b925a7fa0f285851c0e613cc934a50661fa7bb41ca9cbbff89686bb4a0ee366e6ecedc4daafd01e83eee0720111ab294366fe7c185e935475ebcecd + languageName: node + linkType: hard + +"strip-json-comments@npm:~2.0.1": + version: 2.0.1 + resolution: "strip-json-comments@npm:2.0.1" + checksum: 10c0/b509231cbdee45064ff4f9fd73609e2bcc4e84a4d508e9dd0f31f70356473fde18abfb5838c17d56fb236f5a06b102ef115438de0600b749e818a35fbbc48c43 + languageName: node + linkType: hard + +"superagent@npm:^8.1.2": + version: 8.1.2 + resolution: "superagent@npm:8.1.2" + dependencies: + component-emitter: "npm:^1.3.0" + cookiejar: "npm:^2.1.4" + debug: "npm:^4.3.4" + fast-safe-stringify: "npm:^2.1.1" + form-data: "npm:^4.0.0" + formidable: "npm:^2.1.2" + methods: "npm:^1.1.2" + mime: "npm:2.6.0" + qs: "npm:^6.11.0" + semver: "npm:^7.3.8" + checksum: 10c0/016416fc9c3d3a04fb648bc0efb3d3d5c9d96da00de47e4a625d9976d28c6c37ab0a7f185f2c3ec6d653ee8bb522f70fba0c1072aea7774341a6c0269a9fa77f + languageName: node + linkType: hard + +"supertest@npm:^6.3.1": + version: 6.3.4 + resolution: "supertest@npm:6.3.4" + dependencies: + methods: "npm:^1.1.2" + superagent: "npm:^8.1.2" + checksum: 10c0/f8c0b6c73b5e87da31feee6ccb36e7af766a438513cad89d6907f22c97edd83b1e765b4c8de955d5f7af4bca5fd0aaf9149ff48e21567dd290b326a8633af2a7 + languageName: node + linkType: hard + +"supports-color@npm:^5.3.0": + version: 5.5.0 + resolution: "supports-color@npm:5.5.0" + dependencies: + has-flag: "npm:^3.0.0" + checksum: 10c0/6ae5ff319bfbb021f8a86da8ea1f8db52fac8bd4d499492e30ec17095b58af11f0c55f8577390a749b1c4dde691b6a0315dab78f5f54c9b3d83f8fb5905c1c05 + languageName: node + linkType: hard + +"supports-color@npm:^7.1.0": + version: 7.2.0 + resolution: "supports-color@npm:7.2.0" + dependencies: + has-flag: "npm:^4.0.0" + checksum: 10c0/afb4c88521b8b136b5f5f95160c98dee7243dc79d5432db7efc27efb219385bbc7d9427398e43dd6cc730a0f87d5085ce1652af7efbe391327bc0a7d0f7fc124 + languageName: node + linkType: hard + +"supports-color@npm:^8.0.0": + version: 8.1.1 + resolution: "supports-color@npm:8.1.1" + dependencies: + has-flag: "npm:^4.0.0" + checksum: 10c0/ea1d3c275dd604c974670f63943ed9bd83623edc102430c05adb8efc56ba492746b6e95386e7831b872ec3807fd89dd8eb43f735195f37b5ec343e4234cc7e89 + languageName: node + linkType: hard + +"supports-preserve-symlinks-flag@npm:^1.0.0": + version: 1.0.0 + resolution: "supports-preserve-symlinks-flag@npm:1.0.0" + checksum: 10c0/6c4032340701a9950865f7ae8ef38578d8d7053f5e10518076e6554a9381fa91bd9c6850193695c141f32b21f979c985db07265a758867bac95de05f7d8aeb39 + languageName: node + linkType: hard + +"tar-fs@npm:^2.0.0": + version: 2.1.1 + resolution: "tar-fs@npm:2.1.1" + dependencies: + chownr: "npm:^1.1.1" + mkdirp-classic: "npm:^0.5.2" + pump: "npm:^3.0.0" + tar-stream: "npm:^2.1.4" + checksum: 10c0/871d26a934bfb7beeae4c4d8a09689f530b565f79bd0cf489823ff0efa3705da01278160da10bb006d1a793fa0425cf316cec029b32a9159eacbeaff4965fb6d + languageName: node + linkType: hard + +"tar-stream@npm:^2.1.4": + version: 2.2.0 + resolution: "tar-stream@npm:2.2.0" + dependencies: + bl: "npm:^4.0.3" + end-of-stream: "npm:^1.4.1" + fs-constants: "npm:^1.0.0" + inherits: "npm:^2.0.3" + readable-stream: "npm:^3.1.1" + checksum: 10c0/2f4c910b3ee7196502e1ff015a7ba321ec6ea837667220d7bcb8d0852d51cb04b87f7ae471008a6fb8f5b1a1b5078f62f3a82d30c706f20ada1238ac797e7692 + languageName: node + linkType: hard + +"tar@npm:^6.1.11, tar@npm:^6.1.2": + version: 6.2.1 + resolution: "tar@npm:6.2.1" + dependencies: + chownr: "npm:^2.0.0" + fs-minipass: "npm:^2.0.0" + minipass: "npm:^5.0.0" + minizlib: "npm:^2.1.1" + mkdirp: "npm:^1.0.3" + yallist: "npm:^4.0.0" + checksum: 10c0/a5eca3eb50bc11552d453488344e6507156b9193efd7635e98e867fab275d527af53d8866e2370cd09dfe74378a18111622ace35af6a608e5223a7d27fe99537 + languageName: node + linkType: hard + +"test-exclude@npm:^6.0.0": + version: 6.0.0 + resolution: "test-exclude@npm:6.0.0" + dependencies: + "@istanbuljs/schema": "npm:^0.1.2" + glob: "npm:^7.1.4" + minimatch: "npm:^3.0.4" + checksum: 10c0/019d33d81adff3f9f1bfcff18125fb2d3c65564f437d9be539270ee74b994986abb8260c7c2ce90e8f30162178b09dbbce33c6389273afac4f36069c48521f57 + languageName: node + linkType: hard + +"text-hex@npm:1.0.x": + version: 1.0.0 + resolution: "text-hex@npm:1.0.0" + checksum: 10c0/57d8d320d92c79d7c03ffb8339b825bb9637c2cbccf14304309f51d8950015c44464b6fd1b6820a3d4821241c68825634f09f5a2d9d501e84f7c6fd14376860d + languageName: node + linkType: hard + +"text-table@npm:^0.2.0": + version: 0.2.0 + resolution: "text-table@npm:0.2.0" + checksum: 10c0/02805740c12851ea5982686810702e2f14369a5f4c5c40a836821e3eefc65ffeec3131ba324692a37608294b0fd8c1e55a2dd571ffed4909822787668ddbee5c + languageName: node + linkType: hard + +"tmpl@npm:1.0.5": + version: 1.0.5 + resolution: "tmpl@npm:1.0.5" + checksum: 10c0/f935537799c2d1922cb5d6d3805f594388f75338fe7a4a9dac41504dd539704ca4db45b883b52e7b0aa5b2fd5ddadb1452bf95cd23a69da2f793a843f9451cc9 + languageName: node + linkType: hard + +"to-fast-properties@npm:^2.0.0": + version: 2.0.0 + resolution: "to-fast-properties@npm:2.0.0" + checksum: 10c0/b214d21dbfb4bce3452b6244b336806ffea9c05297148d32ebb428d5c43ce7545bdfc65a1ceb58c9ef4376a65c0cb2854d645f33961658b3e3b4f84910ddcdd7 + languageName: node + linkType: hard + +"to-regex-range@npm:^5.0.1": + version: 5.0.1 + resolution: "to-regex-range@npm:5.0.1" + dependencies: + is-number: "npm:^7.0.0" + checksum: 10c0/487988b0a19c654ff3e1961b87f471702e708fa8a8dd02a298ef16da7206692e8552a0250e8b3e8759270f62e9d8314616f6da274734d3b558b1fc7b7724e892 + languageName: node + linkType: hard + +"toidentifier@npm:1.0.1": + version: 1.0.1 + resolution: "toidentifier@npm:1.0.1" + checksum: 10c0/93937279934bd66cc3270016dd8d0afec14fb7c94a05c72dc57321f8bd1fa97e5bea6d1f7c89e728d077ca31ea125b78320a616a6c6cd0e6b9cb94cb864381c1 + languageName: node + linkType: hard + +"tr46@npm:~0.0.3": + version: 0.0.3 + resolution: "tr46@npm:0.0.3" + checksum: 10c0/047cb209a6b60c742f05c9d3ace8fa510bff609995c129a37ace03476a9b12db4dbf975e74600830ef0796e18882b2381fb5fb1f6b4f96b832c374de3ab91a11 + languageName: node + linkType: hard + +"triple-beam@npm:^1.3.0": + version: 1.4.1 + resolution: "triple-beam@npm:1.4.1" + checksum: 10c0/4bf1db71e14fe3ff1c3adbe3c302f1fdb553b74d7591a37323a7badb32dc8e9c290738996cbb64f8b10dc5a3833645b5d8c26221aaaaa12e50d1251c9aba2fea + languageName: node + linkType: hard + +"tslib@npm:^1.8.1": + version: 1.14.1 + resolution: "tslib@npm:1.14.1" + checksum: 10c0/69ae09c49eea644bc5ebe1bca4fa4cc2c82b7b3e02f43b84bd891504edf66dbc6b2ec0eef31a957042de2269139e4acff911e6d186a258fb14069cd7f6febce2 + languageName: node + linkType: hard + +"tsutils@npm:^3.21.0": + version: 3.21.0 + resolution: "tsutils@npm:3.21.0" + dependencies: + tslib: "npm:^1.8.1" + peerDependencies: + typescript: ">=2.8.0 || >= 3.2.0-dev || >= 3.3.0-dev || >= 3.4.0-dev || >= 3.5.0-dev || >= 3.6.0-dev || >= 3.6.0-beta || >= 3.7.0-dev || >= 3.7.0-beta" + checksum: 10c0/02f19e458ec78ead8fffbf711f834ad8ecd2cc6ade4ec0320790713dccc0a412b99e7fd907c4cda2a1dc602c75db6f12e0108e87a5afad4b2f9e90a24cabd5a2 + languageName: node + linkType: hard + +"tunnel-agent@npm:^0.6.0": + version: 0.6.0 + resolution: "tunnel-agent@npm:0.6.0" + dependencies: + safe-buffer: "npm:^5.0.1" + checksum: 10c0/4c7a1b813e7beae66fdbf567a65ec6d46313643753d0beefb3c7973d66fcec3a1e7f39759f0a0b4465883499c6dc8b0750ab8b287399af2e583823e40410a17a + languageName: node + linkType: hard + +"type-check@npm:^0.4.0, type-check@npm:~0.4.0": + version: 0.4.0 + resolution: "type-check@npm:0.4.0" + dependencies: + prelude-ls: "npm:^1.2.1" + checksum: 10c0/7b3fd0ed43891e2080bf0c5c504b418fbb3e5c7b9708d3d015037ba2e6323a28152ec163bcb65212741fa5d2022e3075ac3c76440dbd344c9035f818e8ecee58 + languageName: node + linkType: hard + +"type-detect@npm:4.0.8": + version: 4.0.8 + resolution: "type-detect@npm:4.0.8" + checksum: 10c0/8fb9a51d3f365a7de84ab7f73b653534b61b622aa6800aecdb0f1095a4a646d3f5eb295322127b6573db7982afcd40ab492d038cf825a42093a58b1e1353e0bd + languageName: node + linkType: hard + +"type-fest@npm:^0.20.2": + version: 0.20.2 + resolution: "type-fest@npm:0.20.2" + checksum: 10c0/dea9df45ea1f0aaa4e2d3bed3f9a0bfe9e5b2592bddb92eb1bf06e50bcf98dbb78189668cd8bc31a0511d3fc25539b4cd5c704497e53e93e2d40ca764b10bfc3 + languageName: node + linkType: hard + +"type-fest@npm:^0.21.3": + version: 0.21.3 + resolution: "type-fest@npm:0.21.3" + checksum: 10c0/902bd57bfa30d51d4779b641c2bc403cdf1371fb9c91d3c058b0133694fcfdb817aef07a47f40faf79039eecbaa39ee9d3c532deff244f3a19ce68cea71a61e8 + languageName: node + linkType: hard + +"type-is@npm:~1.6.18": + version: 1.6.18 + resolution: "type-is@npm:1.6.18" + dependencies: + media-typer: "npm:0.3.0" + mime-types: "npm:~2.1.24" + checksum: 10c0/a23daeb538591b7efbd61ecf06b6feb2501b683ffdc9a19c74ef5baba362b4347e42f1b4ed81f5882a8c96a3bfff7f93ce3ffaf0cbbc879b532b04c97a55db9d + languageName: node + linkType: hard + +"typescript@npm:^4.9.5": + version: 4.9.5 + resolution: "typescript@npm:4.9.5" + bin: + tsc: bin/tsc + tsserver: bin/tsserver + checksum: 10c0/5f6cad2e728a8a063521328e612d7876e12f0d8a8390d3b3aaa452a6a65e24e9ac8ea22beb72a924fd96ea0a49ea63bb4e251fb922b12eedfb7f7a26475e5c56 + languageName: node + linkType: hard + +"typescript@patch:typescript@npm%3A^4.9.5#optional!builtin<compat/typescript>": + version: 4.9.5 + resolution: "typescript@patch:typescript@npm%3A4.9.5#optional!builtin<compat/typescript>::version=4.9.5&hash=289587" + bin: + tsc: bin/tsc + tsserver: bin/tsserver + checksum: 10c0/e3333f887c6829dfe0ab6c1dbe0dd1e3e2aeb56c66460cb85c5440c566f900c833d370ca34eb47558c0c69e78ced4bfe09b8f4f98b6de7afed9b84b8d1dd06a1 + languageName: node + linkType: hard + +"undici-types@npm:~5.26.4": + version: 5.26.5 + resolution: "undici-types@npm:5.26.5" + checksum: 10c0/bb673d7876c2d411b6eb6c560e0c571eef4a01c1c19925175d16e3a30c4c428181fb8d7ae802a261f283e4166a0ac435e2f505743aa9e45d893f9a3df017b501 + languageName: node + linkType: hard + +"unique-filename@npm:^3.0.0": + version: 3.0.0 + resolution: "unique-filename@npm:3.0.0" + dependencies: + unique-slug: "npm:^4.0.0" + checksum: 10c0/6363e40b2fa758eb5ec5e21b3c7fb83e5da8dcfbd866cc0c199d5534c42f03b9ea9ab069769cc388e1d7ab93b4eeef28ef506ab5f18d910ef29617715101884f + languageName: node + linkType: hard + +"unique-slug@npm:^4.0.0": + version: 4.0.0 + resolution: "unique-slug@npm:4.0.0" + dependencies: + imurmurhash: "npm:^0.1.4" + checksum: 10c0/cb811d9d54eb5821b81b18205750be84cb015c20a4a44280794e915f5a0a70223ce39066781a354e872df3572e8155c228f43ff0cce94c7cbf4da2cc7cbdd635 + languageName: node + linkType: hard + +"unpipe@npm:1.0.0, unpipe@npm:~1.0.0": + version: 1.0.0 + resolution: "unpipe@npm:1.0.0" + checksum: 10c0/193400255bd48968e5c5383730344fbb4fa114cdedfab26e329e50dd2d81b134244bb8a72c6ac1b10ab0281a58b363d06405632c9d49ca9dfd5e90cbd7d0f32c + languageName: node + linkType: hard + +"update-browserslist-db@npm:^1.0.16": + version: 1.0.16 + resolution: "update-browserslist-db@npm:1.0.16" + dependencies: + escalade: "npm:^3.1.2" + picocolors: "npm:^1.0.1" + peerDependencies: + browserslist: ">= 4.21.0" + bin: + update-browserslist-db: cli.js + checksum: 10c0/5995399fc202adbb51567e4810e146cdf7af630a92cc969365a099150cb00597e425cc14987ca7080b09a4d0cfd2a3de53fbe72eebff171aed7f9bb81f9bf405 + languageName: node + linkType: hard + +"uri-js@npm:^4.2.2": + version: 4.4.1 + resolution: "uri-js@npm:4.4.1" + dependencies: + punycode: "npm:^2.1.0" + checksum: 10c0/4ef57b45aa820d7ac6496e9208559986c665e49447cb072744c13b66925a362d96dd5a46c4530a6b8e203e5db5fe849369444440cb22ecfc26c679359e5dfa3c + languageName: node + linkType: hard + +"util-deprecate@npm:^1.0.1": + version: 1.0.2 + resolution: "util-deprecate@npm:1.0.2" + checksum: 10c0/41a5bdd214df2f6c3ecf8622745e4a366c4adced864bc3c833739791aeeeb1838119af7daed4ba36428114b5c67dcda034a79c882e97e43c03e66a4dd7389942 + languageName: node + linkType: hard + +"utils-merge@npm:1.0.1": + version: 1.0.1 + resolution: "utils-merge@npm:1.0.1" + checksum: 10c0/02ba649de1b7ca8854bfe20a82f1dfbdda3fb57a22ab4a8972a63a34553cf7aa51bc9081cf7e001b035b88186d23689d69e71b510e610a09a4c66f68aa95b672 + languageName: node + linkType: hard + +"uuid@npm:^9.0.0": + version: 9.0.1 + resolution: "uuid@npm:9.0.1" + bin: + uuid: dist/bin/uuid + checksum: 10c0/1607dd32ac7fc22f2d8f77051e6a64845c9bce5cd3dd8aa0070c074ec73e666a1f63c7b4e0f4bf2bc8b9d59dc85a15e17807446d9d2b17c8485fbc2147b27f9b + languageName: node + linkType: hard + +"v8-to-istanbul@npm:^9.0.1": + version: 9.2.0 + resolution: "v8-to-istanbul@npm:9.2.0" + dependencies: + "@jridgewell/trace-mapping": "npm:^0.3.12" + "@types/istanbul-lib-coverage": "npm:^2.0.1" + convert-source-map: "npm:^2.0.0" + checksum: 10c0/e691ba4dd0dea4a884e52c37dbda30cce6f9eeafe9b26721e449429c6bb0f4b6d1e33fabe7711d0f67f7a34c3bfd56c873f7375bba0b1534e6a2843ce99550e5 + languageName: node + linkType: hard + +"vary@npm:^1, vary@npm:~1.1.2": + version: 1.1.2 + resolution: "vary@npm:1.1.2" + checksum: 10c0/f15d588d79f3675135ba783c91a4083dcd290a2a5be9fcb6514220a1634e23df116847b1cc51f66bfb0644cf9353b2abb7815ae499bab06e46dd33c1a6bf1f4f + languageName: node + linkType: hard + +"walker@npm:^1.0.8": + version: 1.0.8 + resolution: "walker@npm:1.0.8" + dependencies: + makeerror: "npm:1.0.12" + checksum: 10c0/a17e037bccd3ca8a25a80cb850903facdfed0de4864bd8728f1782370715d679fa72e0a0f5da7c1c1379365159901e5935f35be531229da53bbfc0efdabdb48e + languageName: node + linkType: hard + +"webidl-conversions@npm:^3.0.0": + version: 3.0.1 + resolution: "webidl-conversions@npm:3.0.1" + checksum: 10c0/5612d5f3e54760a797052eb4927f0ddc01383550f542ccd33d5238cfd65aeed392a45ad38364970d0a0f4fea32e1f4d231b3d8dac4a3bdd385e5cf802ae097db + languageName: node + linkType: hard + +"whatwg-url@npm:^5.0.0": + version: 5.0.0 + resolution: "whatwg-url@npm:5.0.0" + dependencies: + tr46: "npm:~0.0.3" + webidl-conversions: "npm:^3.0.0" + checksum: 10c0/1588bed84d10b72d5eec1d0faa0722ba1962f1821e7539c535558fb5398d223b0c50d8acab950b8c488b4ba69043fd833cc2697056b167d8ad46fac3995a55d5 + languageName: node + linkType: hard + +"which@npm:^2.0.1": + version: 2.0.2 + resolution: "which@npm:2.0.2" + dependencies: + isexe: "npm:^2.0.0" + bin: + node-which: ./bin/node-which + checksum: 10c0/66522872a768b60c2a65a57e8ad184e5372f5b6a9ca6d5f033d4b0dc98aff63995655a7503b9c0a2598936f532120e81dd8cc155e2e92ed662a2b9377cc4374f + languageName: node + linkType: hard + +"which@npm:^4.0.0": + version: 4.0.0 + resolution: "which@npm:4.0.0" + dependencies: + isexe: "npm:^3.1.1" + bin: + node-which: bin/which.js + checksum: 10c0/449fa5c44ed120ccecfe18c433296a4978a7583bf2391c50abce13f76878d2476defde04d0f79db8165bdf432853c1f8389d0485ca6e8ebce3bbcded513d5e6a + languageName: node + linkType: hard + +"wide-align@npm:^1.1.2": + version: 1.1.5 + resolution: "wide-align@npm:1.1.5" + dependencies: + string-width: "npm:^1.0.2 || 2 || 3 || 4" + checksum: 10c0/1d9c2a3e36dfb09832f38e2e699c367ef190f96b82c71f809bc0822c306f5379df87bab47bed27ea99106d86447e50eb972d3c516c2f95782807a9d082fbea95 + languageName: node + linkType: hard + +"winston-transport@npm:^4.7.0": + version: 4.7.1 + resolution: "winston-transport@npm:4.7.1" + dependencies: + logform: "npm:^2.6.1" + readable-stream: "npm:^3.6.2" + triple-beam: "npm:^1.3.0" + checksum: 10c0/99b7b55cc2ef7f38988ab1717e7fd946c81b856b42a9530aef8ee725490ef2f2811f9cb06d63aa2f76a85fe99ae15b3bef10a54afde3be8b5059ce325e78481f + languageName: node + linkType: hard + +"winston@npm:^3.14.2": + version: 3.14.2 + resolution: "winston@npm:3.14.2" + dependencies: + "@colors/colors": "npm:^1.6.0" + "@dabh/diagnostics": "npm:^2.0.2" + async: "npm:^3.2.3" + is-stream: "npm:^2.0.0" + logform: "npm:^2.6.0" + one-time: "npm:^1.0.0" + readable-stream: "npm:^3.4.0" + safe-stable-stringify: "npm:^2.3.1" + stack-trace: "npm:0.0.x" + triple-beam: "npm:^1.3.0" + winston-transport: "npm:^4.7.0" + checksum: 10c0/3f8fe505ea18310982e60452f335dd2b22fdbc9b25839b6ad882971b2416d5adc94a1f1a46e24cb37d967ad01dfe5499adaf5e53575626b5ebb2a25ff30f4e1d + languageName: node + linkType: hard + +"word-wrap@npm:^1.2.5": + version: 1.2.5 + resolution: "word-wrap@npm:1.2.5" + checksum: 10c0/e0e4a1ca27599c92a6ca4c32260e8a92e8a44f4ef6ef93f803f8ed823f486e0889fc0b93be4db59c8d51b3064951d25e43d434e95dc8c960cc3a63d65d00ba20 + languageName: node + linkType: hard + +"wrap-ansi-cjs@npm:wrap-ansi@^7.0.0, wrap-ansi@npm:^7.0.0": + version: 7.0.0 + resolution: "wrap-ansi@npm:7.0.0" + dependencies: + ansi-styles: "npm:^4.0.0" + string-width: "npm:^4.1.0" + strip-ansi: "npm:^6.0.0" + checksum: 10c0/d15fc12c11e4cbc4044a552129ebc75ee3f57aa9c1958373a4db0292d72282f54373b536103987a4a7594db1ef6a4f10acf92978f79b98c49306a4b58c77d4da + languageName: node + linkType: hard + +"wrap-ansi@npm:^8.1.0": + version: 8.1.0 + resolution: "wrap-ansi@npm:8.1.0" + dependencies: + ansi-styles: "npm:^6.1.0" + string-width: "npm:^5.0.1" + strip-ansi: "npm:^7.0.1" + checksum: 10c0/138ff58a41d2f877eae87e3282c0630fc2789012fc1af4d6bd626eeb9a2f9a65ca92005e6e69a75c7b85a68479fe7443c7dbe1eb8fbaa681a4491364b7c55c60 + languageName: node + linkType: hard + +"wrappy@npm:1": + version: 1.0.2 + resolution: "wrappy@npm:1.0.2" + checksum: 10c0/56fece1a4018c6a6c8e28fbc88c87e0fbf4ea8fd64fc6c63b18f4acc4bd13e0ad2515189786dd2c30d3eec9663d70f4ecf699330002f8ccb547e4a18231fc9f0 + languageName: node + linkType: hard + +"write-file-atomic@npm:^4.0.2": + version: 4.0.2 + resolution: "write-file-atomic@npm:4.0.2" + dependencies: + imurmurhash: "npm:^0.1.4" + signal-exit: "npm:^3.0.7" + checksum: 10c0/a2c282c95ef5d8e1c27b335ae897b5eca00e85590d92a3fd69a437919b7b93ff36a69ea04145da55829d2164e724bc62202cdb5f4b208b425aba0807889375c7 + languageName: node + linkType: hard + +"y18n@npm:^5.0.5": + version: 5.0.8 + resolution: "y18n@npm:5.0.8" + checksum: 10c0/4df2842c36e468590c3691c894bc9cdbac41f520566e76e24f59401ba7d8b4811eb1e34524d57e54bc6d864bcb66baab7ffd9ca42bf1eda596618f9162b91249 + languageName: node + linkType: hard + +"yallist@npm:^3.0.2": + version: 3.1.1 + resolution: "yallist@npm:3.1.1" + checksum: 10c0/c66a5c46bc89af1625476f7f0f2ec3653c1a1791d2f9407cfb4c2ba812a1e1c9941416d71ba9719876530e3340a99925f697142989371b72d93b9ee628afd8c1 + languageName: node + linkType: hard + +"yallist@npm:^4.0.0": + version: 4.0.0 + resolution: "yallist@npm:4.0.0" + checksum: 10c0/2286b5e8dbfe22204ab66e2ef5cc9bbb1e55dfc873bbe0d568aa943eb255d131890dfd5bf243637273d31119b870f49c18fcde2c6ffbb7a7a092b870dc90625a + languageName: node + linkType: hard + +"yargs-parser@npm:^21.1.1": + version: 21.1.1 + resolution: "yargs-parser@npm:21.1.1" + checksum: 10c0/f84b5e48169479d2f402239c59f084cfd1c3acc197a05c59b98bab067452e6b3ea46d4dd8ba2985ba7b3d32a343d77df0debd6b343e5dae3da2aab2cdf5886b2 + languageName: node + linkType: hard + +"yargs@npm:^17.3.1": + version: 17.7.2 + resolution: "yargs@npm:17.7.2" + dependencies: + cliui: "npm:^8.0.1" + escalade: "npm:^3.1.1" + get-caller-file: "npm:^2.0.5" + require-directory: "npm:^2.1.1" + string-width: "npm:^4.2.3" + y18n: "npm:^5.0.5" + yargs-parser: "npm:^21.1.1" + checksum: 10c0/ccd7e723e61ad5965fffbb791366db689572b80cca80e0f96aad968dfff4156cd7cd1ad18607afe1046d8241e6fb2d6c08bf7fa7bfb5eaec818735d8feac8f05 + languageName: node + linkType: hard + +"yocto-queue@npm:^0.1.0": + version: 0.1.0 + resolution: "yocto-queue@npm:0.1.0" + checksum: 10c0/dceb44c28578b31641e13695d200d34ec4ab3966a5729814d5445b194933c096b7ced71494ce53a0e8820685d1d010df8b2422e5bf2cdea7e469d97ffbea306f + languageName: node + linkType: hard diff --git a/yarn.lock b/yarn.lock index d6d3297eacd..0b918b4da3d 100644 --- a/yarn.lock +++ b/yarn.lock @@ -38,7 +38,7 @@ __metadata: languageName: unknown linkType: soft -"@actual-app/crdt@npm:*, @actual-app/crdt@workspace:^, @actual-app/crdt@workspace:packages/crdt": +"@actual-app/crdt@npm:*, @actual-app/crdt@npm:2.1.0, @actual-app/crdt@workspace:^, @actual-app/crdt@workspace:packages/crdt": version: 0.0.0-use.local resolution: "@actual-app/crdt@workspace:packages/crdt" dependencies: @@ -55,7 +55,7 @@ __metadata: languageName: unknown linkType: soft -"@actual-app/web@npm:*, @actual-app/web@workspace:packages/desktop-client": +"@actual-app/web@npm:25.1.0, @actual-app/web@workspace:packages/desktop-client": version: 0.0.0-use.local resolution: "@actual-app/web@workspace:packages/desktop-client" dependencies: @@ -7742,8 +7742,8 @@ __metadata: version: 0.0.0-use.local resolution: "actual-sync@workspace:packages/sync-server" dependencies: - "@actual-app/crdt": "npm:*" - "@actual-app/web": "npm:*" + "@actual-app/crdt": "npm:2.1.0" + "@actual-app/web": "npm:25.1.0" "@babel/preset-typescript": "npm:^7.20.2" "@types/bcrypt": "npm:^5.0.2" "@types/better-sqlite3": "npm:^7.6.12" From 39e465d801f03024a012c39714e3f3b98bdb6cc7 Mon Sep 17 00:00:00 2001 From: Mike Clark <mclarkgb@gmail.com> Date: Sat, 11 Jan 2025 14:05:56 +0000 Subject: [PATCH 55/55] fix bug --- packages/desktop-electron/index.ts | 67 ++++++++++++++----- .../src/app-gocardless/bank-factory.js | 4 +- 2 files changed, 53 insertions(+), 18 deletions(-) diff --git a/packages/desktop-electron/index.ts b/packages/desktop-electron/index.ts index c675d6f1e69..901d0b9341d 100644 --- a/packages/desktop-electron/index.ts +++ b/packages/desktop-electron/index.ts @@ -1,3 +1,4 @@ +/* eslint-disable rulesdir/typography */ import fs from 'fs'; import { createServer, Server } from 'http'; import path from 'path'; @@ -60,10 +61,35 @@ let serverProcess: UtilityProcess | null; let actualServerProcess: UtilityProcess | null; let oAuthServer: ReturnType<typeof createServer> | null; +let queuedClientWinLogs = []; + +const logMessage = (loglevel: 'info' | 'error', message: string) => { + // Electron main process logs + switch (loglevel) { + case 'info': + console.info(message); + break; + case 'error': + console.error(message); + break; + } + + // App devtools logs + if (!clientWin) { + // queue up the logs until the client window is ready + queuedClientWinLogs.push( + `console.${loglevel}('Actual Sync Server Log:', ${JSON.stringify(message)})`, + ); + } else { + clientWin.webContents.executeJavaScript( + `console.${loglevel}('Actual Sync Server Log:', ${JSON.stringify(message)})`, + ); + } +}; const createOAuthServer = async () => { const port = 3010; - console.log(`OAuth server running on port: ${port}`); + logMessage('info', `OAuth server running on port: ${port}`); if (oAuthServer) { return { url: `http://localhost:${port}`, server: oAuthServer }; @@ -114,7 +140,7 @@ async function loadGlobalPrefs() { ), ); } catch (e) { - console.info('Could not load global state - using defaults'); // This could be the first time running the app - no global-store.json + logMessage('info', 'Could not load global state - using defaults'); state = {}; } @@ -174,7 +200,7 @@ async function createBackgroundProcess() { } break; default: - console.log('Unknown server message: ' + msg.type); + logMessage('info', 'Unknown server message: ' + msg.type); } }); } @@ -280,19 +306,17 @@ function startSyncServer() { const chunkValue = JSON.stringify(chunk.toString('utf8')); if (chunkValue.includes('Listening on')) { // can we send a signal from the server instead of doing this? - console.info('Actual Sync Server has started!'); + logMessage('info', 'Actual Sync Server has started!'); syncServerStarted = true; resolve(); } - clientWin?.webContents.executeJavaScript(` - console.info('Actual Sync Server Log:', ${chunkValue})`); + logMessage('info', chunkValue); }); actualServerProcess.stderr?.on('data', (chunk: Buffer) => { // Send the Server console.error messages out to the main browser window - clientWin?.webContents.executeJavaScript(` - console.error('Actual Sync Server Log:', ${JSON.stringify(chunk.toString('utf8'))})`); + logMessage('error', JSON.stringify(chunk.toString('utf8'))); }); }); @@ -300,7 +324,7 @@ function startSyncServer() { setTimeout(() => { if (!syncServerStarted) { const errorMessage = `Sync server failed to start within ${SYNC_SERVER_WAIT_TIMEOUT / 1000} seconds. Something is wrong. Please raise a github issue.`; - console.error(errorMessage); + logMessage('error', errorMessage); reject(new Error(errorMessage)); } }, SYNC_SERVER_WAIT_TIMEOUT); @@ -315,7 +339,7 @@ async function exposeSyncServer(ngrokConfig: GlobalPrefs['ngrokConfig']) { ngrokConfig?.authToken && ngrokConfig?.domain && ngrokConfig?.port; if (!hasRequiredConfig) { - console.error('Cannot expose sync server: missing ngrok settings'); + logMessage('error', 'Cannot expose sync server: missing ngrok settings'); return { error: 'Missing ngrok settings' }; } @@ -329,10 +353,10 @@ async function exposeSyncServer(ngrokConfig: GlobalPrefs['ngrokConfig']) { // key: fs.readFileSync("key.pem", "utf8"), }); - console.info(`Exposing actual server on url: ${listener.url()}`); + logMessage('info', `Exposing actual server on url: ${listener.url()}`); return { url: listener.url() }; } catch (error) { - console.error('Unable to run ngrok', error); + logMessage('error', `Unable to run ngrok: ${error}`); return { error: `Unable to run ngrok. ${error}` }; } } @@ -381,8 +405,9 @@ async function createWindow() { }); win.on('unresponsive', () => { - console.log( - 'browser window went unresponsive (maybe because of a modal though)', + logMessage( + 'info', + 'browser window went unresponsive (maybe because of a modal)', ); }); @@ -424,6 +449,13 @@ async function createWindow() { } clientWin = win; + + // Execute any queued logs + queuedClientWinLogs.map((log: string) => + clientWin.webContents.executeJavaScript(log), + ); + + queuedClientWinLogs = []; } function isExternalUrl(url: string) { @@ -534,7 +566,7 @@ app.on('ready', async () => { // This is mainly to aid debugging Sentry errors - it will add a // breadcrumb powerMonitor.on('suspend', () => { - console.log('Suspending', new Date()); + logMessage('info', 'Suspending: ' + new Date()); }); await createBackgroundProcess(); @@ -701,7 +733,10 @@ ipcMain.handle( }); await remove(currentBudgetDirectory); } catch (error) { - console.error('There was an error moving your directory', error); + logMessage( + 'error', + `There was an error moving your directory: ${error}`, + ); throw error; } }, diff --git a/packages/sync-server/src/app-gocardless/bank-factory.js b/packages/sync-server/src/app-gocardless/bank-factory.js index 33e83a78db3..37906b1172c 100644 --- a/packages/sync-server/src/app-gocardless/bank-factory.js +++ b/packages/sync-server/src/app-gocardless/bank-factory.js @@ -11,8 +11,8 @@ import CbcCregbebb from './banks/cbc_cregbebb.js'; import DanskebankDabno22 from './banks/danskebank_dabno22.js'; import EasybankBawaatww from './banks/easybank_bawaatww.js'; import EntercardSwednokk from './banks/entercard_swednokk.js'; -import FortuneoFtnofrp1xxx from './banks/fortuneo_ftnofrp1xxx.js'; -import HanseaticHstbdehh from './banks/hanseatic_hstbdehh.js'; +import FortuneoFtnofrp1xxx from './banks/FORTUNEO_FTNOFRP1XXX.js'; +import HanseaticHstbdehh from './banks/HANSEATIC_HSTBDEHH.js'; import HypeHyeeit22 from './banks/hype_hyeeit22.js'; import IngIngbrobu from './banks/ing_ingbrobu.js'; import IngIngddeff from './banks/ing_ingddeff.js';