diff --git a/bin/vaultclient b/bin/vaultclient index 72f876b..b5eb338 100755 --- a/bin/vaultclient +++ b/bin/vaultclient @@ -127,7 +127,7 @@ program .command('create-account') .option('--name ') .option('--email ') - .option('--quota ', 'Maximum quota for the account', parseInt) + .option('--quota ', 'Maximum quota for the account', BigInt) .option('--accountid ') .option('--canonicalid ') .action(action.bind(null, 'create-account', (client, args) => { @@ -213,7 +213,7 @@ program program .command('update-account-quota') .option('--account-name ', 'Name of the account') - .option('--quota ', 'Maximum quota for the account', parseInt) + .option('--quota ', 'Maximum quota for the account', BigInt) .option('--parameter-validation', 'Disable validation of quota') .action(action.bind(null, 'update-account-quota', (client, args) => { client.updateAccountQuota(args.accountName, args.quota, handleVaultResponse); diff --git a/lib/IAMClient.d.ts b/lib/IAMClient.d.ts index 503b7d5..6a03a07 100644 --- a/lib/IAMClient.d.ts +++ b/lib/IAMClient.d.ts @@ -59,13 +59,13 @@ declare class VaultClient { * @param {string} accountName - account name * @param {object} options - additional creation params * @param {string} options.email - account email - * @param {string} [options.quota] - maximum quota for the account + * @param {string|number|bigint} [options.quota] - maximum quota for the account * @param {VaultClient~requestCallback} callback - callback * @returns {undefined} */ createAccount(accountName: string, options: { email: string; - quota?: string; + quota?: string | number | bigint; }, callback: any): undefined; /** * Create a password for an account @@ -97,11 +97,11 @@ declare class VaultClient { * Update Quota of an account * * @param {string} accountName - account name - * @param {number} quota - maximum quota for the account + * @param {string|number|bigint} quota - maximum quota for the account * @param {VaultClient~requestCallback} callback - callback * @returns {undefined} */ - updateAccountQuota(accountName: string, quota: number, callback: any): undefined; + updateAccountQuota(accountName: string, quota: string | number | bigint, callback: any): undefined; /** * Delete Quota of an account * diff --git a/lib/IAMClient.d.ts.map b/lib/IAMClient.d.ts.map index 56b1791..f8957e7 100644 --- a/lib/IAMClient.d.ts.map +++ b/lib/IAMClient.d.ts.map @@ -1 +1 @@ -{"version":3,"file":"IAMClient.d.ts","sourceRoot":"","sources":["IAMClient.js"],"names":[],"mappings":"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"} \ No newline at end of file +{"version":3,"file":"IAMClient.d.ts","sourceRoot":"","sources":["IAMClient.js"],"names":[],"mappings":"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"} \ No newline at end of file diff --git a/lib/IAMClient.js b/lib/IAMClient.js index c05008b..c1bab28 100644 --- a/lib/IAMClient.js +++ b/lib/IAMClient.js @@ -120,7 +120,7 @@ class VaultClient { * @param {string} accountName - account name * @param {object} options - additional creation params * @param {string} options.email - account email - * @param {string} [options.quota] - maximum quota for the account + * @param {string|number|bigint} [options.quota] - maximum quota for the account * @param {VaultClient~requestCallback} callback - callback * @returns {undefined} */ @@ -144,11 +144,13 @@ class VaultClient { } = options; if (quota) { - const conv = Number.isNaN(Number.parseInt(quota, 10)); - assert(typeof quota === 'number' - && !conv - && quota >= 0, 'Quota must be a non-negative number'); - data.quotaMax = quota; + try { + const quotaValue = BigInt(quota); + assert(quotaValue >= 0n, 'Quota must be non-negative'); + data.quotaMax = quotaValue.toString(); + } catch { + throw new Error('Quota must be a non-negative number, bigint, or string'); + } } if (externalAccountId) { const conv = Number.isNaN(Number.parseInt(externalAccountId, 10)); @@ -285,18 +287,23 @@ class VaultClient { * Update Quota of an account * * @param {string} accountName - account name - * @param {number} quota - maximum quota for the account + * @param {string|number|bigint} quota - maximum quota for the account * @param {VaultClient~requestCallback} callback - callback * @returns {undefined} */ updateAccountQuota(accountName, quota, callback) { + if (this.parameterValidation) { + try { + assert(BigInt(quota) >= 0n, 'Quota must be non-negative'); + } catch { + throw new Error('Quota must be a non-negative number, bigint, or string'); + } + } const data = { Action: 'UpdateAccountQuota', Version: '2010-05-08', - quotaMax: quota, + quotaMax: quota.toString(), }; - const quotaIsValid = typeof quota === 'number' && !Number.isNaN(Number.parseInt(quota, 10)) && quota >= 0; - assert(!this.parameterValidation || quotaIsValid, 'Quota must be a positive number'); if (accountName) { assert(!this.parameterValidation || typeof accountName === 'string', 'the account name, if set, should be a string'); @@ -344,6 +351,7 @@ class VaultClient { 'the account name, if set, should be a string'); data.AccountName = accountName; } + this.request('POST', '/', true, callback, data); } @@ -590,6 +598,7 @@ class VaultClient { 'filterKeyStartsWith should be a string'); data.filterKeyStartsWith = filterKeyStartsWith; } + this.request('POST', '/', true, callback, data); } @@ -653,6 +662,7 @@ class VaultClient { assert(false, 'arn, name, id, emailAddress and canonicalId IDs' + ' are exclusive'); } + this.request('POST', '/', true, callback, data); } @@ -1252,7 +1262,14 @@ class VaultClient { let obj = null; let err = null; try { - obj = JSON.parse(ret); + obj = JSON.parse(ret, (key, value) => { + // keep old behavior for backward compatibility + if (key === 'maxQuota' && typeof value === 'string') { + return BigInt(value); + } + + return value; + }); } catch (error) { err = error; } diff --git a/package.json b/package.json index 5b8102b..b5f9d53 100644 --- a/package.json +++ b/package.json @@ -3,7 +3,7 @@ "engines": { "node": ">=20" }, - "version": "8.5.4", + "version": "8.5.5", "description": "Client library and binary for Vault, the user directory and key management service", "main": "index.js", "repository": "scality/vaultclient", @@ -25,6 +25,7 @@ "eslint": "^9.9.1", "eslint-config-scality": "scality/Guidelines#8.3.0", "mocha": "^10.7.3", + "sinon": "^21.0.1", "typescript": "^5.5.4" }, "scripts": { diff --git a/tests/unit/clientUtilities.js b/tests/unit/clientUtilities.js new file mode 100644 index 0000000..59a4a47 --- /dev/null +++ b/tests/unit/clientUtilities.js @@ -0,0 +1,86 @@ +'use strict'; + +const assert = require('assert'); +const IAMClient = require('../../lib/IAMClient'); + +describe('client utilities', () => { + let client; + + beforeEach(() => { + client = new IAMClient('127.0.0.1', 8500); + }); + + describe('parseObj', () => { + it('should convert maxQuota string to bigint', () => { + const json = JSON.stringify({ maxQuota: '9007199254740992' }); // > MAX_SAFE_INTEGER + const result = client.parseObj(json); + + assert.strictEqual(typeof result.maxQuota, 'bigint'); + assert.strictEqual(result.maxQuota, 9007199254740992n); + }); + + it('should keep maxQuota as number when received as number (backward compatibility)', () => { + const json = JSON.stringify({ maxQuota: 1000000 }); + const result = client.parseObj(json); + + assert.strictEqual(typeof result.maxQuota, 'number'); + assert.strictEqual(result.maxQuota, 1000000); + }); + + it('should convert maxQuota in nested structures', () => { + const json = JSON.stringify({ + Accounts: [ + { id: '1', name: 'account1', maxQuota: '100000' }, + { id: '2', name: 'account2', maxQuota: '200000' }, + ], + }); + const result = client.parseObj(json); + + assert.strictEqual(typeof result.Accounts[0].maxQuota, 'bigint'); + assert.strictEqual(result.Accounts[0].maxQuota, 100000n); + assert.strictEqual(result.Accounts[1].maxQuota, 200000n); + }); + + it('should handle maxQuota value of "0"', () => { + const json = JSON.stringify({ maxQuota: '0' }); + const result = client.parseObj(json); + + assert.strictEqual(typeof result.maxQuota, 'bigint'); + assert.strictEqual(result.maxQuota, 0n); + }); + + it('should return error for invalid JSON', () => { + const invalidJson = '{ invalid json }'; + const result = client.parseObj(invalidJson); + + assert(result instanceof Error); + }); + }); + + describe('handleResponse', () => { + const res = { statusCode: 200, statusMessage: 'OK' }; + + it('should convert maxQuota in successful JSON response', done => { + const ret = JSON.stringify({ maxQuota: '9999999999999' }); + + client.handleResponse(res, ret, {}, (err, obj, code, message) => { + assert.strictEqual(err, null); + assert.strictEqual(typeof obj.maxQuota, 'bigint'); + assert.strictEqual(obj.maxQuota, 9999999999999n); + assert.strictEqual(code, 200); + assert.strictEqual(message, 'OK'); + done(); + }); + }); + + it('should handle empty response', done => { + const ret = ''; + + client.handleResponse(res, ret, {}, (err, obj) => { + assert.strictEqual(err, null); + assert.deepStrictEqual(obj, {}); + done(); + }); + }); + }); +}); diff --git a/tests/unit/createAccount.js b/tests/unit/createAccount.js new file mode 100644 index 0000000..23cf1ce --- /dev/null +++ b/tests/unit/createAccount.js @@ -0,0 +1,152 @@ +'use strict'; + +const assert = require('assert'); +const VaultClient = require('../../lib/IAMClient'); +const sinon = require('sinon'); + +describe('createAccount', () => { + let client; + let requestStub; + + beforeEach(() => { + client = new VaultClient('127.0.0.1', 8500); + requestStub = sinon.stub(client, 'request'); + }); + + afterEach(() => sinon.restore()); + + describe('quota parameter handling', () => { + it('should send number quota as string', () => { + const mockAccountData = { id: '123', name: 'testAccount' }; + requestStub.callsFake((_method, _path, _auth, callback, data) => { + assert.strictEqual(data.quotaMax, '1000000'); + callback(null, { account: { data: mockAccountData } }); + }); + + const options = { + email: 'test@example.com', + quota: 1000000, + }; + + client.createAccount('testAccount', options, err => assert.ifError(err)); + }); + + it('should send bigint quota as string', () => { + const mockAccountData = { id: '123', name: 'testAccount' }; + requestStub.callsFake((_method, _path, _auth, callback, data) => { + assert.strictEqual(data.quotaMax, '9007199254740993'); + callback(null, { account: { data: mockAccountData } }); + }); + + const options = { + email: 'test@example.com', + quota: 9007199254740993n, // Number.MAX_SAFE_INTEGER + 2 + }; + + client.createAccount('testAccount', options, err => assert.ifError(err)); + }); + + it('should send string quota as-is', () => { + const mockAccountData = { id: '123', name: 'testAccount' }; + requestStub.callsFake((_method, _path, _auth, callback, data) => { + assert.strictEqual(data.quotaMax, '5000000000'); + callback(null, { account: { data: mockAccountData } }); + }); + + const options = { + email: 'test@example.com', + quota: '5000000000', + }; + + client.createAccount('testAccount', options, err => assert.ifError(err)); + }); + + it('should not include quotaMax when quota is not provided', () => { + const mockAccountData = { id: '123', name: 'testAccount' }; + requestStub.callsFake((_method, _path, _auth, callback, data) => { + assert.strictEqual(data.quotaMax, undefined); + callback(null, { account: { data: mockAccountData } }); + }); + + const options = { + email: 'test@example.com', + }; + + client.createAccount('testAccount', options, err => assert.ifError(err)); + }); + }); + + describe('request parameters', () => { + it('should include accountName and email in request data', () => { + const mockAccountData = { id: '123', name: 'myAccount' }; + requestStub.callsFake((_method, _path, _auth, callback, data) => { + assert.strictEqual(data.name, 'testAccount'); + assert.strictEqual(data.emailAddress, 'test@example.com'); + assert.strictEqual(data.Action, 'CreateAccount'); + assert.strictEqual(data.Version, '2010-05-08'); + callback(null, { account: { data: mockAccountData } }); + }); + + const options = { email: 'test@example.com' }; + + client.createAccount('testAccount', options, err => assert.ifError(err)); + }); + }); + + describe('validation', () => { + it('should throw error if accountName is empty', () => { + const options = { email: 'test@example.com' }; + + assert.throws(() => { + client.createAccount('', options, () => {}); + }, /accountName is required/); + }); + + it('should throw error if email is missing', () => { + assert.throws(() => { + client.createAccount('testAccount', {}, () => {}); + }, /options.email is required/); + }); + + it('should throw error if quota is negative', () => { + const options = { + email: 'test@example.com', + quota: -100, + }; + + assert.throws(() => { + client.createAccount('testAccount', options, () => {}); + }, /Quota must be a non-negative number, bigint, or string/); + }); + }); + + describe('response handling', () => { + it('should return account data from response', () => { + const mockAccountData = { id: '123', name: 'testAccount', email: 'test@example.com' }; + requestStub.callsFake((_method, _path, _auth, callback) => { + callback(null, { account: { data: mockAccountData } }); + }); + + const options = { email: 'test@example.com' }; + + client.createAccount('testAccount', options, (err, response) => { + assert.ifError(err); + assert.deepStrictEqual(response, { account: mockAccountData }); + }); + }); + + it('should pass error to callback when request fails', () => { + const mockError = new Error('Request failed'); + requestStub.callsFake((_method, _path, _auth, callback) => { + callback(mockError); + }); + + const options = { email: 'test@example.com' }; + + client.createAccount('testAccount', options, (err, response) => { + assert.strictEqual(err, mockError); + assert.strictEqual(response, undefined); + }); + }); + }); +}); diff --git a/tests/unit/updateAccountQuota.js b/tests/unit/updateAccountQuota.js index f01161e..4ff3913 100644 --- a/tests/unit/updateAccountQuota.js +++ b/tests/unit/updateAccountQuota.js @@ -1,103 +1,149 @@ +'use strict'; + const assert = require('assert'); const IAMClient = require('../../lib/IAMClient'); +const sinon = require('sinon'); describe('updateAccountQuota', () => { let client; - let lastRequestData; - - const createClient = parameterValidation => { - client = new IAMClient('127.0.0.1', 8500, undefined, undefined, undefined, - undefined, undefined, undefined, undefined, undefined, undefined, undefined, parameterValidation); - lastRequestData = null; - client.request = (method, path, iamAuthenticate, callback, data, reqUid, contentType) => { - lastRequestData = { method, path, iamAuthenticate, data, reqUid, contentType }; - callback(); - }; - return client; - }; + let requestStub; + + beforeEach(() => { + client = new IAMClient('127.0.0.1', 8500); + client.parameterValidation = true; + requestStub = sinon.stub(client, 'request'); + }); + + afterEach(() => sinon.restore()); it('should call the request method with the correct parameters when options are provided', () => { - client = createClient(true); const quota = 100; const accountName = 'exampleAccount'; - const expectedData = { - Action: 'UpdateAccountQuota', - Version: '2010-05-08', - quotaMax: quota, - AccountName: 'exampleAccount', - }; + requestStub.callsFake((method, path, iamAuthenticate, cb, data) => { + assert.strictEqual(method, 'POST'); + assert.strictEqual(path, '/'); + assert.strictEqual(iamAuthenticate, true); + assert.deepStrictEqual(data, { + Action: 'UpdateAccountQuota', + Version: '2010-05-08', + quotaMax: '100', + AccountName: 'exampleAccount', + }); + cb(); + }); + + client.updateAccountQuota(accountName, quota, () => {}); + }); + + it('should call the request method with bigint quota', () => { + const quota = 9007199254740993n; // larger than Number.MAX_SAFE_INTEGER + const accountName = 'exampleAccount'; + + requestStub.callsFake((method, path, iamAuthenticate, cb, data) => { + assert.strictEqual(method, 'POST'); + assert.strictEqual(path, '/'); + assert.strictEqual(iamAuthenticate, true); + assert.deepStrictEqual(data, { + Action: 'UpdateAccountQuota', + Version: '2010-05-08', + quotaMax: '9007199254740993', + AccountName: 'exampleAccount', + }); + cb(); + }); + + client.updateAccountQuota(accountName, quota, () => {}); + }); + + it('should call the request method with string quota', () => { + const quota = '5000000000'; + const accountName = 'exampleAccount'; + + requestStub.callsFake((method, path, iamAuthenticate, cb, data) => { + assert.strictEqual(method, 'POST'); + assert.strictEqual(path, '/'); + assert.strictEqual(iamAuthenticate, true); + assert.deepStrictEqual(data, { + Action: 'UpdateAccountQuota', + Version: '2010-05-08', + quotaMax: '5000000000', + AccountName: 'exampleAccount', + }); + cb(); + }); client.updateAccountQuota(accountName, quota, () => {}); - assert.strictEqual(lastRequestData.method, 'POST'); - assert.strictEqual(lastRequestData.path, '/'); - assert.strictEqual(lastRequestData.iamAuthenticate, true); - assert.deepStrictEqual(lastRequestData.data, expectedData); }); it('should call the request method with the correct parameters when options are not provided', () => { - client = createClient(true); const quota = 100; - const expectedData = { - Action: 'UpdateAccountQuota', - Version: '2010-05-08', - quotaMax: quota, - }; + requestStub.callsFake((method, path, iamAuthenticate, cb, data) => { + assert.strictEqual(method, 'POST'); + assert.strictEqual(path, '/'); + assert.strictEqual(iamAuthenticate, true); + assert.deepStrictEqual(data, { + Action: 'UpdateAccountQuota', + Version: '2010-05-08', + quotaMax: '100', + }); + cb(); + }); client.updateAccountQuota(undefined, quota, () => {}); - assert.strictEqual(lastRequestData.method, 'POST'); - assert.strictEqual(lastRequestData.path, '/'); - assert.strictEqual(lastRequestData.iamAuthenticate, true); - assert.deepStrictEqual(lastRequestData.data, expectedData); }); - it('should throw an error if the quota is not a positive number', () => { - client = createClient(true); + it('should throw an error if the quota is negative', () => { const quota = -100; assert.throws(() => { client.updateAccountQuota(undefined, quota, () => {}); - }, /Quota must be a positive number/); + }, /Quota must be a non-negative number, bigint, or string/); }); - it('should not throw an error even when the quota is not a positive number if ci is true', () => { - client = createClient(false); + it('should not throw an error even when the quota is negative if ci is true', () => { + client.parameterValidation = false; + const quota = -100; - const expectedData = { - Action: 'UpdateAccountQuota', - Version: '2010-05-08', - quotaMax: quota, - }; + requestStub.callsFake((method, path, iamAuthenticate, cb, data) => { + assert.strictEqual(method, 'POST'); + assert.strictEqual(path, '/'); + assert.strictEqual(iamAuthenticate, true); + assert.deepStrictEqual(data, { + Action: 'UpdateAccountQuota', + Version: '2010-05-08', + quotaMax: '-100', + }); + cb(); + }); client.updateAccountQuota(undefined, quota, () => {}); - assert.strictEqual(lastRequestData.method, 'POST'); - assert.strictEqual(lastRequestData.path, '/'); - assert.strictEqual(lastRequestData.iamAuthenticate, true); - assert.deepStrictEqual(lastRequestData.data, expectedData); }); it('should not throw an error even when the quota is 0 if ci is true', () => { - client = createClient(false); + client.parameterValidation = false; + const quota = 0; - const expectedData = { - Action: 'UpdateAccountQuota', - Version: '2010-05-08', - quotaMax: quota, - }; + requestStub.callsFake((method, path, iamAuthenticate, cb, data) => { + assert.strictEqual(method, 'POST'); + assert.strictEqual(path, '/'); + assert.strictEqual(iamAuthenticate, true); + assert.deepStrictEqual(data, { + Action: 'UpdateAccountQuota', + Version: '2010-05-08', + quotaMax: '0', + }); + cb(); + }); client.updateAccountQuota(undefined, quota, () => {}); - assert.strictEqual(lastRequestData.method, 'POST'); - assert.strictEqual(lastRequestData.path, '/'); - assert.strictEqual(lastRequestData.iamAuthenticate, true); - assert.deepStrictEqual(lastRequestData.data, expectedData); }); it('should throw an error if accountName is not a string', () => { - client = createClient(true); const quota = 100; const accountName = 123; diff --git a/yarn.lock b/yarn.lock index e651be6..21a0944 100644 --- a/yarn.lock +++ b/yarn.lock @@ -150,6 +150,28 @@ "@nodelib/fs.scandir" "2.1.5" fastq "^1.6.0" +"@sinonjs/commons@^3.0.1": + version "3.0.1" + resolved "https://registry.yarnpkg.com/@sinonjs/commons/-/commons-3.0.1.tgz#1029357e44ca901a615585f6d27738dbc89084cd" + integrity sha512-K3mCHKQ9sVh8o1C9cxkwxaOmXoAMlDxC1mYyHrjqOWEcBjYr76t96zL2zlj5dUGZ3HSw240X1qgH3Mjf1yJWpQ== + dependencies: + type-detect "4.0.8" + +"@sinonjs/fake-timers@^15.1.0": + version "15.1.0" + resolved "https://registry.yarnpkg.com/@sinonjs/fake-timers/-/fake-timers-15.1.0.tgz#f42e713425d4eb1a7bc88ef5d7f76c4546586c25" + integrity sha512-cqfapCxwTGsrR80FEgOoPsTonoefMBY7dnUEbQ+GRcved0jvkJLzvX6F4WtN+HBqbPX/SiFsIRUp+IrCW/2I2w== + dependencies: + "@sinonjs/commons" "^3.0.1" + +"@sinonjs/samsam@^8.0.3": + version "8.0.3" + resolved "https://registry.yarnpkg.com/@sinonjs/samsam/-/samsam-8.0.3.tgz#eb6ffaef421e1e27783cc9b52567de20cb28072d" + integrity sha512-hw6HbX+GyVZzmaYNh82Ecj1vdGZrqVIn/keDTg63IgAwiQPO+xCz99uG6Woqgb4tM0mUiFENKZ4cqd7IX94AXQ== + dependencies: + "@sinonjs/commons" "^3.0.1" + type-detect "^4.1.0" + "@smithy/hash-node@^1.0.1": version "1.1.0" resolved "https://registry.yarnpkg.com/@smithy/hash-node/-/hash-node-1.1.0.tgz#a8da64fa4b2e2c64185df92897165c8113b499b2" @@ -483,6 +505,11 @@ diff@^5.2.0: resolved "https://registry.yarnpkg.com/diff/-/diff-5.2.0.tgz#26ded047cd1179b78b9537d5ef725503ce1ae531" integrity sha512-uIFDxqpRZGZ6ThOk84hEfqWoHx2devRFvpTZcTHur85vImfaxUbTW9Ryh4CpCuDnToOP1CEtXKIgytHBPVff5A== +diff@^8.0.2: + version "8.0.3" + resolved "https://registry.yarnpkg.com/diff/-/diff-8.0.3.tgz#c7da3d9e0e8c283bb548681f8d7174653720c2d5" + integrity sha512-qejHi7bcSD4hQAZE0tNAawRK1ZtafHDmMTMkrrIGgSLl7hTnQHmKCeB45xAcbfTqK2zowkM3j3bHt/4b/ARbYQ== + emoji-regex@^8.0.0: version "8.0.0" resolved "https://registry.yarnpkg.com/emoji-regex/-/emoji-regex-8.0.0.tgz#e818fd69ce5ccfcb404594f842963bf53164cc37" @@ -1116,6 +1143,17 @@ shebang-regex@^3.0.0: resolved "https://registry.yarnpkg.com/shebang-regex/-/shebang-regex-3.0.0.tgz#ae16f1644d873ecad843b0307b143362d4c42172" integrity sha512-7++dFhtcx3353uBaq8DDR4NuxBetBzC7ZQOhmTQInHEd6bSrXdiEyzCvG07Z44UYdLShWUyXt5M/yhz8ekcb1A== +sinon@^21.0.1: + version "21.0.1" + resolved "https://registry.yarnpkg.com/sinon/-/sinon-21.0.1.tgz#36b9126065a44906f7ba4a47b723b99315a8c356" + integrity sha512-Z0NVCW45W8Mg5oC/27/+fCqIHFnW8kpkFOq0j9XJIev4Ld0mKmERaZv5DMLAb9fGCevjKwaEeIQz5+MBXfZcDw== + dependencies: + "@sinonjs/commons" "^3.0.1" + "@sinonjs/fake-timers" "^15.1.0" + "@sinonjs/samsam" "^8.0.3" + diff "^8.0.2" + supports-color "^7.2.0" + string-width@^4.1.0, string-width@^4.2.0: version "4.2.3" resolved "https://registry.yarnpkg.com/string-width/-/string-width-4.2.3.tgz#269c7117d27b05ad2e536830a8ec895ef9c6d010" @@ -1137,7 +1175,7 @@ strip-json-comments@^3.1.1: resolved "https://registry.yarnpkg.com/strip-json-comments/-/strip-json-comments-3.1.1.tgz#31f1281b3832630434831c310c01cccda8cbe006" integrity sha512-6fPc+R4ihwqP6N/aIv2f1gMH8lOVtWQHoqC4yK6oSDVVocumAsfCqjkXnqiYMhmMwS/mEHLp7Vehlt3ql6lEig== -supports-color@^7.1.0: +supports-color@^7.1.0, supports-color@^7.2.0: version "7.2.0" resolved "https://registry.yarnpkg.com/supports-color/-/supports-color-7.2.0.tgz#1b7dcdcb32b8138801b3e478ba6a51caa89648da" integrity sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw== @@ -1180,6 +1218,16 @@ type-check@^0.4.0, type-check@~0.4.0: dependencies: prelude-ls "^1.2.1" +type-detect@4.0.8: + version "4.0.8" + resolved "https://registry.yarnpkg.com/type-detect/-/type-detect-4.0.8.tgz#7646fb5f18871cfbb7749e69bd39a6388eb7450c" + integrity sha512-0fr/mIH1dlO+x7TlcMy+bIDqKPsw/70tVyeHW787goQjhmqaZe10uwLujubK9q9Lg6Fiho1KUKDYz0Z7k7g5/g== + +type-detect@^4.1.0: + version "4.1.0" + resolved "https://registry.yarnpkg.com/type-detect/-/type-detect-4.1.0.tgz#deb2453e8f08dcae7ae98c626b13dddb0155906c" + integrity sha512-Acylog8/luQ8L7il+geoSxhEkazvkslg7PSNKOX59mbB9cOveP5aq9h74Y7YU8yDpJwetzQQrfIwtf4Wp4LKcw== + typescript@^5.5.4: version "5.5.4" resolved "https://registry.yarnpkg.com/typescript/-/typescript-5.5.4.tgz#d9852d6c82bad2d2eda4fd74a5762a8f5909e9ba"