diff --git a/.idea/dictionaries/fuxing.xml b/.idea/dictionaries/fuxing.xml index 414c529..88c1aab 100644 --- a/.idea/dictionaries/fuxing.xml +++ b/.idea/dictionaries/fuxing.xml @@ -5,6 +5,7 @@ contentedjs fuxing fuxingloh + jsonrpc karfia testcontainers diff --git a/definitions/bip122-0f9188f13cb7b2c71f2a335e3a4fc328/bitcoind.json b/definitions/bip122-0f9188f13cb7b2c71f2a335e3a4fc328/bitcoind.json index 93a9490..999dcf7 100644 --- a/definitions/bip122-0f9188f13cb7b2c71f2a335e3a4fc328/bitcoind.json +++ b/definitions/bip122-0f9188f13cb7b2c71f2a335e3a4fc328/bitcoind.json @@ -5,14 +5,12 @@ "name": "Bitcoin Regtest", "environment": { "RPC_USER": { - "type": "RandomBytes", - "length": 16, - "encoding": "hex" + "type": "Value", + "value": "karfia" }, "RPC_PASSWORD": { - "type": "RandomBytes", - "length": 16, - "encoding": "hex" + "type": "Value", + "value": "karfia" } }, "containers": { diff --git a/packages/karfia-agent/package.json b/packages/karfia-agent/package.json index 9471cfb..f7daff1 100644 --- a/packages/karfia-agent/package.json +++ b/packages/karfia-agent/package.json @@ -11,8 +11,7 @@ "build:docker": "docker buildx build --progress=plain -t ghcr.io/fuxingloh/karfia-agent:$(node -p \"require('./package.json').version\") -f Dockerfile ../../", "clean": "tsc --build --clean", "lint": "eslint .", - "push:docker": "docker buildx build --progress=plain -t ghcr.io/fuxingloh/karfia-agent:$(node -p \"require('./package.json').version\") -f Dockerfile ../../ --output type=registry --platform linux/amd64,linux/arm64", - "test": "jest --passWithNoTests" + "push:docker": "docker buildx build --progress=plain -t ghcr.io/fuxingloh/karfia-agent:$(node -p \"require('./package.json').version\") -f Dockerfile ../../ --output type=registry --platform linux/amd64,linux/arm64" }, "lint-staged": { "*": [ @@ -23,9 +22,6 @@ "prettier --write" ] }, - "jest": { - "preset": "@workspace/jest-preset" - }, "dependencies": { "@trpc/server": "^10.45.2", "ajv": "^8.12.0", @@ -35,7 +31,6 @@ "zod": "^3.22.5" }, "devDependencies": { - "@workspace/jest-preset": "workspace:*", "@workspace/tsconfig": "workspace:*" } } diff --git a/packages/karfia-definition/environment.ts b/packages/karfia-definition/environment.ts new file mode 100644 index 0000000..493e1e6 --- /dev/null +++ b/packages/karfia-definition/environment.ts @@ -0,0 +1,98 @@ +import { randomBytes } from 'node:crypto'; + +import { Environment } from './index'; + +// Dotenv Expansion Implementation +// +// Copyright (c) 2016, Scott Motte +// All rights reserved. +// +// Redistribution and use in source and binary forms, with or without +// modification, are permitted provided that the following conditions are met: +// +// * Redistributions of source code must retain the above copyright notice, this +// list of conditions and the following disclaimer. +// +// * Redistributions in binary form must reproduce the above copyright notice, +// this list of conditions and the following disclaimer in the documentation +// and/or other materials provided with the distribution. +// +// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +// AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +// IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE +// DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE +// FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL +// DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR +// SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER +// CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, +// OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +const dotenv = { + INTERPOLATE_SUBSTITUTION_REGEX: + /(\\)?(\$)(?!\()(\{?)([\w.]+)(?::?-((?:\$\{(?:\$\{(?:\$\{[^}]*\}|[^}])*}|[^}])*}|[^}])+))?(\}?)/gi, + resolveEscapeSequences: (value: string): string => { + return value.replace(/\\\$/g, '$'); + }, + interpolate: (value: string, parsed: Record): string => { + return value.replace( + dotenv.INTERPOLATE_SUBSTITUTION_REGEX, + (match, escaped, dollarSign, openBrace, key, defaultValue) => { + if (escaped === '\\') return match.slice(1); + + if (parsed[key]) { + // avoid recursion from EXPAND_SELF=$EXPAND_SELF + if (parsed[key] === value) { + return parsed[key]; + } else { + return dotenv.interpolate(parsed[key], parsed); + } + } + + if (defaultValue) { + if (defaultValue.startsWith('$')) { + return dotenv.interpolate(defaultValue, parsed); + } else { + return defaultValue; + } + } + + return ''; + }, + ); + }, + expand: (env: Record): Record => { + const copied = { ...env }; + + for (const key in copied) { + const value = dotenv.interpolate(copied[key], copied); + copied[key] = dotenv.resolveEscapeSequences(value); + } + + return copied; + }, +}; + +export function synthEnv(environment: Environment): Record { + return dotenv.expand( + Object.entries(environment).reduce( + (env, [key, factory]) => { + if (factory.type === 'RandomBytes') { + env[key] = randomBytes(factory.length).toString(factory.encoding); + return env; + } + if (factory.type === 'Value') { + env[key] = factory.value; + return env; + } + + // if (factory.type === 'Injection') { + // TODO: Prompt if CLI, inject if constructs. + // To allow for simple configuration, e.g. Masternode Keys. + + // @ts-expect-error so that we error out if we forget to handle a new factory type + throw new Error(`Unsupported Environment Factory: ${factory.type}`); + }, + {} as Record, + ), + ); +} diff --git a/packages/karfia-definition/environment.unit.ts b/packages/karfia-definition/environment.unit.ts new file mode 100644 index 0000000..520e9e8 --- /dev/null +++ b/packages/karfia-definition/environment.unit.ts @@ -0,0 +1,34 @@ +import { expect, it } from '@jest/globals'; + +import { Environment } from '.'; +import { synthEnv } from './environment'; + +it('should expand environment', async () => { + const environment: Environment = { + USER: { + type: 'RandomBytes', + length: 8, + encoding: 'hex', + }, + PASS: { + type: 'RandomBytes', + length: 16, + encoding: 'hex', + }, + URL: { + type: 'Value', + value: `http://$USER:$PASS@localhost:3000000`, + }, + DEBUG: { + type: 'Value', + value: 'karfia:*', + }, + }; + + expect(synthEnv(environment)).toStrictEqual({ + USER: expect.stringMatching(/[0-9a-f]{16}/), + PASS: expect.stringMatching(/[0-9a-f]{32}/), + URL: expect.stringMatching(/http:\/\/[0-9a-f]{16}:[0-9a-f]{32}@localhost:3000000/), + DEBUG: 'karfia:*', + }); +}); diff --git a/packages/karfia-definition/index.json b/packages/karfia-definition/index.json index c09a84c..0463eeb 100644 --- a/packages/karfia-definition/index.json +++ b/packages/karfia-definition/index.json @@ -273,7 +273,7 @@ ] }, "body": { - "$ref": "http://json-schema.org/draft-07/schema" + "$ref": "http://json-schema.org/draft-07/schema#" } }, "required": ["status"] diff --git a/packages/karfia-definition/package.json b/packages/karfia-definition/package.json index 4ffd289..18b267c 100644 --- a/packages/karfia-definition/package.json +++ b/packages/karfia-definition/package.json @@ -14,12 +14,16 @@ "index.d.ts", "schema.js", "schema.d.ts", - "schema.d.ts.map" + "schema.d.ts.map", + "environment.js", + "environment.d.ts", + "environment.d.ts.map" ], "scripts": { "build": "json2ts index.json > index.d.ts && tsc --project tsconfig.build.json", "clean": "rm -f index.d.ts && tsc --build --clean", - "lint": "eslint ." + "lint": "eslint .", + "test": "jest" }, "lint-staged": { "*": [ @@ -30,12 +34,16 @@ "prettier --write" ] }, + "jest": { + "preset": "@workspace/jest-preset" + }, "dependencies": { "ajv": "^8.12.0", "ajv-formats": "^3.0.1" }, "devDependencies": { + "@workspace/jest-preset": "workspace:*", "@workspace/tsconfig": "workspace:*", - "json-schema-to-typescript": "^13.1.2" + "json-schema-to-typescript": "^14.0.0" } } diff --git a/packages/karfia-docker-compose/environment.ts b/packages/karfia-docker-compose/environment.ts deleted file mode 100644 index 5abb84a..0000000 --- a/packages/karfia-docker-compose/environment.ts +++ /dev/null @@ -1,66 +0,0 @@ -// Copyright (c) 2016, Scott Motte -// All rights reserved. -// -// Redistribution and use in source and binary forms, with or without -// modification, are permitted provided that the following conditions are met: -// -// * Redistributions of source code must retain the above copyright notice, this -// list of conditions and the following disclaimer. -// -// * Redistributions in binary form must reproduce the above copyright notice, -// this list of conditions and the following disclaimer in the documentation -// and/or other materials provided with the distribution. -// -// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" -// AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE -// IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE -// DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE -// FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL -// DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR -// SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER -// CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, -// OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE -// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. - -const INTERPOLATE_SUBSTITUTION_REGEX = - /(\\)?(\$)(?!\()(\{?)([\w.]+)(?::?-((?:\$\{(?:\$\{(?:\$\{[^}]*\}|[^}])*}|[^}])*}|[^}])+))?(\}?)/gi; - -function resolveEscapeSequences(value: string) { - return value.replace(/\\\$/g, '$'); -} - -function interpolate(value: string, parsed: Record): string { - return value.replace(INTERPOLATE_SUBSTITUTION_REGEX, (match, escaped, dollarSign, openBrace, key, defaultValue) => { - if (escaped === '\\') return match.slice(1); - - if (parsed[key]) { - // avoid recursion from EXPAND_SELF=$EXPAND_SELF - if (parsed[key] === value) { - return parsed[key]; - } else { - return interpolate(parsed[key], parsed); - } - } - - if (defaultValue) { - if (defaultValue.startsWith('$')) { - return interpolate(defaultValue, parsed); - } else { - return defaultValue; - } - } - - return ''; - }); -} - -export function expand(env: Record) { - const copied = { ...env }; - - for (const key in copied) { - const value = interpolate(copied[key], copied); - copied[key] = resolveEscapeSequences(value); - } - - return copied; -} diff --git a/packages/karfia-docker-compose/synthesizer.ts b/packages/karfia-docker-compose/synthesizer.ts index 62db081..cfbfda2 100644 --- a/packages/karfia-docker-compose/synthesizer.ts +++ b/packages/karfia-docker-compose/synthesizer.ts @@ -2,9 +2,9 @@ import { randomBytes } from 'node:crypto'; import yaml from 'js-yaml'; import { Container, KarfiaDefinition } from 'karfia-definition'; +import { synthEnv } from 'karfia-definition/environment'; import { validate } from 'karfia-definition/schema'; -import { expand } from './environment'; import { version } from './package.json'; /** @@ -23,31 +23,10 @@ export class Synthesizer { } public synthEnv(): string { - const env = expand({ + return Object.entries({ + ...synthEnv(this.definition.environment ?? {}), KARFIA_DEPLOYMENT_ID: this.deploymentId, - ...Object.entries(this.definition.environment ?? {}).reduce( - (env, [key, factory]) => { - if (factory.type === 'RandomBytes') { - env[key] = randomBytes(factory.length).toString(factory.encoding); - return env; - } - if (factory.type === 'Value') { - env[key] = factory.value; - return env; - } - - // if (factory.type === 'Injection') { - // TODO: Prompt if CLI, inject if constructs. - // To allow for simple configuration, e.g. Masternode Keys. - - // @ts-expect-error so that we error if we forget to handle a new factory type - throw new Error(`Unsupported Environment Factory: ${factory.type}`); - }, - {} as Record, - ), - }); - - return Object.entries(env ?? {}) + }) .map(([key, value]) => `${key}=${value}`) .join('\n'); } diff --git a/packages/karfia-docker-compose/synthesizer.unit.ts b/packages/karfia-docker-compose/synthesizer.unit.ts index 4a30865..19ffe23 100644 --- a/packages/karfia-docker-compose/synthesizer.unit.ts +++ b/packages/karfia-docker-compose/synthesizer.unit.ts @@ -93,10 +93,10 @@ it('should synthesize with valid definition', async () => { const synthesizer = new Synthesizer(definition); expect(synthesizer.synthCompose()).toMatchSnapshot(); expect(synthesizer.synthEnv().split('\n')).toStrictEqual([ - expect.stringMatching(/^KARFIA_DEPLOYMENT_ID=[0-9a-f]{16}$/), expect.stringMatching(/^RPC_USER=[0-9a-f]{32}$/), expect.stringMatching(/^RPC_PASSWORD=[0-9a-f]{32}$/), expect.stringMatching(/^URL=http:\/\/[0-9a-f]{32}:[0-9a-f]{32}@ganache:8554$/), + expect.stringMatching(/^KARFIA_DEPLOYMENT_ID=[0-9a-f]{16}$/), ]); }); diff --git a/packages/karfia-testcontainers/index.ts b/packages/karfia-testcontainers/index.ts index cbd401e..3f8578b 100644 --- a/packages/karfia-testcontainers/index.ts +++ b/packages/karfia-testcontainers/index.ts @@ -65,6 +65,10 @@ export class KarfiaTestcontainers { return this.deploymentId; } + getEnvironment(): Record { + return this.environment; + } + getContainer(name: string): KarfiaContainer { const containerDef = this.definition.containers[name]; if (containerDef === undefined) { diff --git a/packages/karfia-testcontainers/karfia-container.ts b/packages/karfia-testcontainers/karfia-container.ts index 9161a8e..1149e8a 100644 --- a/packages/karfia-testcontainers/karfia-container.ts +++ b/packages/karfia-testcontainers/karfia-container.ts @@ -3,6 +3,7 @@ import { randomInt } from 'node:crypto'; import { Container, + ContainerEndpoint, ContainerEndpointHttpAuthorization, ContainerEndpointHttpJsonRpc, ContainerEndpointHttpRest, @@ -19,6 +20,12 @@ export class KarfiaContainer extends AbstractStartedContainer { super(started); } + /** + * Get the host port for a given endpoint name. + * To make a request to the container, you need to use the host and the port. + * The port is the port on the host machine that is mapped to the container port. + * @param name of the endpoint to get + */ getHostPort(name: string): number { const endpoint = this.container.endpoints[name]; if (endpoint === undefined) { @@ -27,33 +34,97 @@ export class KarfiaContainer extends AbstractStartedContainer { return this.getMappedPort(endpoint.port); } + private getEndpoint(name: string): E { + const endpoint = this.container.endpoints?.[name]; + if (endpoint === undefined) { + throw new Error(`Endpoint: '${name}' not found.`); + } + return endpoint as E; + } + /** - * Get the host endpoint for a given name. - * @param {string} name of the endpoint to get - * @param {string} host to use, defaults to the container host, + * Get the host endpoint for a given endpoint name. + * @param name of the endpoint to get + * @param host to use, defaults to the container host, * use `host.docker.internal` if you need to access the host from a container */ getHostEndpoint(name: string, host = this.getHost()): string { - const endpoint = this.container.endpoints?.[name]; - if (endpoint === undefined) { - throw new Error(`Endpoint not found, please define a '${name}' endpoint to use rpc()`); + const endpoint = this.getEndpoint(name); + const port = this.getMappedPort(endpoint.port); + switch (endpoint.protocol) { + case 'HTTP JSON-RPC 1.0': + case 'HTTP JSON-RPC 2.0': + return `http://${host}:${port}/${endpoint.path ?? ''}`; + case 'HTTPS JSON-RPC 1.0': + case 'HTTPS JSON-RPC 2.0': + return `https://${host}:${port}/${endpoint.path ?? ''}`; + case 'HTTP REST': + return `http://${host}:${port}`; + case 'HTTPS REST': + return `https://${host}:${port}`; + default: + throw new Error(`Endpoint: '${name}' does not support getHostEndpoint()`); } + } - const protocol = (endpoint as any).protocol; - switch (protocol) { - default: - throw new Error(`Unsupported protocol: ${protocol} for rpc()`); + /** + * Get the authorization headers for a given endpoint name. + * + * ### Usage Example + * + * Given you want to call a rpc method on a container that requires authorization: + * + * ```ts + * const endpoint = container.getHostEndpoint('rpc'); + * const headers = container.getAuthorizationHeaders('rpc'); + * const response = await fetch(endpoint, { + * method: 'POST', + * headers: headers, + * body: JSON.stringify({ + * jsonrpc: '2.0', + * method: 'rpc_method', + * }) + * }) + * ``` + * + * @param name of the endpoint to get + */ + getAuthHeaders(name: string): Record { + const endpoint = this.getEndpoint(name); + + const getHttpAuthHeaders = (auth?: ContainerEndpointHttpAuthorization): Record => { + if (auth === undefined) { + return {}; + } + + const type = auth.type; + if (type === 'HttpBasic') { + const username = this.resolveValue(auth.username); + const password = this.resolveValue(auth.password); + return { + Authorization: `Basic ${Buffer.from(`${username}:${password}`).toString('base64')}`, + }; + } else if (type === 'HttpBearer') { + const token = this.resolveValue(auth.token); + return { + Authorization: `Bearer ${token}`, + }; + } else { + throw new Error(`Unknown authorization type: ${type}`); + } + }; + + switch (endpoint.protocol) { case 'HTTP JSON-RPC 1.0': case 'HTTPS JSON-RPC 1.0': case 'HTTP JSON-RPC 2.0': case 'HTTPS JSON-RPC 2.0': + case 'HTTP REST': + case 'HTTPS REST': + return getHttpAuthHeaders(endpoint.authorization); + default: + throw new Error(`Endpoint: '${name}' does not support getAuthHeaders()`); } - - const jsonRpc = endpoint as ContainerEndpointHttpJsonRpc; - const scheme = jsonRpc.protocol.startsWith('HTTPS') ? 'https' : 'http'; - - const hostPort = this.getMappedPort(endpoint.port); - return `${scheme}://${host}:${hostPort}${jsonRpc.path ?? ''}`; } async rpc(options: { @@ -63,17 +134,24 @@ export class KarfiaContainer extends AbstractStartedContainer { endpoint?: string; }): Promise { const name = options.endpoint ?? 'rpc'; - const hostEndpoint = this.getHostEndpoint(name); - const endpoint = this.container.endpoints?.[name]; - const jsonRpc = endpoint as ContainerEndpointHttpJsonRpc; - const jsonRpcVersion = jsonRpc.protocol.endsWith('2.0') ? '2.0' : '1.0'; + const protocol = (endpoint as ContainerEndpointHttpJsonRpc).protocol; + switch (protocol) { + default: + throw new Error(`Unsupported protocol: ${protocol} for rpc()`); + case 'HTTP JSON-RPC 1.0': + case 'HTTPS JSON-RPC 1.0': + case 'HTTP JSON-RPC 2.0': + case 'HTTPS JSON-RPC 2.0': + } + + const jsonRpcVersion = protocol.endsWith('2.0') ? '2.0' : '1.0'; - return await fetch(hostEndpoint, { + return await fetch(this.getHostEndpoint(name), { method: 'POST', headers: { 'Content-Type': 'application/json', - ...(jsonRpc.authorization ? this.getHttpAuthorizationHeaders(jsonRpc.authorization) : {}), + ...this.getAuthHeaders(name), ...(options.headers ?? {}), }, body: JSON.stringify({ @@ -94,10 +172,10 @@ export class KarfiaContainer extends AbstractStartedContainer { }): Promise { const endpoint = this.container.endpoints?.[options.endpoint]; if (endpoint === undefined) { - throw new Error(`Endpoint not found, please define a '${options.endpoint}' endpoint to use api()`); + throw new Error(`Endpoint not found, please define a '${options.endpoint}' endpoint to use fetch()`); } - const protocol = (endpoint as any).protocol; + const protocol = (endpoint as ContainerEndpointHttpRest).protocol; switch (protocol) { default: throw new Error(`Unsupported protocol: ${protocol} for fetch()`); @@ -105,41 +183,19 @@ export class KarfiaContainer extends AbstractStartedContainer { case 'HTTPS REST': } - const rest = endpoint as ContainerEndpointHttpRest; - const scheme = rest.protocol.startsWith('HTTPS') ? 'https' : 'http'; - - const hostPort = this.getMappedPort(endpoint.port); - const hostEndpoint = `${scheme}://${this.getHost()}:${hostPort}${options.path}`; - const headers: Record = { - ...(rest.authorization ? this.getHttpAuthorizationHeaders(rest.authorization) : {}), - ...(options.headers ?? {}), - }; - - return await fetch(hostEndpoint, { + const scheme = protocol.startsWith('HTTPS') ? 'https' : 'http'; + const host = this.getHost(); + const port = this.getMappedPort(endpoint.port); + return await fetch(`${scheme}://${host}:${port}${options.path}`, { method: options.method, - headers: headers, + headers: { + ...this.getAuthHeaders(options.endpoint), + ...(options.headers ?? {}), + }, body: options.body, }); } - private getHttpAuthorizationHeaders(auth: ContainerEndpointHttpAuthorization): Record { - const type = auth.type; - if (type === 'HttpBasic') { - const username = this.resolveValue(auth.username); - const password = this.resolveValue(auth.password); - return { - Authorization: `Basic ${Buffer.from(`${username}:${password}`).toString('base64')}`, - }; - } else if (type === 'HttpBearer') { - const token = this.resolveValue(auth.token); - return { - Authorization: `Bearer ${token}`, - }; - } else { - throw new Error(`Unknown authorization type: ${type}`); - } - } - private resolveValue(value: string | EnvironmentReference): string { if (typeof value === 'string') { return value; diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 2826a18..4ec52f4 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -177,9 +177,6 @@ importers: specifier: ^3.22.5 version: 3.22.5 devDependencies: - '@workspace/jest-preset': - specifier: workspace:* - version: link:../../workspace/jest-preset '@workspace/tsconfig': specifier: workspace:* version: link:../../workspace/tsconfig @@ -193,12 +190,15 @@ importers: specifier: ^3.0.1 version: 3.0.1(ajv@8.12.0) devDependencies: + '@workspace/jest-preset': + specifier: workspace:* + version: link:../../workspace/jest-preset '@workspace/tsconfig': specifier: workspace:* version: link:../../workspace/tsconfig json-schema-to-typescript: - specifier: ^13.1.2 - version: 13.1.2 + specifier: ^14.0.0 + version: 14.0.0 packages/karfia-docker-compose: dependencies: @@ -295,6 +295,15 @@ packages: '@jridgewell/trace-mapping': 0.3.25 dev: true + /@apidevtools/json-schema-ref-parser@11.5.5: + resolution: {integrity: sha512-hv/aXDILyroHioVW27etFMV+IX6FyNn41YwbeGIAt5h/7fUTQvHI5w3ols8qYAT8aQt3kzexq5ZwxFDxNHIhdQ==} + engines: {node: '>= 16'} + dependencies: + '@jsdevtools/ono': 7.1.3 + '@types/json-schema': 7.0.15 + js-yaml: 4.1.0 + dev: true + /@babel/code-frame@7.24.2: resolution: {integrity: sha512-y5+tLQyV8pg3fsiln67BVLD1P13Eg4lh5RW9mF0zUuvLrv9uIQ4MCL+CRT+FTsBlBjcIan6PGsLcBN0m3ClUyQ==} engines: {node: '>=6.9.0'} @@ -625,16 +634,6 @@ packages: resolution: {integrity: sha512-wMue2Sy4GAVTk6Ic4tJVcnfdau+gx2EnG7S+uAEe+TWJFqE4YoWN4/H8MSLj4eYJKxGg26lZwboEniNiNwZQ6Q==} dev: false - /@bcherny/json-schema-ref-parser@10.0.5-fork: - resolution: {integrity: sha512-E/jKbPoca1tfUPj3iSbitDZTGnq6FUFjkH6L8U2oDwSuwK1WhnnVtCG7oFOTg/DDnyoXbQYUiUiGOibHqaGVnw==} - engines: {node: '>= 16'} - dependencies: - '@jsdevtools/ono': 7.1.3 - '@types/json-schema': 7.0.15 - call-me-maybe: 1.0.2 - js-yaml: 4.1.0 - dev: true - /@bcoe/v8-coverage@0.2.3: resolution: {integrity: sha512-0hYQ8SB4Db5zvZB4axdMHGwEaQjkZzFjQiN9LVYvIFB2nSUHW9tYpxWriPrWDASIxiaXax83REcLxuSdnGPZtw==} dev: true @@ -689,6 +688,18 @@ packages: /@humanwhocodes/object-schema@2.0.3: resolution: {integrity: sha512-93zYdMES/c1D69yZiKDBj0V24vqNzB/koF26KPaagAfd3P/4gUlh3Dys5ogAK+Exi9QyzlD8x/08Zt7wIKcDcA==} + /@isaacs/cliui@8.0.2: + resolution: {integrity: sha512-O8jcjabXaleOG9DQ0+ARXWZBTfnP4WNAqzuiJK7ll44AmxGKv/J2M4TPjxjY3znBCfvBXFzucm1twdyFybFqEA==} + engines: {node: '>=12'} + dependencies: + string-width: 5.1.2 + string-width-cjs: /string-width@4.2.3 + strip-ansi: 7.1.0 + strip-ansi-cjs: /strip-ansi@6.0.1 + wrap-ansi: 8.1.0 + wrap-ansi-cjs: /wrap-ansi@7.0.0 + dev: true + /@istanbuljs/load-nyc-config@1.1.0: resolution: {integrity: sha512-VjeHSlIzpv/NyD3N0YuHfXOPDIixcA1q2ZV98wsMqcYlPmv2n3Yb2lYP9XMElnaFVXg5A7YLTeLu6V84uQDjmQ==} engines: {node: '>=8'} @@ -978,6 +989,13 @@ packages: '@nodelib/fs.scandir': 2.1.5 fastq: 1.17.1 + /@pkgjs/parseargs@0.11.0: + resolution: {integrity: sha512-+1VkjdD0QBLPodGrJUeqarH8VAIvQODIbwh9XpP5Syisf7YoQgsJKPNFoqqLQlu+VQ/tVSshMR6loPMn8U+dPg==} + engines: {node: '>=14'} + requiresBuild: true + dev: true + optional: true + /@pkgr/core@0.1.1: resolution: {integrity: sha512-cq8o4cWH0ibXh9VGi5P20Tu9XF/0fFXl9EUinr9QfTM7a7p0oTA4iJRCQWppXR1Pg8dSM0UCItCkPwsk9qWWYA==} engines: {node: ^12.20.0 || ^14.18.0 || >=16.0.0} @@ -1215,13 +1233,6 @@ packages: '@types/serve-static': 1.15.7 dev: false - /@types/glob@7.2.0: - resolution: {integrity: sha512-ZUxbzKl0IfJILTS6t7ip5fQQM/J3TJYubDm3nMbgubNNYS62eXeUpoLUC8/7fJNiFYHTrGPQn7hspDUzIHX3UA==} - dependencies: - '@types/minimatch': 5.1.2 - '@types/node': 20.12.7 - dev: true - /@types/graceful-fs@4.1.9: resolution: {integrity: sha512-olP3sd1qOEe5dXTSaFvQG+02VdRXcdytWLAZsAq1PecU8uqQAhkrnbli7DagjtXKW/Bl7YJbUsa8MPcuc8LHEQ==} dependencies: @@ -1267,10 +1278,6 @@ packages: resolution: {integrity: sha512-/pyBZWSLD2n0dcHE3hq8s8ZvcETHtEuF+3E7XVt0Ig2nvsVQXdghHVcEkIWjy9A0wKfTn97a/PSDYohKIlnP/w==} dev: false - /@types/minimatch@5.1.2: - resolution: {integrity: sha512-K0VQKziLUWkVKiRVrx4a40iPaxTUefQmjtkQofBkYRcoaaL/8rhwDWww9qWbrgicNOgnpIsMxyNIUM4+n6dUIA==} - dev: true - /@types/node@18.19.31: resolution: {integrity: sha512-ArgCD39YpyyrtFKIqMDvjz79jto5fcI/SVUs2HwB+f0dAzq68yqOdyaSivLiLugSziTpNXLQrVb7RZFmdZzbhA==} dependencies: @@ -1282,8 +1289,11 @@ packages: dependencies: undici-types: 5.26.5 - /@types/prettier@2.7.3: - resolution: {integrity: sha512-+68kP9yzs4LMp7VNh8gdzMSPZFL44MLGqiHWvttYJe+6qnuVr4Ek9wSBQoveqY/r+LwjCcU29kNVkidwim+kYA==} + /@types/prettier@3.0.0: + resolution: {integrity: sha512-mFMBfMOz8QxhYVbuINtswBp9VL2b4Y0QqYHwqLz3YbgtfAcat2Dl6Y1o4e22S/OVE6Ebl9m7wWiMT2lSbAs1wA==} + deprecated: This is a stub types definition. prettier provides its own type definitions, so you do not need this installed. + dependencies: + prettier: 3.2.5 dev: true /@types/qs@6.9.15: @@ -1868,7 +1878,6 @@ packages: resolution: {integrity: sha512-XnAIvQ8eM+kC6aULx6wuQiwVsnzsi9d3WxzV3FpWTGA19F621kwdbsAcFKXgKUHZWsy+mY6iL1sHTxWEFCytDA==} dependencies: balanced-match: 1.0.2 - dev: false /braces@3.0.2: resolution: {integrity: sha512-b8um+L1RzM3WDSzvhm6gIz1yfTbBt6YTlcEKAvsmqCZZFw46z626lVj9j1yEPW33H5H+lBQpZMP1k8l+78Ha0A==} @@ -1936,10 +1945,6 @@ packages: set-function-length: 1.2.2 dev: false - /call-me-maybe@1.0.2: - resolution: {integrity: sha512-HpX65o1Hnr9HH25ojC1YGs7HCQLq0GCOibSaWER0eNpgJ/Z1MZv2mTc7+xh6WOPxbRVcmgbv4hGU+uSQ/2xFZQ==} - dev: true - /callsites@3.1.0: resolution: {integrity: sha512-P8BjAsXvZS+VIDUI11hHCQEv74YT67YUi5JJFNWIqL235sBmjX4+qx9Muvls5ivyNENctx46xQLQ3aTuE7ssaQ==} engines: {node: '>=6'} @@ -2191,6 +2196,11 @@ packages: type: 2.7.2 dev: true + /data-uri-to-buffer@4.0.1: + resolution: {integrity: sha512-0R9ikRb668HB7QDxT1vkpuUBtqc53YyAwMwGeUFKRojY/NWKvdZ+9UYtRfGmhqNbRkTSVpMbmyhXipFFv2cb/A==} + engines: {node: '>= 12'} + dev: true + /data-view-buffer@1.0.1: resolution: {integrity: sha512-0lht7OugA5x3iJLOWFhWK/5ehONdprk0ISXqVFn/NFrDu+cuc8iADFrGQz5BnRK7LLU3JmkbXSxaqX+/mXYtUA==} engines: {node: '>= 0.4'} @@ -2363,6 +2373,10 @@ packages: dependencies: esutils: 2.0.3 + /eastasianwidth@0.2.0: + resolution: {integrity: sha512-I88TYZWc9XiYHRQ4/3c5rjjfgkjhLyW2luGIheGERbNQ6OY7yTybanSpDXZa8y7VUP9YmDcYa+eyq4ca7iLqWA==} + dev: true + /electron-to-chromium@1.4.744: resolution: {integrity: sha512-nAGcF0yeKKfrP13LMFr5U1eghfFSvFLg302VUFzWlcjPOnUYd52yU5x6PBYrujhNbc4jYmZFrGZFK+xasaEzVA==} dev: true @@ -2380,6 +2394,10 @@ packages: resolution: {integrity: sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==} dev: true + /emoji-regex@9.2.2: + resolution: {integrity: sha512-L18DaJsXSUk2+42pv8mLs5jJT2hqFkFE4j21wOmgbUqsZ2hL72NsUU785g9RXgo3s0ZNgVl42TiHp3ZtOv/Vyg==} + dev: true + /end-of-stream@1.4.4: resolution: {integrity: sha512-+uw1inIHVPQoaVuHzRyXd21icM+cnt4CzD5rW+NC1wjOUSTOs+Te7FOv7AhN7vS9x/oIyhLP5PR1H+phQAHu5Q==} dependencies: @@ -2856,6 +2874,14 @@ packages: bser: 2.1.1 dev: true + /fetch-blob@3.2.0: + resolution: {integrity: sha512-7yAQpD2UMJzLi1Dqv7qFYnPbaPx7ZfFK6PiIxQ4PfkGPyNyl2Ugx+a/umUonmKqjhM4DnfbMvdX6otXq83soQQ==} + engines: {node: ^12.20 || >= 14.13} + dependencies: + node-domexception: 1.0.0 + web-streams-polyfill: 3.3.3 + dev: true + /file-entry-cache@6.0.1: resolution: {integrity: sha512-7Gps/XWymbLk2QLYK4NzpMOrYjMhdIxXuIvy2QBsLE6ljuodKvdkWs/cpyJJ3CVIVpH0Oi1Hvg1ovbMzLdFBBg==} engines: {node: ^10.12.0 || >=12.0.0} @@ -2900,6 +2926,21 @@ packages: is-callable: 1.2.7 dev: false + /foreground-child@3.1.1: + resolution: {integrity: sha512-TMKDUnIte6bfb5nWv7V/caI169OHgvwjb7V4WkeUvbQQdjr5rWKqHFiKWb/fcOwB+CzBT+qbWjvj+DVwRskpIg==} + engines: {node: '>=14'} + dependencies: + cross-spawn: 7.0.3 + signal-exit: 4.1.0 + dev: true + + /formdata-polyfill@4.0.10: + resolution: {integrity: sha512-buewHzMvYL29jdeQTVILecSaZKnt/RJWjoZCF5OW60Z67/GmSLBkOFM7qh1PI3zFNtJbaZL5eQu1vLfazOwj4g==} + engines: {node: '>=12.20.0'} + dependencies: + fetch-blob: 3.2.0 + dev: true + /fresh@0.5.2: resolution: {integrity: sha512-zJ2mQYM18rEFOudeV4GShTGIQ7RbzA7ozbU9I/XBpm7kqgMywgmylMwXHxZJmkVoYkna9d2pVXVXPdYTP9ej8Q==} engines: {node: '>= 0.6'} @@ -2973,11 +3014,6 @@ packages: engines: {node: '>=8'} dev: false - /get-stdin@8.0.0: - resolution: {integrity: sha512-sY22aA6xchAzprjyqmSEQv4UbAAzRN0L2dQB0NlN5acTTK9Don6nhoc3eAbUnpZiCANAMfd/+40kVdKfFygohg==} - engines: {node: '>=10'} - dev: true - /get-stdin@9.0.0: resolution: {integrity: sha512-dVKBjfWisLAicarI2Sf+JuBE/DghV4UzNAVe9yhEJuzeREd3JhOTE9cUaJTeSa77fsbQUK3pcOpJfM59+VKZaA==} engines: {node: '>=12'} @@ -3019,14 +3055,16 @@ packages: dependencies: is-glob: 4.0.3 - /glob-promise@4.2.2(glob@7.2.3): - resolution: {integrity: sha512-xcUzJ8NWN5bktoTIX7eOclO1Npxd/dyVqUJxlLIDasT4C7KZyqlPIwkdJ0Ypiy3p2ZKahTjK4M9uC3sNSfNMzw==} - engines: {node: '>=12'} - peerDependencies: - glob: ^7.1.6 + /glob@10.3.12: + resolution: {integrity: sha512-TCNv8vJ+xz4QiqTpfOJA7HvYv+tNIRHKfUWw/q+v2jdgN4ebz+KY9tGx5J4rHP0o84mNP+ApH66HRX8us3Khqg==} + engines: {node: '>=16 || 14 >=14.17'} + hasBin: true dependencies: - '@types/glob': 7.2.0 - glob: 7.2.3 + foreground-child: 3.1.1 + jackspeak: 2.3.6 + minimatch: 9.0.4 + minipass: 7.0.4 + path-scurry: 1.10.2 dev: true /glob@7.2.3: @@ -3482,6 +3520,15 @@ packages: istanbul-lib-report: 3.0.1 dev: true + /jackspeak@2.3.6: + resolution: {integrity: sha512-N3yCS/NegsOBokc8GAdM8UcmfsKiSS8cipheD/nivzr700H+nsMOxJjQnvwOcRYVuFkdH0wGUvW2WbXGmrZGbQ==} + engines: {node: '>=14'} + dependencies: + '@isaacs/cliui': 8.0.2 + optionalDependencies: + '@pkgjs/parseargs': 0.11.0 + dev: true + /jest-changed-files@29.7.0: resolution: {integrity: sha512-fEArFiwf1BpQ+4bXSprcDc3/x4HSzL4al2tozwVpDFpsxALjLYdyiIK4e5Vz66GQJIbXJ82+35PtysofptNX2w==} engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0} @@ -3923,25 +3970,25 @@ packages: resolution: {integrity: sha512-xyFwyhro/JEof6Ghe2iz2NcXoj2sloNsWr/XsERDK/oiPCfaNhl5ONfp+jQdAZRQQ0IJWNzH9zIZF7li91kh2w==} dev: true - /json-schema-to-typescript@13.1.2: - resolution: {integrity: sha512-17G+mjx4nunvOpkPvcz7fdwUwYCEwyH8vR3Ym3rFiQ8uzAL3go+c1306Kk7iGRk8HuXBXqy+JJJmpYl0cvOllw==} - engines: {node: '>=12.0.0'} + /json-schema-to-typescript@14.0.0: + resolution: {integrity: sha512-y/Pj93Ggu69LyRPxqpe16t8LA/5ZyJVoqmUf+o+2cBG33dH/GsDn5oDZD3EiOTkHXjVuZg4qWnWzTtS2LcoeiA==} + engines: {node: '>=16.0.0'} hasBin: true dependencies: - '@bcherny/json-schema-ref-parser': 10.0.5-fork + '@apidevtools/json-schema-ref-parser': 11.5.5 '@types/json-schema': 7.0.15 '@types/lodash': 4.17.0 - '@types/prettier': 2.7.3 + '@types/prettier': 3.0.0 cli-color: 2.0.4 - get-stdin: 8.0.0 - glob: 7.2.3 - glob-promise: 4.2.2(glob@7.2.3) + glob: 10.3.12 is-glob: 4.0.3 + js-yaml: 4.1.0 lodash: 4.17.21 minimist: 1.2.8 - mkdirp: 1.0.4 + mkdirp: 3.0.1 mz: 2.7.0 - prettier: 2.8.8 + node-fetch: 3.3.2 + prettier: 3.2.5 dev: true /json-schema-traverse@0.4.1: @@ -4095,6 +4142,11 @@ packages: wrap-ansi: 9.0.0 dev: true + /lru-cache@10.2.0: + resolution: {integrity: sha512-2bIM8x+VAf6JT4bKAljS1qUWgMsqZRPGJS6FSahIMPVvctcNhyVp7AJu7quxOW9jwkryBReKZY5tY5JYv2n/7Q==} + engines: {node: 14 || >=16.14} + dev: true + /lru-cache@5.1.1: resolution: {integrity: sha512-KpNARQA3Iwv+jTA0utUVVbrh+Jlrr1Fv0e56GGzAFOXN7dk/FviaDW8LHmK52DlcH4WP2n6gI8vN1aesBFgo9w==} dependencies: @@ -4220,11 +4272,15 @@ packages: engines: {node: '>=16 || 14 >=14.17'} dependencies: brace-expansion: 2.0.1 - dev: false /minimist@1.2.8: resolution: {integrity: sha512-2yyAR8qBkN3YuheJanUpWC5U3bb5osDywNB8RzDVlDwDHbocAJveqqj1u8+SVD7jkWT4yvsHCpWqqWqAxb0zCA==} + /minipass@7.0.4: + resolution: {integrity: sha512-jYofLM5Dam9279rdkWzqHozUo4ybjdZmCsDHePy5V/PbBcVMiSZR97gmAy45aqi8CK1lG2ECd356FU86avfwUQ==} + engines: {node: '>=16 || 14 >=14.17'} + dev: true + /mkdirp-classic@0.5.3: resolution: {integrity: sha512-gKLcREMhtuZRwRAfqP3RFW+TK4JqApVBtOIftVgjuABpAtpxhPGaDcfvbhNvD0B8iD1oUr/txX35NjcaY6Ns/A==} dev: false @@ -4233,6 +4289,13 @@ packages: resolution: {integrity: sha512-vVqVZQyf3WLx2Shd0qJ9xuvqgAyKPLAiqITEtqW0oIUjzo3PePDd6fW9iFz30ef7Ysp/oiWqbhszeGWW2T6Gzw==} engines: {node: '>=10'} hasBin: true + dev: false + + /mkdirp@3.0.1: + resolution: {integrity: sha512-+NsyUUAZDmo6YVHzL/stxSu3t9YS1iljliy3BSDrXJ/dkn1KYdmtZODGGjLcc9XLgVVpH4KshHB8XmZgMhaBXg==} + engines: {node: '>=10'} + hasBin: true + dev: true /ms@2.1.2: resolution: {integrity: sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==} @@ -4267,6 +4330,11 @@ packages: resolution: {integrity: sha512-CXdUiJembsNjuToQvxayPZF9Vqht7hewsvy2sOWafLvi2awflj9mOC6bHIg50orX8IJvWKY9wYQ/zB2kogPslQ==} dev: true + /node-domexception@1.0.0: + resolution: {integrity: sha512-/jKZoMpw0F8GRwl4/eLROPA3cfcXtLApP0QzLmUT/HuPCZWyB7IY9ZrMeKw2O/nFIqPQB3PVM9aYm0F312AXDQ==} + engines: {node: '>=10.5.0'} + dev: true + /node-fetch-native@1.6.4: resolution: {integrity: sha512-IhOigYzAKHd244OC0JIMIUrjzctirCmPkaIfhDeGcEETWof5zKYUW7e7MYvChGWh/4CJeXEgsRyGzuF334rOOQ==} dev: false @@ -4283,6 +4351,15 @@ packages: whatwg-url: 5.0.0 dev: false + /node-fetch@3.3.2: + resolution: {integrity: sha512-dRB78srN/l6gqWulah9SrxeYnxeddIG30+GOqK/9OlLVyLg3HPnr6SqOWTWOXKRwC2eGYCkZ59NNuSgvSrpgOA==} + engines: {node: ^12.20.0 || ^14.13.1 || >=16.0.0} + dependencies: + data-uri-to-buffer: 4.0.1 + fetch-blob: 3.2.0 + formdata-polyfill: 4.0.10 + dev: true + /node-int64@0.4.0: resolution: {integrity: sha512-O5lz91xSOeoXP6DulyHfllpq+Eg00MWitZIbtPfoSEvqIHdl5gfcY6hYzDWnj0qD5tz52PI08u9qUvSVeUBeHw==} dev: true @@ -4489,6 +4566,14 @@ packages: /path-parse@1.0.7: resolution: {integrity: sha512-LDJzPVEEEPR+y48z93A0Ed0yXb8pAByGWo/k5YYdYgpY2/2EsOsksJrq7lOHxryrVOn1ejG6oAp8ahvOIQD8sw==} + /path-scurry@1.10.2: + resolution: {integrity: sha512-7xTavNy5RQXnsjANvVvMkEjvloOinkAjv/Z6Ildz9v2RinZ4SBKTWFOVRbaF8p0vpHnyjV/UwNDdKuUv6M5qcA==} + engines: {node: '>=16 || 14 >=14.17'} + dependencies: + lru-cache: 10.2.0 + minipass: 7.0.4 + dev: true + /path-type@4.0.0: resolution: {integrity: sha512-gDKb8aZMDeD/tZWs9P6+q0J9Mwkdl6xMV8TjnGP3qJVJ06bdMgkbBlLU8IdfOsIsFz2BW1rNVT3XuNEl8zPAvw==} engines: {node: '>=8'} @@ -4601,12 +4686,6 @@ packages: prettier: 3.2.5 dev: false - /prettier@2.8.8: - resolution: {integrity: sha512-tdN8qQGvNjw4CHbY+XXk0JgCXn9QiF21a55rBe5LJAU+kDyC4WQn4+awm2Xfk2lQMk5fKup9XgzTZtGkjBdP9Q==} - engines: {node: '>=10.13.0'} - hasBin: true - dev: true - /prettier@3.2.5: resolution: {integrity: sha512-3/GWa9aOC0YeD7LUfvOG2NiDyhOWRvt1k+rcKhOuYnMY24iiCphgneUfJDyFXd6rZCAnuLBv6UeAULtrhT/F4A==} engines: {node: '>=14'} @@ -5034,6 +5113,15 @@ packages: strip-ansi: 6.0.1 dev: true + /string-width@5.1.2: + resolution: {integrity: sha512-HnLOCR3vjcY8beoNLtcjZ5/nxn2afmME6lhrDrebokqMap+XbeW8n9TXpPDOqdGK5qcI3oT0GKTW6wC7EMiVqA==} + engines: {node: '>=12'} + dependencies: + eastasianwidth: 0.2.0 + emoji-regex: 9.2.2 + strip-ansi: 7.1.0 + dev: true + /string-width@7.1.0: resolution: {integrity: sha512-SEIJCWiX7Kg4c129n48aDRwLbFb2LJmXXFrWBG4NGaRtMQ3myKPKbwrD1BKqQn74oCoNMBVrfDEr5M9YxCsrkw==} engines: {node: '>=18'} @@ -5555,6 +5643,11 @@ packages: makeerror: 1.0.12 dev: true + /web-streams-polyfill@3.3.3: + resolution: {integrity: sha512-d2JWLCivmZYTSIoge9MsgFCZrt571BikcWGYkjC1khllbTeDlGqZ2D8vD8E/lJa8WGWbb7Plm8/XJYV7IJHZZw==} + engines: {node: '>= 8'} + dev: true + /webidl-conversions@3.0.1: resolution: {integrity: sha512-2JAn3z8AR6rjK8Sm8orRC0h/bcl/DqL7tRPdGZ4I1CjdF+EaMLmYxBHyXuKL849eucPFhvBoxMsflfOb8kxaeQ==} dev: false @@ -5603,6 +5696,15 @@ packages: strip-ansi: 6.0.1 dev: true + /wrap-ansi@8.1.0: + resolution: {integrity: sha512-si7QWI6zUMq56bESFvagtmzMdGOtoxfR+Sez11Mobfc7tm+VkUckk9bW2UeffTGVUbOksxmSw0AA2gs8g71NCQ==} + engines: {node: '>=12'} + dependencies: + ansi-styles: 6.2.1 + string-width: 5.1.2 + strip-ansi: 7.1.0 + dev: true + /wrap-ansi@9.0.0: resolution: {integrity: sha512-G8ura3S+3Z2G+mkgNRq8dqaFZAuxfsxpBB8OCTGRTCtp+l/v9nbFNmCUP1BZMts3G1142MsZfn6eeUKrr4PD1Q==} engines: {node: '>=18'}