From 0378bf76522cbaf2c6f7f007151a998bffa351e9 Mon Sep 17 00:00:00 2001 From: Sergei Zharinov Date: Tue, 30 Jan 2024 04:50:24 -0300 Subject: [PATCH] feat: SQLite-based package cache (#26608) Co-authored-by: Michael Kriese --- docs/usage/self-hosted-experimental.md | 5 + lib/util/cache/package/index.spec.ts | 15 +++ lib/util/cache/package/index.ts | 12 +- lib/util/cache/package/sqlite.spec.ts | 55 +++++++++ lib/util/cache/package/sqlite.ts | 148 +++++++++++++++++++++++++ package.json | 2 + pnpm-lock.yaml | 147 +++++++++++++++++++++++- 7 files changed, 379 insertions(+), 5 deletions(-) create mode 100644 lib/util/cache/package/sqlite.spec.ts create mode 100644 lib/util/cache/package/sqlite.ts diff --git a/docs/usage/self-hosted-experimental.md b/docs/usage/self-hosted-experimental.md index c0b0ef870d8285..99d3dc642ff0ef 100644 --- a/docs/usage/self-hosted-experimental.md +++ b/docs/usage/self-hosted-experimental.md @@ -167,3 +167,8 @@ If set, Renovate will enable `forcePathStyle` when instantiating the AWS S3 clie > Whether to force path style URLs for S3 objects (e.g., `https://s3.amazonaws.com//` instead of `https://.s3.amazonaws.com/`) Source: [AWS S3 documentation - Interface BucketEndpointInputConfig](https://docs.aws.amazon.com/AWSJavaScriptSDK/v3/latest/clients/client-s3/interfaces/bucketendpointinputconfig.html) + +## `RENOVATE_X_SQLITE_PACKAGE_CACHE` + +If set, Renovate will use SQLite as the backend for the package cache. +Don't combine with `redisUrl`, Redis would be preferred over SQlite. diff --git a/lib/util/cache/package/index.spec.ts b/lib/util/cache/package/index.spec.ts index 2ae1929c790706..75fed5dad88389 100644 --- a/lib/util/cache/package/index.spec.ts +++ b/lib/util/cache/package/index.spec.ts @@ -2,8 +2,13 @@ import { cleanup, get, init, set } from '.'; jest.mock('./file'); jest.mock('./redis'); +jest.mock('./sqlite'); describe('util/cache/package/index', () => { + beforeEach(() => { + delete process.env.RENOVATE_X_SQLITE_PACKAGE_CACHE; + }); + it('returns undefined if not initialized', async () => { expect(await get('test', 'missing-key')).toBeUndefined(); expect(await set('test', 'some-key', 'some-value', 5)).toBeUndefined(); @@ -28,4 +33,14 @@ describe('util/cache/package/index', () => { expect(await get('some-namespace', 'unknown-key')).toBeUndefined(); expect(await cleanup({ redisUrl: 'some-url' })).toBeUndefined(); }); + + it('sets and gets sqlite', async () => { + process.env.RENOVATE_X_SQLITE_PACKAGE_CACHE = 'true'; + await init({ cacheDir: 'some-dir' }); + expect( + await set('some-namespace', 'some-key', 'some-value', 1), + ).toBeUndefined(); + expect(await get('some-namespace', 'unknown-key')).toBeUndefined(); + expect(await cleanup({ redisUrl: 'some-url' })).toBeUndefined(); + }); }); diff --git a/lib/util/cache/package/index.ts b/lib/util/cache/package/index.ts index 4bc2a94966a35c..5afcf60cd1f1a3 100644 --- a/lib/util/cache/package/index.ts +++ b/lib/util/cache/package/index.ts @@ -2,6 +2,7 @@ import type { AllConfig } from '../../../config/types'; import * as memCache from '../memory'; import * as fileCache from './file'; import * as redisCache from './redis'; +import { SqlitePackageCache } from './sqlite'; import type { PackageCache } from './types'; let cacheProxy: PackageCache | undefined; @@ -60,13 +61,22 @@ export async function init(config: AllConfig): Promise { get: redisCache.get, set: redisCache.set, }; - } else if (config.cacheDir) { + return; + } + + if (process.env.RENOVATE_X_SQLITE_PACKAGE_CACHE) { + cacheProxy = await SqlitePackageCache.init(config.cacheDir!); + return; + } + + if (config.cacheDir) { fileCache.init(config.cacheDir); cacheProxy = { get: fileCache.get, set: fileCache.set, cleanup: fileCache.cleanup, }; + return; } } diff --git a/lib/util/cache/package/sqlite.spec.ts b/lib/util/cache/package/sqlite.spec.ts new file mode 100644 index 00000000000000..93edaa1331ab9d --- /dev/null +++ b/lib/util/cache/package/sqlite.spec.ts @@ -0,0 +1,55 @@ +import { withDir } from 'tmp-promise'; +import { GlobalConfig } from '../../../config/global'; +import { SqlitePackageCache } from './sqlite'; + +function withSqlite( + fn: (sqlite: SqlitePackageCache) => Promise, +): Promise { + return withDir( + async ({ path }) => { + GlobalConfig.set({ cacheDir: path }); + const sqlite = await SqlitePackageCache.init(path); + const res = await fn(sqlite); + await sqlite.cleanup(); + return res; + }, + { unsafeCleanup: true }, + ); +} + +describe('util/cache/package/sqlite', () => { + it('should get undefined', async () => { + const res = await withSqlite((sqlite) => sqlite.get('foo', 'bar')); + expect(res).toBeUndefined(); + }); + + it('should set and get', async () => { + const res = await withSqlite(async (sqlite) => { + await sqlite.set('foo', 'bar', { foo: 'foo' }); + await sqlite.set('foo', 'bar', { bar: 'bar' }); + await sqlite.set('foo', 'bar', { baz: 'baz' }); + return sqlite.get('foo', 'bar'); + }); + expect(res).toEqual({ baz: 'baz' }); + }); + + it('reopens', async () => { + const res = await withDir( + async ({ path }) => { + GlobalConfig.set({ cacheDir: path }); + + const client1 = await SqlitePackageCache.init(path); + await client1.set('foo', 'bar', 'baz'); + await client1.cleanup(); + + const client2 = await SqlitePackageCache.init(path); + const res = await client2.get('foo', 'bar'); + await client2.cleanup(); + return res; + }, + { unsafeCleanup: true }, + ); + + expect(res).toBe('baz'); + }); +}); diff --git a/lib/util/cache/package/sqlite.ts b/lib/util/cache/package/sqlite.ts new file mode 100644 index 00000000000000..d8d6967e802804 --- /dev/null +++ b/lib/util/cache/package/sqlite.ts @@ -0,0 +1,148 @@ +import { promisify } from 'node:util'; +import zlib, { constants } from 'node:zlib'; +import Sqlite from 'better-sqlite3'; +import type { Database, Statement } from 'better-sqlite3'; +import { exists } from 'fs-extra'; +import * as upath from 'upath'; +import { logger } from '../../../logger'; +import { ensureDir } from '../../fs'; + +const brotliCompress = promisify(zlib.brotliCompress); +const brotliDecompress = promisify(zlib.brotliDecompress); + +function compress(input: unknown): Promise { + const jsonStr = JSON.stringify(input); + return brotliCompress(jsonStr, { + params: { + [constants.BROTLI_PARAM_MODE]: constants.BROTLI_MODE_TEXT, + [constants.BROTLI_PARAM_QUALITY]: 3, + }, + }); +} + +async function decompress(input: Buffer): Promise { + const buf = await brotliDecompress(input); + const jsonStr = buf.toString('utf8'); + return JSON.parse(jsonStr) as T; +} + +export class SqlitePackageCache { + private readonly upsertStatement: Statement; + private readonly getStatement: Statement; + private readonly deleteExpiredRows: Statement; + private readonly countStatement: Statement; + + static async init(cacheDir: string): Promise { + const sqliteDir = upath.join(cacheDir, 'renovate/renovate-cache-sqlite'); + await ensureDir(sqliteDir); + const sqliteFile = upath.join(sqliteDir, 'db.sqlite'); + + if (await exists(sqliteFile)) { + logger.debug(`Using SQLite package cache: ${sqliteFile}`); + } else { + logger.debug(`Creating SQLite package cache: ${sqliteFile}`); + } + + const client = new Sqlite(sqliteFile); + const res = new SqlitePackageCache(client); + return res; + } + + private constructor(private client: Database) { + client.pragma('journal_mode = WAL'); + client.pragma("encoding = 'UTF-8'"); + + client + .prepare( + ` + CREATE TABLE IF NOT EXISTS package_cache ( + namespace TEXT NOT NULL, + key TEXT NOT NULL, + expiry INTEGER NOT NULL, + data BLOB NOT NULL, + PRIMARY KEY (namespace, key) + ) + `, + ) + .run(); + client + .prepare('CREATE INDEX IF NOT EXISTS expiry ON package_cache (expiry)') + .run(); + client + .prepare( + 'CREATE INDEX IF NOT EXISTS namespace_key ON package_cache (namespace, key)', + ) + .run(); + + this.upsertStatement = client.prepare(` + INSERT INTO package_cache (namespace, key, data, expiry) + VALUES (@namespace, @key, @data, unixepoch() + @ttlSeconds) + ON CONFLICT (namespace, key) DO UPDATE SET + data = @data, + expiry = unixepoch() + @ttlSeconds + `); + + this.getStatement = client + .prepare( + ` + SELECT data FROM package_cache + WHERE + namespace = @namespace AND key = @key AND expiry > unixepoch() + `, + ) + .pluck(true); + + this.deleteExpiredRows = client.prepare(` + DELETE FROM package_cache + WHERE expiry <= unixepoch() + `); + + this.countStatement = client + .prepare('SELECT COUNT(*) FROM package_cache') + .pluck(true); + } + + async set( + namespace: string, + key: string, + value: unknown, + ttlMinutes = 5, + ): Promise { + const data = await compress(value); + const ttlSeconds = ttlMinutes * 60; + this.upsertStatement.run({ namespace, key, data, ttlSeconds }); + return Promise.resolve(); + } + + async get( + namespace: string, + key: string, + ): Promise { + const data = this.getStatement.get({ namespace, key }) as + | Buffer + | undefined; + + if (!data) { + return undefined; + } + + return await decompress(data); + } + + private cleanupExpired(): void { + const start = Date.now(); + const totalCount = this.countStatement.get() as number; + const { changes: deletedCount } = this.deleteExpiredRows.run(); + const finish = Date.now(); + const durationMs = finish - start; + logger.debug( + `SQLite package cache: deleted ${deletedCount} of ${totalCount} entries in ${durationMs}ms`, + ); + } + + cleanup(): Promise { + this.cleanupExpired(); + this.client.close(); + return Promise.resolve(); + } +} diff --git a/package.json b/package.json index cb33897c01a803..c1417664cb716f 100644 --- a/package.json +++ b/package.json @@ -168,6 +168,7 @@ "@renovatebot/pep440": "3.0.17", "@renovatebot/ruby-semver": "3.0.23", "@sindresorhus/is": "4.6.0", + "@types/better-sqlite3": "7.6.8", "@types/ms": "0.7.34", "@types/tmp": "0.2.6", "@yarnpkg/core": "4.0.2", @@ -177,6 +178,7 @@ "auth-header": "1.0.0", "aws4": "1.12.0", "azure-devops-node-api": "12.3.0", + "better-sqlite3": "9.2.2", "bunyan": "1.8.15", "cacache": "18.0.2", "cacheable-lookup": "5.0.4", diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 7ae5c1355d284d..a5aa94e5f75121 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -77,6 +77,9 @@ importers: '@sindresorhus/is': specifier: 4.6.0 version: 4.6.0 + '@types/better-sqlite3': + specifier: 7.6.8 + version: 7.6.8 '@types/ms': specifier: 0.7.34 version: 0.7.34 @@ -104,6 +107,9 @@ importers: azure-devops-node-api: specifier: 12.3.0 version: 12.3.0 + better-sqlite3: + specifier: 9.2.2 + version: 9.2.2 bunyan: specifier: 1.8.15 version: 1.8.15 @@ -3668,6 +3674,12 @@ packages: '@babel/types': 7.23.6 dev: true + /@types/better-sqlite3@7.6.8: + resolution: {integrity: sha512-ASndM4rdGrzk7iXXqyNC4fbwt4UEjpK0i3j4q4FyeQrLAthfB6s7EF135ZJE0qQxtKIMFwmyT6x0switET7uIw==} + dependencies: + '@types/node': 18.19.8 + dev: false + /@types/breejs__later@4.1.5: resolution: {integrity: sha512-O7VIO7sktsIwmLUyEeUnLMJ+QD2pv0yBGI2EMbVmwC1GOOTWJAaneL82ZyIwRgpEjJ9ciUHP8LuuuU55uj5ZjA==} dev: true @@ -4643,10 +4655,32 @@ packages: /before-after-hook@2.2.3: resolution: {integrity: sha512-NzUnlZexiaH/46WDhANlyR2bXRopNg4F/zuSA3OpZnllCUgRaOF2znDioDWrmbNVsuZk6l9pMquQB38cfBZwkQ==} + /better-sqlite3@9.2.2: + resolution: {integrity: sha512-qwjWB46il0lsDkeB4rSRI96HyDQr8sxeu1MkBVLMrwusq1KRu4Bpt1TMI+8zIJkDUtZ3umjAkaEjIlokZKWCQw==} + requiresBuild: true + dependencies: + bindings: 1.5.0 + prebuild-install: 7.1.1 + dev: false + /bignumber.js@9.1.2: resolution: {integrity: sha512-2/mKyZH9K85bzOEfhXDBFZTGd1CTs+5IHpeFQo9luiBG7hghdC851Pj2WAhb6E3R6b9tZj/XKhbg4fum+Kepug==} dev: false + /bindings@1.5.0: + resolution: {integrity: sha512-p2q/t/mhvuOj/UeLlV6566GD/guowlr0hHxClI0W9m7MWYkL1F0hLo+0Aexs9HSPCtR1SXQ0TD3MMKrXZajbiQ==} + dependencies: + file-uri-to-path: 1.0.0 + dev: false + + /bl@4.1.0: + resolution: {integrity: sha512-1W07cM9gS6DcLperZfFSj+bWLtaPGSOHWhPiGzXmvVJbRLdG82sH/Kn8EtW1VqWVA54AKf2h5k5BbnIbwF3h6w==} + dependencies: + buffer: 5.7.1 + inherits: 2.0.4 + readable-stream: 3.6.2 + dev: false + /bn.js@4.12.0: resolution: {integrity: sha512-c98Bf3tPniI+scsdk237ku1Dc3ujXQTSgyiPUDEOe7tRkhrqridvh8klBv0HCEso1OLOYcHuCv/cS6DNxKH+ZA==} dev: false @@ -4719,6 +4753,13 @@ packages: /buffer-from@1.1.2: resolution: {integrity: sha512-E+XQCRwSbaaiChtv6k6Dwgc+bx+Bs6vuKJHHl5kox/BaKbhiXzqQOwK4cO22yElGp2OCmjwVhT3HmxgyPGnJfQ==} + /buffer@5.7.1: + resolution: {integrity: sha512-EHcyIPBQ4BSGlvjB16k5KgAJ27CIsHY/2JBmCRReo48y9rQ3MaUzWX3KVlBa4U7MyX02HdVj0K7C3WaB3ju7FQ==} + dependencies: + base64-js: 1.5.1 + ieee754: 1.2.1 + dev: false + /builtins@5.0.1: resolution: {integrity: sha512-qwVpFEHNfhYJIzNRBvd2C1kyo6jz3ZSMPyyuR47OPdiKWlbYnZNyDWuyR175qDnAJLiCo5fBBqPb3RiXgWlkOQ==} dependencies: @@ -4877,6 +4918,10 @@ packages: resolution: {integrity: sha512-mKKUkUbhPpQlCOfIuZkvSEgktjPFIsZKRRbC6KWVEMvlzblj3i3asQv5ODsrwt0N3pHAEvjP8KTQPHkp0+6jOg==} dev: false + /chownr@1.1.4: + resolution: {integrity: sha512-jJ0bqzaylmJtVnNgzTeSOs8DPavpbYgEr/b0YL8/2GO3xJEhInFmhKMUnEJQjZumK7KXGFhUy89PrsJWlakBVg==} + dev: false + /chownr@2.0.0: resolution: {integrity: sha512-bIomtDF5KGpdogkLd9VspvFzk9KfpyyGlS8YFVZl7TGPBHL5snIOnxeshwVgPteQ9b4Eydl+pVbIyE1DcvCWgQ==} engines: {node: '>=10'} @@ -5209,7 +5254,6 @@ packages: /deep-extend@0.6.0: resolution: {integrity: sha512-LOHxIOaPYdHlJRtCQfDIVZtfw/ufM8+rVj649RIHzcm/vGwQRXFt6OPqIFWsm2XEMrNIEtWR64sY1LEKD2vAOA==} engines: {node: '>=4.0.0'} - dev: true /deep-is@0.1.4: resolution: {integrity: sha512-oIPzksmTg4/MriiaYGO+okXDT7ztn/w3Eptv/+gSIdMdKsJo0u4CfYNFJPy+4SKMuCqGw2wxnA+URMg3t8a/bQ==} @@ -5260,6 +5304,11 @@ packages: engines: {node: '>=8'} dev: false + /detect-libc@2.0.2: + resolution: {integrity: sha512-UX6sGumvvqSaXgdKGUsgZWqcUyIXZ/vZTrlRT/iobiKhGL0zL4d3osHj3uqllWJK+i+sixDS/3COVEOFbupFyw==} + engines: {node: '>=8'} + dev: false + /detect-newline@3.1.0: resolution: {integrity: sha512-TLz+x/vEXm/Y7P7wn1EJFNLxYpUD4TgMosxY6fAVJUnJMbupHBOncxyWUG9OpTaH9EBD7uFI5LfEgmMOc54DsA==} engines: {node: '>=8'} @@ -5900,6 +5949,11 @@ packages: engines: {node: '>= 0.8.0'} dev: true + /expand-template@2.0.3: + resolution: {integrity: sha512-XYfuKMvj4O35f/pOXLObndIRvyQ+/+6AhODh+OKWj9S9498pHHn/IMszH+gt0fBCRWMNfk1ZSp5x3AifmnI2vg==} + engines: {node: '>=6'} + dev: false + /expect-more-jest@5.5.0: resolution: {integrity: sha512-l3SwYCvT02r97uFlJnFQQiFGFEAdt6zHBiFDUiOuAfPxM1/kVGpzNXw9UM66WieNsK/e/G+UzuW39GGxqGjG8A==} dependencies: @@ -6016,6 +6070,10 @@ packages: flat-cache: 3.2.0 dev: true + /file-uri-to-path@1.0.0: + resolution: {integrity: sha512-0Zt+s3L7Vf1biwWZ29aARiVYLx7iMGnEUl9x33fbB/j3jR81u/O2LbqK+Bm1CDSNDKVtJ/YjwY7TUd5SkeLQLw==} + dev: false + /fill-range@7.0.1: resolution: {integrity: sha512-qOo9F+dMUmC2Lcb4BbVvnKJxTPjCm+RRpe4gDuGrzkL7mEVl/djYSu2OdQ2Pa302N4oqkSg9ir6jaLWJ2USVpQ==} engines: {node: '>=8'} @@ -6119,6 +6177,10 @@ packages: resolution: {integrity: sha512-cHEpEQHUg0f8XdtZCc2ZAhrHzKzT0MrFUTcvx+hfxYu7rGMDc5SKoXFh+n4YigxsHXRzc6OrCshdR1bWH6HHyg==} dev: true + /fs-constants@1.0.0: + resolution: {integrity: sha512-y6OAwoSIf7FyjMIv94u+b5rdheZEjzR63GTyZJm5qh4Bi+2YgwLCcI/fPFZkL5PSixOt6ZNKm+w+Hfp/Bciwow==} + dev: false + /fs-extra@11.2.0: resolution: {integrity: sha512-PmDi3uwK5nFuXh7XDTlVnS17xJS7vW36is2+w3xcv8SVxiB4NyATf4ctkVY5bkSjX0Y4nbvZCq1/EjtEyr9ktw==} engines: {node: '>=14.14'} @@ -6293,6 +6355,10 @@ packages: git-up: 7.0.0 dev: false + /github-from-package@0.0.0: + resolution: {integrity: sha512-SyHy3T1v2NUXn29OsWdxmK6RwHD+vkj3v8en8AOBZ1wBQ/hCAQ5bAQTD02kW4W9tUp/3Qh6J8r9EvntiyCmOOw==} + dev: false + /github-url-from-git@1.5.0: resolution: {integrity: sha512-WWOec4aRI7YAykQ9+BHmzjyNlkfJFG8QLXnDTsLz/kZefq7qkzdfo4p6fkYYMIq1aj+gZcQs/1HQhQh3DPPxlQ==} dev: false @@ -6640,6 +6706,10 @@ packages: dev: false optional: true + /ieee754@1.2.1: + resolution: {integrity: sha512-dcyqhDvX1C46lXZcVqCpK+FtMRQVdIMN6/Df5js2zouUsqG7I6sFxitIC+7KYK29KdXOLHdu9zL4sFnoVQnqaA==} + dev: false + /ignore@5.3.0: resolution: {integrity: sha512-g7dmpshy+gD7mh88OC9NwSGTKoc3kyLAZQRU1mt53Aw/vnvfXnbC+F/7F7QoYVKbV+KNvJx8wArewKy1vXMtlg==} engines: {node: '>= 4'} @@ -6726,7 +6796,6 @@ packages: /ini@1.3.8: resolution: {integrity: sha512-JV/yugV2uzW5iMRSiZAyDtQd+nxtUnjeLt0acNdw98kKLrvuRVyB80tsREOE7yvGVgalhZ6RNXCmEHkUKBKxew==} - dev: true /ini@4.1.1: resolution: {integrity: sha512-QQnnxNyfvmHFIsj7gkPcYymR8Jdw/o7mp5ZFihxn6h8Ci6fh3Dx4E1gPjpQEpIuPo9XVNY/ZUwh4BPMjGyL01g==} @@ -8269,6 +8338,10 @@ packages: minipass: 3.3.6 yallist: 4.0.0 + /mkdirp-classic@0.5.3: + resolution: {integrity: sha512-gKLcREMhtuZRwRAfqP3RFW+TK4JqApVBtOIftVgjuABpAtpxhPGaDcfvbhNvD0B8iD1oUr/txX35NjcaY6Ns/A==} + dev: false + /mkdirp@0.5.6: resolution: {integrity: sha512-FP+p8RB8OWpF3YZBCrP5gtADmtXApB5AMLn+vdyA+PyxCjrCs00mjyUozssO33cwDeT3wNGdLxJ5M//YqtHAJw==} hasBin: true @@ -8326,6 +8399,10 @@ packages: hasBin: true dev: false + /napi-build-utils@1.0.2: + resolution: {integrity: sha512-ONmRUqK7zj7DWX0D9ADe03wbwOBZxNAfF20PlGfCWQcD3+/MakShIHrMqx9YwPTfxDdF1zLeL+RGZiR9kGMLdg==} + dev: false + /natural-compare@1.4.0: resolution: {integrity: sha512-OWND8ei3VtNC9h7V60qff3SVobHr996CTwgxubgyQYEpg290h9J0buyECNNJexkFm5sOajh5G116RYA1c8ZMSw==} dev: true @@ -8372,6 +8449,13 @@ packages: - supports-color dev: true + /node-abi@3.54.0: + resolution: {integrity: sha512-p7eGEiQil0YUV3ItH4/tBb781L5impVmmx2E9FRKF7d18XXzp4PGT2tdYMFY6wQqgxD0IwNZOiSJ0/K0fSi/OA==} + engines: {node: '>=10'} + dependencies: + semver: 7.5.4 + dev: false + /node-emoji@2.1.3: resolution: {integrity: sha512-E2WEOVsgs7O16zsURJ/eH8BqhF029wGpEOnv7Urwdo2wmQanOACwJQh0devF9D9RhoZru0+9JXIS0dBXIAz+lA==} engines: {node: '>=18'} @@ -9030,6 +9114,25 @@ packages: find-up: 4.1.0 dev: true + /prebuild-install@7.1.1: + resolution: {integrity: sha512-jAXscXWMcCK8GgCoHOfIr0ODh5ai8mj63L2nWrjuAgXE6tDyYGnx4/8o/rCgU+B4JSyZBKbeZqzhtwtC3ovxjw==} + engines: {node: '>=10'} + hasBin: true + dependencies: + detect-libc: 2.0.2 + expand-template: 2.0.3 + github-from-package: 0.0.0 + minimist: 1.2.8 + mkdirp-classic: 0.5.3 + napi-build-utils: 1.0.2 + node-abi: 3.54.0 + pump: 3.0.0 + rc: 1.2.8 + simple-get: 4.0.1 + tar-fs: 2.1.1 + tunnel-agent: 0.6.0 + dev: false + /prelude-ls@1.2.1: resolution: {integrity: sha512-vkcDPrRZo1QZLbn5RLGPpg/WmIQ65qoWWhcGKf/b5eplkkarX0m9z8ppCat4mlOqUsWpyNuYgO3VRyrYHSzX5g==} engines: {node: '>= 0.8.0'} @@ -9156,7 +9259,6 @@ packages: ini: 1.3.8 minimist: 1.2.8 strip-json-comments: 2.0.1 - dev: true /re2@1.20.9: resolution: {integrity: sha512-ZYcPTFr5ha2xq3WQjBDTF9CWPSDK1z28MLh5UFRxc//7X8BNQ3A7yR7ITnP0jO346661ertdKVFqw1qoL3FMEQ==} @@ -9665,6 +9767,18 @@ packages: pkg-conf: 2.1.0 dev: true + /simple-concat@1.0.1: + resolution: {integrity: sha512-cSFtAPtRhljv69IK0hTVZQ+OfE9nePi/rtJmw5UjHeVyVroEqJXP1sFztKUy1qU+xvz3u/sfYJLa947b7nAN2Q==} + dev: false + + /simple-get@4.0.1: + resolution: {integrity: sha512-brv7p5WgH0jmQJr1ZDDfKDOSeWWg+OVypG99A/5vYGPqJ6pxiaHLy8nxtFjBA7oMa01ebA9gfh1uMCFqOuXxvA==} + dependencies: + decompress-response: 6.0.0 + once: 1.4.0 + simple-concat: 1.0.1 + dev: false + /simple-git@3.22.0: resolution: {integrity: sha512-6JujwSs0ac82jkGjMHiCnTifvf1crOiY/+tfs/Pqih6iow7VrpNKRRNdWm6RtaXpvvv/JGNYhlUtLhGFqHF+Yw==} dependencies: @@ -9951,7 +10065,6 @@ packages: /strip-json-comments@2.0.1: resolution: {integrity: sha512-4gB8na07fecVVkOI6Rs4e7T6NOTki5EmL7TUduTs6bu3EdnSycntVJ4re8kgZA+wx9IueI2Y11bfbgwtzuE0KQ==} engines: {node: '>=0.10.0'} - dev: true /strip-json-comments@3.1.1: resolution: {integrity: sha512-6fPc+R4ihwqP6N/aIv2f1gMH8lOVtWQHoqC4yK6oSDVVocumAsfCqjkXnqiYMhmMwS/mEHLp7Vehlt3ql6lEig==} @@ -9998,6 +10111,26 @@ packages: engines: {node: '>=6'} dev: true + /tar-fs@2.1.1: + resolution: {integrity: sha512-V0r2Y9scmbDRLCNex/+hYzvp/zyYjvFbHPNgVTKfQvVrb6guiE/fxP+XblDNR011utopbkex2nM4dHNV6GDsng==} + dependencies: + chownr: 1.1.4 + mkdirp-classic: 0.5.3 + pump: 3.0.0 + tar-stream: 2.2.0 + dev: false + + /tar-stream@2.2.0: + resolution: {integrity: sha512-ujeqbceABgwMZxEJnk2HDY2DlnUZ+9oEcb1KzTVfYHio0UE6dG71n60d8D2I4qNvleWrrXpmjpt7vZeF1LnMZQ==} + engines: {node: '>=6'} + dependencies: + bl: 4.1.0 + end-of-stream: 1.4.4 + fs-constants: 1.0.0 + inherits: 2.0.4 + readable-stream: 3.6.2 + dev: false + /tar@6.2.0: resolution: {integrity: sha512-/Wo7DcT0u5HUV486xg675HtjNd3BXZ6xDbzsCUZPt5iw8bTQ63bP0Raut3mvro9u+CUyq7YQd8Cx55fsZXxqLQ==} engines: {node: '>=10'} @@ -10242,6 +10375,12 @@ packages: typescript: 5.3.3 dev: true + /tunnel-agent@0.6.0: + resolution: {integrity: sha512-McnNiV1l8RYeY8tBgEpuodCC1mLUdbSN+CYBL7kJsJNInOP8UjDDEwdk6Mw60vdLLrr5NHKZhMAOSrR2NZuQ+w==} + dependencies: + safe-buffer: 5.2.1 + dev: false + /tunnel@0.0.6: resolution: {integrity: sha512-1h/Lnq9yajKY2PEbBadPXj3VxsDDu844OnaAo52UVmIzIvwwtBPIuNvkjuzBlTWpfJyUbG3ez0KSBibQkj4ojg==} engines: {node: '>=0.6.11 <=0.7.0 || >=0.7.3'}