From 04989ca7e1981b5cf3d2213fc4c4d863490ace89 Mon Sep 17 00:00:00 2001 From: Brett Kyle Date: Thu, 19 Dec 2024 15:53:44 +0000 Subject: [PATCH 01/20] Add error handling helper --- build-filtered-data.mjs | 23 +++++++++++++++++++---- helpers/error-handling.mjs | 37 +++++++++++++++++++++++++++++++++++++ 2 files changed, 56 insertions(+), 4 deletions(-) create mode 100644 helpers/error-handling.mjs diff --git a/build-filtered-data.mjs b/build-filtered-data.mjs index 5e3d30b0..f0a32678 100644 --- a/build-filtered-data.mjs +++ b/build-filtered-data.mjs @@ -1,11 +1,12 @@ import { writeFileSync } from 'fs' import { json2csv } from 'json-2-csv' -import { Octokit, RequestError } from 'octokit' +import { Octokit } from 'octokit' import { throttling } from '@octokit/plugin-throttling' import * as yarnLock from '@yarnpkg/lockfile' import checkDenyList from './helpers/check-deny-list.mjs' import checkServiceOwner from './helpers/check-service-owner.mjs' +import { handleError } from './helpers/error-handling.mjs' import rawDeps from './data/raw-deps.json' assert { type: 'json' } @@ -37,9 +38,23 @@ const octokit = new MyOctokit({ }, }) -class NoPackageJsonError extends Error {} -class CouldntReadPackageError extends Error {} -class IndirectDependencyError extends Error {} +/** + * Gets repo metadata + * + * @param {string} repoOwner - The owner of the repo + * @param {string} repoName - The name of the repo + * @returns {Promise>} + */ +async function getRepoMetaData(repoOwner, repoName) { + try { + return await octokit.rest.repos.get({ + owner: repoOwner, + repo: repoName, + }) + } catch (error) { + handleError(error, repoName) + } +} filterDeps() diff --git a/helpers/error-handling.mjs b/helpers/error-handling.mjs new file mode 100644 index 00000000..5d40ec51 --- /dev/null +++ b/helpers/error-handling.mjs @@ -0,0 +1,37 @@ +import { RequestError } from 'octokit' + +class NoPackageJsonError extends Error {} +class CouldntReadPackageError extends Error {} +class IndirectDependencyError extends Error {} + +/** + * Logs errors + * + * @param {Error} error - The error to log + * @param {string} repoName - the repo name + * + * @throws {Error} - If the error is not an expected type + */ +export function handleError(error, repoName) { + if (error instanceof RequestError) { + console.log( + `${performance.now()}: There was a problem accessing ${repoName}: ${ + error.message + }` + ) + } else if (error instanceof NoPackageJsonError) { + console.log( + `${performance.now()}: ${repoName} doesn't have a package.json at its project root. Assuming indirect usage of GOV.UK Frontend.` + ) + } else if (error instanceof CouldntReadPackageError) { + console.log( + `${performance.now()}: Couldn't find a direct dependencies list for ${repoName}. Assuming indirect usage of GOV.UK Frontend.` + ) + } else if (error instanceof IndirectDependencyError) { + console.log( + `${performance.now()}: ${repoName} doesn't list GOV.UK Frontend in its dependencies. Assuming indirect usage of GOV.UK Frontend.` + ) + } else { + throw error + } +} From 14066c3b78fd0d02414d6c7c14daddae030736c0 Mon Sep 17 00:00:00 2001 From: Brett Kyle Date: Thu, 19 Dec 2024 16:26:10 +0000 Subject: [PATCH 02/20] Move Octokit stuff --- helpers/octokit.mjs | 116 ++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 116 insertions(+) create mode 100644 helpers/octokit.mjs diff --git a/helpers/octokit.mjs b/helpers/octokit.mjs new file mode 100644 index 00000000..76965211 --- /dev/null +++ b/helpers/octokit.mjs @@ -0,0 +1,116 @@ +import { Octokit } from 'octokit' +import { throttling } from '@octokit/plugin-throttling' + +import { handleError } from './error-handling.mjs' + +const MyOctokit = Octokit.plugin(throttling) +const octokit = new MyOctokit({ + auth: process.env.GITHUB_AUTH_TOKEN, + throttle: { + onRateLimit: (retryAfter, options, octokit, retryCount) => { + octokit.log.warn( + `${performance.now()}: Request quota exhausted for request ${ + options.method + } ${options.url}` + ) + + if (retryCount < 1) { + // only retries once + octokit.log.info( + `${performance.now()}: Retrying after ${retryAfter} seconds!` + ) + return true + } + }, + onSecondaryRateLimit: (retryAfter, options, octokit) => { + // does not retry, only logs a warning + octokit.log.warn( + `${performance.now()}: SecondaryRateLimit detected for request ${ + options.method + } ${options.url}` + ) + }, + }, +}) + +/** + * Gets repo metadata + * + * @param {string} repoOwner - The owner of the repo + * @param {string} repoName - The name of the repo + * @returns {Promise>} + */ +export async function getRepoMetaData(repoOwner, repoName) { + try { + return await octokit.rest.repos.get({ + owner: repoOwner, + repo: repoName, + }) + } catch (error) { + handleError(error, repoName) + } +} + +/** + * Gets the latest commit for a repo + * @param {string} repoOwner - The owner of the repo + * @param {string} repoName - The name of the repo + * @returns {Promise>} + */ +export async function getLatestCommit(repoOwner, repoName) { + try { + const commits = await octokit.rest.repos.listCommits({ + owner: repoOwner, + repo: repoName, + per_page: 1, + }) + return commits.data[0] + } catch (error) { + handleError(error, repoName) + } +} + +/** + * Gets the tree for a repo with a given sha + * + * @param {string} repoOwner - The owner of the repo + * @param {string} repoName - The name of the repo + * @param {string} treeSha - The sha of the tree + * @returns {Promise>} + */ +export async function getRepoTree(repoOwner, repoName, treeSha) { + try { + return await octokit.rest.git.getTree({ + owner: repoOwner, + repo: repoName, + tree_sha: treeSha, + recursive: true, + }) + } catch (error) { + handleError(error, repoName) + } +} + +/** + * Gets the contents of a file in a repo + * + * Currently set up to only handle JSON files + * + * @param {string} repoOwner - The owner of the repo + * @param {string} repoName - The name of the repo + * @param {string} filePath - The path to the file + * @returns {Promise} + */ +export async function getFileContent(repoOwner, repoName, filePath) { + try { + const content = await octokit.rest.repos.getContent({ + owner: repoOwner, + repo: repoName, + path: filePath, + headers: { accept: 'application/vnd.github.raw+json' }, + }) + return JSON.parse(content.data) + } catch (error) { + handleError(error, repoName) + } +} From 7f6250155d0213b615eb89621b50fcdcd520267b Mon Sep 17 00:00:00 2001 From: Brett Kyle Date: Thu, 19 Dec 2024 17:11:59 +0000 Subject: [PATCH 03/20] Refactor filterDeps --- build-filtered-data.mjs | 442 ++++++++++++++++--------------------- helpers/error-handling.mjs | 6 +- helpers/octokit.mjs | 7 +- 3 files changed, 200 insertions(+), 255 deletions(-) diff --git a/build-filtered-data.mjs b/build-filtered-data.mjs index f0a32678..d0b1469c 100644 --- a/build-filtered-data.mjs +++ b/build-filtered-data.mjs @@ -1,281 +1,206 @@ import { writeFileSync } from 'fs' import { json2csv } from 'json-2-csv' +import { RequestError } from 'octokit' -import { Octokit } from 'octokit' -import { throttling } from '@octokit/plugin-throttling' import * as yarnLock from '@yarnpkg/lockfile' import checkDenyList from './helpers/check-deny-list.mjs' import checkServiceOwner from './helpers/check-service-owner.mjs' -import { handleError } from './helpers/error-handling.mjs' +import { + NoPackageJsonError, + CouldntReadPackageError, + IndirectDependencyError, + handleError, +} from './helpers/error-handling.mjs' +import { + getRepoMetaData, + getLatestCommit, + getFileContent, + getRepoTree, +} from './helpers/octokit.mjs' import rawDeps from './data/raw-deps.json' assert { type: 'json' } +// Set up date for file naming const currentDate = new Date() const yyyymmdd = currentDate.toISOString().split('T')[0] const timestamp = currentDate.getTime() -const MyOctokit = Octokit.plugin(throttling) -const octokit = new MyOctokit({ - auth: process.env.GITHUB_AUTH_TOKEN, - throttle: { - onRateLimit: (retryAfter, options, octokit, retryCount) => { - octokit.log.warn( - `Request quota exhausted for request ${options.method} ${options.url}` - ) - - if (retryCount < 1) { - // only retries once - octokit.log.info(`Retrying after ${retryAfter} seconds!`) - return true - } - }, - onSecondaryRateLimit: (retryAfter, options, octokit) => { - // does not retry, only logs a warning - octokit.log.warn( - `SecondaryRateLimit detected for request ${options.method} ${options.url}` - ) - }, - }, -}) +async function analyseRepo(repo) { + // Output data columns + const repoOwner = repo.owner + const repoName = repo.repo_name + let builtByGovernment = false + let indirectDependency = false + let isPrototype = false + let frontendVersion = null + let lockfileType = null + let versionDoubt = false + let couldntAccess = false + let lastUpdated = null + let repoCreated = null -/** - * Gets repo metadata - * - * @param {string} repoOwner - The owner of the repo - * @param {string} repoName - The name of the repo - * @returns {Promise>} - */ -async function getRepoMetaData(repoOwner, repoName) { try { - return await octokit.rest.repos.get({ - owner: repoOwner, - repo: repoName, - }) - } catch (error) { - handleError(error, repoName) - } -} - -filterDeps() - -async function filterDeps() { - const builtData = [] - - console.log('Analysis BEGIN') - for (const repo of rawDeps.all_public_dependent_repos) { - const repoOwner = repo.owner - const repoName = repo.repo_name - let builtByGovernment = false - let indirectDependency = false - let isPrototype = false - let frontendVersion = null - let lockfileType = null - let versionDoubt = false - let couldntAccess = false - let lastUpdated = null - let repoCreated = null - - try { - console.log(`Analysing ${repo.name}...`) - - if (checkDenyList(repoName, repoOwner)) { - console.log( - `${repo.name} is on the 'deny' list and will not be processed` - ) - continue - } - - builtByGovernment = checkServiceOwner(repoOwner) - - if (builtByGovernment) { - console.log(`${repo.name} looks like a GOV.UK service.`) - } else { - console.log( - `${repo.name} looks like it ISN'T a GOV.UK service. This has been noted.` - ) - } - - console.log('Getting repo data...') - - const repoMetaData = await octokit.rest.repos.get({ - owner: repoOwner, - repo: repoName, - }) + if (checkDenyList(repoName, repoOwner)) { + console.log( + `${performance.now()}: ${ + repo.name + } is on the 'deny' list and will not be processed` + ) + } - const firstCommit = await octokit.rest.repos.listCommits({ - owner: repoOwner, - repo: repoName, - per_page: 1, - }) + builtByGovernment = checkServiceOwner(repoOwner) + if (builtByGovernment) { + console.log( + `${performance.now()}: ${repo.name} looks like a GOV.UK service.` + ) + } else { + console.log( + `${performance.now()}: ${ + repo.name + } looks like it ISN'T a GOV.UK service. This has been noted.` + ) + } + // Get repo data + const repoMetaData = await getRepoMetaData(repoOwner, repoName) + if (repoMetaData) { lastUpdated = repoMetaData.data.pushed_at repoCreated = repoMetaData.data.created_at + } - const repoTree = await octokit.rest.git.getTree({ - owner: repoOwner, - repo: repoName, - tree_sha: firstCommit.data[0].sha, - recursive: true, - }) - - if (!repoTree.data.tree.find((file) => file.path == 'package.json')) { - throw new NoPackageJsonError() - } - - if (repoTree.data.tree.find((file) => file.path == 'package-lock.json')) { - lockfileType = 'package-lock.json' - } else if (repoTree.data.tree.find((file) => file.path == 'yarn.lock')) { - lockfileType = 'yarn.lock' - } - - // TODO: account for multiple package files - const packageFile = await octokit.rest.repos.getContent({ - owner: repoOwner, - repo: repoName, - path: 'package.json', - headers: { - accept: 'application/vnd.github.raw+json', - }, - }) - - const packageObject = JSON.parse(packageFile.data) - - if (!('dependencies' in packageObject)) { - throw new CouldntReadPackageError() - } - - isPrototype = - repoTree.data.tree.find((file) => file.path == 'lib/usage_data.js') != - undefined || 'govuk-prototype-kit' in packageObject.dependencies + const latestCommit = await getLatestCommit(repoOwner, repoName) + const repoTree = await getRepoTree(repoOwner, repoName, latestCommit.sha) + if (!repoTree.data.tree.find((file) => file.path == 'package.json')) { + indirectDependency = true + throw new NoPackageJsonError() + } - if (isPrototype) { - console.log( - `${repo.name} looks like an instance of the prototype kit. This has been noted.` - ) - } + // Handle Package.json + // TODO: account for multiple package files + if (repoTree.data.tree.find((file) => file.path == 'package-lock.json')) { + lockfileType = 'package-lock.json' + } else if (repoTree.data.tree.find((file) => file.path == 'yarn.lock')) { + lockfileType = 'yarn.lock' + } - if (!('govuk-frontend' in packageObject.dependencies)) { - throw new IndirectDependencyError() - } + const packageFile = await getFileContent( + repoOwner, + repoName, + 'package.json' + ) + const packageObject = JSON.parse(packageFile.data) + if (!('dependencies' in packageObject)) { + indirectDependency = true + throw new CouldntReadPackageError() + } - frontendVersion = packageObject.dependencies['govuk-frontend'] + // Prototype checking + isPrototype = + repoTree.data.tree.find((file) => file.path == 'lib/usage_data.js') != + undefined || 'govuk-prototype-kit' in packageObject.dependencies + if (isPrototype) { console.log( - `${repo.name} is using GOV.UK Frontend version ${frontendVersion}` + `${performance.now()}: ${ + repo.name + } looks like an instance of the prototype kit. This has been noted.` ) + } - if ( - frontendVersion.indexOf('^') != -1 || - frontendVersion.indexOf('~') != -1 - ) { - console.log( - `${repo.name} is using approximation syntax in their GOV.UK Frontend version declaration, meaning their actual version might be different to what's in their package.json. Checking their lockfile...` - ) - - if (lockfileType == 'package-lock.json') { - const packageLockFile = await octokit.rest.repos.getContent({ - owner: repoOwner, - repo: repoName, - path: 'package-lock.json', - headers: { - accept: 'application/vnd.github.raw+json', - }, - }) - - try { - const packageLockObject = JSON.parse(packageLockFile.data) - if ('packages' in packageLockObject) { - frontendVersion = - packageLockObject.packages['node_modules/govuk-frontend'] - ?.version || frontendVersion - } else if ('dependencies' in packageLockObject) { - frontendVersion = - packageLockObject.dependencies['govuk-frontend']?.version || - frontendVersion - } - } catch (e) { - console.log('There was a problem with processing this lockfile:', e) - } - } else if (lockfileType == 'yarn.lock') { - const yarnLockFile = await octokit.rest.repos.getContent({ - owner: repoOwner, - repo: repoName, - path: 'yarn.lock', - headers: { - accept: 'application/vnd.github.raw+json', - }, - }) + // Handle indirect dependencies + if (!('govuk-frontend' in packageObject.dependencies)) { + indirectDependency = true + throw new IndirectDependencyError() + } - try { - const yarnLockObject = yarnLock.default.parse(yarnLockFile.data) - frontendVersion = - yarnLockObject.object[`govuk-frontend@${frontendVersion}`] - ?.version || frontendVersion - } catch (e) { - console.log('There was a problem with processing this lockfile:', e) - } - } + frontendVersion = packageObject.dependencies['govuk-frontend'] + console.log( + `${performance.now()}: ${ + repo.name + } is using GOV.UK Frontend version ${frontendVersion}` + ) + if (frontendVersion.includes('^') || frontendVersion.includes('~')) { + frontendVersion = await getExactFrontendVersion( + repoOwner, + repoName, + frontendVersion, + lockfileType + ) + versionDoubt = + frontendVersion.includes('^') || frontendVersion.includes('~') + } + } catch (error) { + handleError(error, repoName) + if (error instanceof RequestError) { + couldntAccess = true + versionDoubt = true + } + } - if ( - frontendVersion.indexOf('^') != -1 || - frontendVersion.indexOf('~') != -1 - ) { - console.log( - `Something went wrong in lockfile processing so we'll have to assume GOV.UK Frontend version for now.` - ) - frontendVersion = frontendVersion.replace('^', '').replace('~', '') - versionDoubt = true - } + return { + repoOwner, + repoName, + couldntAccess, + frontendVersion, + versionDoubt, + builtByGovernment, + indirectDependency, + isPrototype, + lastUpdated, + repoCreated, + } +} - console.log(`GOV.UK Frontend version set to ${frontendVersion}`) - } - } catch (e) { - if (e instanceof NoPackageJsonError) { - console.log(`${repo.name} doesn't have a package.json at its project root. This makes it very difficult to know if this repo is using GOV.UK Frontend directly or at all. - We will presume that this repo is using GOV.UK Frontend indirectly.`) - indirectDependency = true - } else if (e instanceof CouldntReadPackageError) { - console.log(`We couldn't find a direct dependencies list for ${repo.name} and therefore can't ascertain the version of GOV.UK Frontend used by this repo. - We will presume that this repo is using GOV.UK Frontend indirectly.`) - indirectDependency = true - } else if (e instanceof IndirectDependencyError) { - console.log(`${repo.name} doesn't list GOV.UK Frontend in its dependencies and therefore isn't using GOV.UK Frontend directly. - We will note that this repo is using GOV.UK Frontend indirectly.`) - indirectDependency = true - } else if (e instanceof RequestError) { - console.log(`There was a problem accessing ${repo.name}, most likely due to security restrictions from that repo. See error for more details: - ${e} - We will still record this repo but we won't be able to record version`) - couldntAccess = true - versionDoubt = true - } else { - throw e - } +async function getExactFrontendVersion( + repoOwner, + repoName, + frontendVersion, + lockfileType +) { + try { + if (lockfileType === 'package-lock.json') { + const packageLockFile = await getFileContent( + repoOwner, + repoName, + 'package-lock.json' + ) + const packageLockObject = JSON.parse(packageLockFile.data) + return ( + packageLockObject.packages?.['node_modules/govuk-frontend']?.version || + packageLockObject.dependencies?.['govuk-frontend']?.version || + frontendVersion + ) + } else if (lockfileType === 'yarn.lock') { + const yarnLockFile = await getFileContent( + repoOwner, + repoName, + 'yarn.lock' + ) + const yarnLockObject = yarnLock.default.parse(yarnLockFile.data) + return ( + yarnLockObject.object[`govuk-frontend@${frontendVersion}`]?.version || + frontendVersion + ) } + } catch (error) { + console.log('There was a problem with processing the lockfile:', error) + } + return frontendVersion.replace('^', '').replace('~', '') +} - builtData.push({ - repoOwner, - repoName, - couldntAccess, - frontendVersion, - versionDoubt, - builtByGovernment, - indirectDependency, - isPrototype, - lastUpdated, - repoCreated, - }) - - console.log(`Analysis of ${repo.name} complete`) +async function filterDeps() { + const builtData = [] + const batchSize = 500 + let batchCounter = 0 + console.log(`${performance.now()}: Analysis BEGIN`) - // Write JSON file - await writeFileSync( - `data/${yyyymmdd}-${timestamp}-filtered-data.json`, - JSON.stringify(builtData, null, 2) - ) - // Write CSV file - const csv = json2csv(builtData) - await writeFileSync(`data/${yyyymmdd}-${timestamp}-filtered-data.csv`, csv) - console.log('Data updated') + for (const repo of rawDeps.all_public_dependent_repos) { + console.log(`${performance.now()}: Getting repo data...`) + const repoData = await analyseRepo(repo) + if (repoData) { + builtData.push(repoData) + batchCounter++ + } + console.log(`${performance.now()}: Analysis of ${repo.name} complete`) const index = rawDeps.all_public_dependent_repos.findIndex( (item) => item === repo @@ -289,6 +214,29 @@ async function filterDeps() { const rateLimit = await octokit.rest.rateLimit.get() console.log(`${rateLimit.data.rate.remaining} remaining on rate limit`) } + if (batchCounter >= batchSize) { + await writeBatchToFiles(builtData) + builtData.length = 0 + batchCounter = 0 + } + + if (builtData.length > 0) { + await writeBatchToFiles(builtData) + } - console.log(`We're done!`) + console.log(`${performance.now()}: We're done!`) } + +async function writeBatchToFiles(builtData) { + // Write JSON file + await writeFileSync( + `data/${yyyymmdd}-${timestamp}-filtered-data.json`, + JSON.stringify(builtData, null, 2) + ) + // Write CSV file + const csv = json2csv(builtData) + await writeFileSync(`data/${yyyymmdd}-${timestamp}-filtered-data.csv`, csv) + console.log(`${performance.now()}: Data file updated with batch of entries`) +} + +filterDeps() diff --git a/helpers/error-handling.mjs b/helpers/error-handling.mjs index 5d40ec51..e14e8a93 100644 --- a/helpers/error-handling.mjs +++ b/helpers/error-handling.mjs @@ -1,8 +1,8 @@ import { RequestError } from 'octokit' -class NoPackageJsonError extends Error {} -class CouldntReadPackageError extends Error {} -class IndirectDependencyError extends Error {} +export class NoPackageJsonError extends Error {} +export class CouldntReadPackageError extends Error {} +export class IndirectDependencyError extends Error {} /** * Logs errors diff --git a/helpers/octokit.mjs b/helpers/octokit.mjs index 76965211..262b514f 100644 --- a/helpers/octokit.mjs +++ b/helpers/octokit.mjs @@ -94,22 +94,19 @@ export async function getRepoTree(repoOwner, repoName, treeSha) { /** * Gets the contents of a file in a repo * - * Currently set up to only handle JSON files - * * @param {string} repoOwner - The owner of the repo * @param {string} repoName - The name of the repo * @param {string} filePath - The path to the file - * @returns {Promise} + * @returns {Promise>} */ export async function getFileContent(repoOwner, repoName, filePath) { try { - const content = await octokit.rest.repos.getContent({ + return await octokit.rest.repos.getContent({ owner: repoOwner, repo: repoName, path: filePath, headers: { accept: 'application/vnd.github.raw+json' }, }) - return JSON.parse(content.data) } catch (error) { handleError(error, repoName) } From 0ca0a4c134f9a84eab6d37400f304ae8a3d62f2b Mon Sep 17 00:00:00 2001 From: Brett Kyle Date: Thu, 19 Dec 2024 20:30:56 +0000 Subject: [PATCH 04/20] Add rate limit logging --- build-filtered-data.mjs | 5 +++-- helpers/octokit.mjs | 14 ++++++++++++++ 2 files changed, 17 insertions(+), 2 deletions(-) diff --git a/build-filtered-data.mjs b/build-filtered-data.mjs index d0b1469c..8fbd0703 100644 --- a/build-filtered-data.mjs +++ b/build-filtered-data.mjs @@ -16,6 +16,7 @@ import { getLatestCommit, getFileContent, getRepoTree, + getRemainingRateLimit, } from './helpers/octokit.mjs' import rawDeps from './data/raw-deps.json' assert { type: 'json' } @@ -211,8 +212,8 @@ async function filterDeps() { }` ) - const rateLimit = await octokit.rest.rateLimit.get() - console.log(`${rateLimit.data.rate.remaining} remaining on rate limit`) + const remaining = await getRemainingRateLimit() + console.log(`${remaining} remaining on rate limit`) } if (batchCounter >= batchSize) { await writeBatchToFiles(builtData) diff --git a/helpers/octokit.mjs b/helpers/octokit.mjs index 262b514f..3809bb9e 100644 --- a/helpers/octokit.mjs +++ b/helpers/octokit.mjs @@ -111,3 +111,17 @@ export async function getFileContent(repoOwner, repoName, filePath) { handleError(error, repoName) } } + +/** + * Check rate limit + * + * @returns {number} - The number of remaining requests + */ +export async function getRemainingRateLimit() { + try { + const rateLimit = await octokit.rest.rateLimit.get() + return rateLimit.data.rate.remaining + } catch (error) { + handleError(error, 'rate limit') + } +} From 9849d43036a83f313da4fdce9555ecb299dd8524 Mon Sep 17 00:00:00 2001 From: Brett Kyle Date: Thu, 19 Dec 2024 21:10:26 +0000 Subject: [PATCH 05/20] Continue RequestError --- build-filtered-data.mjs | 59 ++++++++++++++++++++++------------------- 1 file changed, 32 insertions(+), 27 deletions(-) diff --git a/build-filtered-data.mjs b/build-filtered-data.mjs index 8fbd0703..b6ee813a 100644 --- a/build-filtered-data.mjs +++ b/build-filtered-data.mjs @@ -195,36 +195,41 @@ async function filterDeps() { console.log(`${performance.now()}: Analysis BEGIN`) for (const repo of rawDeps.all_public_dependent_repos) { - console.log(`${performance.now()}: Getting repo data...`) - const repoData = await analyseRepo(repo) - if (repoData) { - builtData.push(repoData) - batchCounter++ - } - console.log(`${performance.now()}: Analysis of ${repo.name} complete`) - - const index = rawDeps.all_public_dependent_repos.findIndex( - (item) => item === repo - ) - console.log( - `This was repo number ${index + 1} of ${ - rawDeps.all_public_dependent_repos.length - }` - ) + try { + console.log(`${performance.now()}: Getting repo data...`) + const repoData = await analyseRepo(repo) + if (repoData) { + builtData.push(repoData) + batchCounter++ + } + console.log(`${performance.now()}: Analysis of ${repo.name} complete`) + + const index = rawDeps.all_public_dependent_repos.findIndex( + (item) => item === repo + ) + console.log( + `This was repo number ${index + 1} of ${ + rawDeps.all_public_dependent_repos.length + }` + ) - const remaining = await getRemainingRateLimit() - console.log(`${remaining} remaining on rate limit`) - } - if (batchCounter >= batchSize) { - await writeBatchToFiles(builtData) - builtData.length = 0 - batchCounter = 0 - } + const remaining = await getRemainingRateLimit() + console.log(`${remaining} remaining on rate limit`) + } catch (error) { + if (error instanceof RequestError) { + continue + } + } + if (batchCounter >= batchSize) { + await writeBatchToFiles(builtData) + builtData.length = 0 + batchCounter = 0 + } - if (builtData.length > 0) { - await writeBatchToFiles(builtData) + if (builtData.length > 0) { + await writeBatchToFiles(builtData) + } } - console.log(`${performance.now()}: We're done!`) } From ff58a7f13d7cb8620653d7427b44b2215cc14f3a Mon Sep 17 00:00:00 2001 From: Brett Kyle Date: Thu, 19 Dec 2024 22:03:50 +0000 Subject: [PATCH 06/20] Fix batch file write bug --- build-filtered-data.mjs | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/build-filtered-data.mjs b/build-filtered-data.mjs index b6ee813a..32673783 100644 --- a/build-filtered-data.mjs +++ b/build-filtered-data.mjs @@ -225,10 +225,10 @@ async function filterDeps() { builtData.length = 0 batchCounter = 0 } + } - if (builtData.length > 0) { - await writeBatchToFiles(builtData) - } + if (builtData.length > 0) { + await writeBatchToFiles(builtData) } console.log(`${performance.now()}: We're done!`) } From 6b7dfaaac9a22422ad81d5b708ba638ddbc36b8f Mon Sep 17 00:00:00 2001 From: Brett Kyle Date: Thu, 19 Dec 2024 22:06:34 +0000 Subject: [PATCH 07/20] Move code around --- build-filtered-data.mjs | 147 +++++++++++++++++++++++++++------------- package-lock.json | 117 +++++++++++++++++++++++++++++++- package.json | 6 +- 3 files changed, 217 insertions(+), 53 deletions(-) diff --git a/build-filtered-data.mjs b/build-filtered-data.mjs index 32673783..3b68dbce 100644 --- a/build-filtered-data.mjs +++ b/build-filtered-data.mjs @@ -1,6 +1,9 @@ import { writeFileSync } from 'fs' +import { Readable } from 'stream' import { json2csv } from 'json-2-csv' import { RequestError } from 'octokit' +import JSONStream from 'JSONStream' +import es from 'event-stream' import * as yarnLock from '@yarnpkg/lockfile' import checkDenyList from './helpers/check-deny-list.mjs' @@ -26,6 +29,51 @@ const currentDate = new Date() const yyyymmdd = currentDate.toISOString().split('T')[0] const timestamp = currentDate.getTime() +async function filterDeps() { + const builtData = [] + const batchSize = 500 + let batchCounter = 0 + console.log(`${performance.now()}: Analysis BEGIN`) + + for (const repo of rawDeps.all_public_dependent_repos) { + try { + console.log(`${performance.now()}: Getting repo data...`) + const repoData = await analyseRepo(repo) + if (repoData) { + builtData.push(repoData) + batchCounter++ + } + console.log(`${performance.now()}: Analysis of ${repo.name} complete`) + + const index = rawDeps.all_public_dependent_repos.findIndex( + (item) => item === repo + ) + console.log( + `This was repo number ${index + 1} of ${ + rawDeps.all_public_dependent_repos.length + }` + ) + + const remaining = await getRemainingRateLimit() + console.log(`${remaining} remaining on rate limit`) + } catch (error) { + if (error instanceof RequestError) { + continue + } + } + if (batchCounter >= batchSize) { + await writeBatchToFiles(builtData) + builtData.length = 0 + batchCounter = 0 + } + } + + if (builtData.length > 0) { + await writeBatchToFiles(builtData) + } + console.log(`${performance.now()}: We're done!`) +} + async function analyseRepo(repo) { // Output data columns const repoOwner = repo.owner @@ -39,6 +87,7 @@ async function analyseRepo(repo) { let couldntAccess = false let lastUpdated = null let repoCreated = null + let parentDependency = null try { if (checkDenyList(repoName, repoOwner)) { @@ -111,6 +160,7 @@ async function analyseRepo(repo) { if (!('govuk-frontend' in packageObject.dependencies)) { indirectDependency = true throw new IndirectDependencyError() + // TODO: Create a findIndirectDependencies function, add an array of the parents to the output column } frontendVersion = packageObject.dependencies['govuk-frontend'] @@ -119,12 +169,18 @@ async function analyseRepo(repo) { repo.name } is using GOV.UK Frontend version ${frontendVersion}` ) + // TODO: Since we only search the Packagelock file if we find a frontend version + // we don't need to do anything but search for the `node_modules/govuk-frontend` entry + // in the getExactFrontendVersion function. + // If however, we don't find govuk-frontend in the dependencies, then we have an indirect dependency + // and we should search the lockfile for the govuk-frontend sub-dependencies if (frontendVersion.includes('^') || frontendVersion.includes('~')) { frontendVersion = await getExactFrontendVersion( repoOwner, repoName, frontendVersion, - lockfileType + lockfileType, + parentDependency ) versionDoubt = frontendVersion.includes('^') || frontendVersion.includes('~') @@ -148,6 +204,7 @@ async function analyseRepo(repo) { isPrototype, lastUpdated, repoCreated, + parentDependency, } } @@ -155,7 +212,8 @@ async function getExactFrontendVersion( repoOwner, repoName, frontendVersion, - lockfileType + lockfileType, + parentDependency ) { try { if (lockfileType === 'package-lock.json') { @@ -164,12 +222,12 @@ async function getExactFrontendVersion( repoName, 'package-lock.json' ) - const packageLockObject = JSON.parse(packageLockFile.data) - return ( - packageLockObject.packages?.['node_modules/govuk-frontend']?.version || - packageLockObject.dependencies?.['govuk-frontend']?.version || - frontendVersion + const versionAndParent = await getFrontendVersionFromPackageLock( + packageLockFile.data ) + // eslint-disable-next-line no-unused-vars + parentDependency = versionAndParent.parent + return versionAndParent.version || frontendVersion } else if (lockfileType === 'yarn.lock') { const yarnLockFile = await getFileContent( repoOwner, @@ -188,49 +246,44 @@ async function getExactFrontendVersion( return frontendVersion.replace('^', '').replace('~', '') } -async function filterDeps() { - const builtData = [] - const batchSize = 500 - let batchCounter = 0 - console.log(`${performance.now()}: Analysis BEGIN`) - - for (const repo of rawDeps.all_public_dependent_repos) { - try { - console.log(`${performance.now()}: Getting repo data...`) - const repoData = await analyseRepo(repo) - if (repoData) { - builtData.push(repoData) - batchCounter++ - } - console.log(`${performance.now()}: Analysis of ${repo.name} complete`) +// TODO: Streaming is probably overkill. +async function getFrontendVersionFromPackageLock(packageLockText) { + const stream = Readable.from([packageLockText]) - const index = rawDeps.all_public_dependent_repos.findIndex( - (item) => item === repo - ) - console.log( - `This was repo number ${index + 1} of ${ - rawDeps.all_public_dependent_repos.length - }` - ) + // Parse top-level keys to track parents + const parser = JSONStream.parse('*') - const remaining = await getRemainingRateLimit() - console.log(`${remaining} remaining on rate limit`) - } catch (error) { - if (error instanceof RequestError) { - continue - } - } - if (batchCounter >= batchSize) { - await writeBatchToFiles(builtData) - builtData.length = 0 - batchCounter = 0 - } - } + return new Promise((resolve, reject) => { + let result = { version: null, parent: null } - if (builtData.length > 0) { - await writeBatchToFiles(builtData) - } - console.log(`${performance.now()}: We're done!`) + stream.pipe(parser).pipe( + es + .mapSync((data) => { + Object.entries(data).forEach(([parentKey, value]) => { + if (parentKey === 'node_modules/govuk-frontend') { + console.log( + `${performance.now()}: Found the node_modules/govuk-frontend package entry, version ${ + data[parentKey].version + }` + ) + result = { version: data[parentKey].version, parent: null } + } else if (value.dependencies?.['govuk-frontend']) { + if (parentKey) { + console.log( + `${performance.now()}: Found govuk-frontend as a dependency of: ${parentKey}. This has been noted.` + ) + } + result = { + version: value.dependencies['govuk-frontend'].version, + parent: parentKey, + } + } + }) + }) + .on('end', () => resolve(result)) + .on('error', reject) + ) + }) } async function writeBatchToFiles(builtData) { diff --git a/package-lock.json b/package-lock.json index 0bc55636..ffb60d6b 100644 --- a/package-lock.json +++ b/package-lock.json @@ -6,9 +6,6 @@ "": { "name": "design-system-github-stats", "license": "MIT", - "dependencies": { - "json-2-csv": "^5.5.7" - }, "devDependencies": { "@babel/eslint-parser": "^7.25.9", "@babel/plugin-syntax-import-assertions": "^7.26.0", @@ -16,7 +13,10 @@ "@octokit/plugin-throttling": "^9.3.0", "@yarnpkg/lockfile": "^1.1.0", "eslint": "^9.17.0", + "event-stream": "^4.0.1", "globals": "^15.13.0", + "json-2-csv": "^5.5.7", + "JSONStream": "^1.3.5", "octokit": "^4.0.2" } }, @@ -1301,6 +1301,7 @@ "version": "3.1.0", "resolved": "https://registry.npmjs.org/deeks/-/deeks-3.1.0.tgz", "integrity": "sha512-e7oWH1LzIdv/prMQ7pmlDlaVoL64glqzvNgkgQNgyec9ORPHrT2jaOqMtRyqJuwWjtfb6v+2rk9pmaHj+F137A==", + "dev": true, "license": "MIT", "engines": { "node": ">= 16" @@ -1317,11 +1318,19 @@ "version": "4.1.1", "resolved": "https://registry.npmjs.org/doc-path/-/doc-path-4.1.1.tgz", "integrity": "sha512-h1ErTglQAVv2gCnOpD3sFS6uolDbOKHDU1BZq+Kl3npPqroU3dYL42lUgMfd5UimlwtRgp7C9dLGwqQ5D2HYgQ==", + "dev": true, "license": "MIT", "engines": { "node": ">=16" } }, + "node_modules/duplexer": { + "version": "0.1.2", + "resolved": "https://registry.npmjs.org/duplexer/-/duplexer-0.1.2.tgz", + "integrity": "sha512-jtD6YG370ZCIi/9GTaJKQxWTZD045+4R4hTk/x1UyoqadyJ9x9CgSi1RlVDQF8U2sxLLSnFkCaMihqljHIWgMg==", + "dev": true, + "license": "MIT" + }, "node_modules/electron-to-chromium": { "version": "1.5.73", "resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.5.73.tgz", @@ -1520,6 +1529,22 @@ "node": ">=0.10.0" } }, + "node_modules/event-stream": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/event-stream/-/event-stream-4.0.1.tgz", + "integrity": "sha512-qACXdu/9VHPBzcyhdOWR5/IahhGMf0roTeZJfzz077GwylcDd90yOHLouhmv7GJ5XzPi6ekaQWd8AvPP2nOvpA==", + "dev": true, + "license": "MIT", + "dependencies": { + "duplexer": "^0.1.1", + "from": "^0.1.7", + "map-stream": "0.0.7", + "pause-stream": "^0.0.11", + "split": "^1.0.1", + "stream-combiner": "^0.2.2", + "through": "^2.3.8" + } + }, "node_modules/fast-deep-equal": { "version": "3.1.3", "resolved": "https://registry.npmjs.org/fast-deep-equal/-/fast-deep-equal-3.1.3.tgz", @@ -1592,6 +1617,13 @@ "dev": true, "license": "ISC" }, + "node_modules/from": { + "version": "0.1.7", + "resolved": "https://registry.npmjs.org/from/-/from-0.1.7.tgz", + "integrity": "sha512-twe20eF1OxVxp/ML/kq2p1uc6KvFK/+vs8WjEbeKmV2He22MKm7YF2ANIt+EOqhJ5L3K/SuuPhk0hWQDjOM23g==", + "dev": true, + "license": "MIT" + }, "node_modules/gensync": { "version": "1.0.0-beta.2", "resolved": "https://registry.npmjs.org/gensync/-/gensync-1.0.0-beta.2.tgz", @@ -1757,6 +1789,7 @@ "version": "5.5.7", "resolved": "https://registry.npmjs.org/json-2-csv/-/json-2-csv-5.5.7.tgz", "integrity": "sha512-aZ0EOadeNnO4ifF60oXXTH8P177WeHhFLbRLqILW1Kk1gNHlgAOuvddMwEIaxbLpCzx+vXo49whK6AILdg8qLg==", + "dev": true, "license": "MIT", "dependencies": { "deeks": "3.1.0", @@ -1801,6 +1834,33 @@ "node": ">=6" } }, + "node_modules/jsonparse": { + "version": "1.3.1", + "resolved": "https://registry.npmjs.org/jsonparse/-/jsonparse-1.3.1.tgz", + "integrity": "sha512-POQXvpdL69+CluYsillJ7SUhKvytYjW9vG/GKpnf+xP8UWgYEM/RaMzHHofbALDiKbbP1W8UEYmgGl39WkPZsg==", + "dev": true, + "engines": [ + "node >= 0.2.0" + ], + "license": "MIT" + }, + "node_modules/JSONStream": { + "version": "1.3.5", + "resolved": "https://registry.npmjs.org/JSONStream/-/JSONStream-1.3.5.tgz", + "integrity": "sha512-E+iruNOY8VV9s4JEbe1aNEm6MiszPRr/UfcHMz0TQh1BXSxHK+ASV1R6W4HpjBhSeS+54PIsAMCBmwD06LLsqQ==", + "dev": true, + "license": "(MIT OR Apache-2.0)", + "dependencies": { + "jsonparse": "^1.2.0", + "through": ">=2.2.7 <3" + }, + "bin": { + "JSONStream": "bin.js" + }, + "engines": { + "node": "*" + } + }, "node_modules/keyv": { "version": "4.5.4", "resolved": "https://registry.npmjs.org/keyv/-/keyv-4.5.4.tgz", @@ -1857,6 +1917,13 @@ "node": "14 || >=16.14" } }, + "node_modules/map-stream": { + "version": "0.0.7", + "resolved": "https://registry.npmjs.org/map-stream/-/map-stream-0.0.7.tgz", + "integrity": "sha512-C0X0KQmGm3N2ftbTGBhSyuydQ+vV1LC3f3zPvT3RXHXNZrvfPZcoXp/N5DOa8vedX/rTMm2CjTtivFg2STJMRQ==", + "dev": true, + "license": "MIT" + }, "node_modules/minimatch": { "version": "3.1.2", "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz", @@ -1996,6 +2063,19 @@ "node": ">=8" } }, + "node_modules/pause-stream": { + "version": "0.0.11", + "resolved": "https://registry.npmjs.org/pause-stream/-/pause-stream-0.0.11.tgz", + "integrity": "sha512-e3FBlXLmN/D1S+zHzanP4E/4Z60oFAa3O051qt1pxa7DEJWKAyil6upYVXCWadEnuoqa4Pkc9oUx9zsxYeRv8A==", + "dev": true, + "license": [ + "MIT", + "Apache2" + ], + "dependencies": { + "through": "~2.3" + } + }, "node_modules/picocolors": { "version": "1.1.1", "resolved": "https://registry.npmjs.org/picocolors/-/picocolors-1.1.1.tgz", @@ -2067,6 +2147,30 @@ "node": ">=8" } }, + "node_modules/split": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/split/-/split-1.0.1.tgz", + "integrity": "sha512-mTyOoPbrivtXnwnIxZRFYRrPNtEFKlpB2fvjSnCQUiAA6qAZzqwna5envK4uk6OIeP17CsdF3rSBGYVBsU0Tkg==", + "dev": true, + "license": "MIT", + "dependencies": { + "through": "2" + }, + "engines": { + "node": "*" + } + }, + "node_modules/stream-combiner": { + "version": "0.2.2", + "resolved": "https://registry.npmjs.org/stream-combiner/-/stream-combiner-0.2.2.tgz", + "integrity": "sha512-6yHMqgLYDzQDcAkL+tjJDC5nSNuNIx0vZtRZeiPh7Saef7VHX9H5Ijn9l2VIol2zaNYlYEX6KyuT/237A58qEQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "duplexer": "~0.1.1", + "through": "~2.3.4" + } + }, "node_modules/strip-json-comments": { "version": "3.1.1", "resolved": "https://registry.npmjs.org/strip-json-comments/-/strip-json-comments-3.1.1.tgz", @@ -2093,6 +2197,13 @@ "node": ">=8" } }, + "node_modules/through": { + "version": "2.3.8", + "resolved": "https://registry.npmjs.org/through/-/through-2.3.8.tgz", + "integrity": "sha512-w89qg7PI8wAdvX60bMDP+bFoD5Dvhm9oLheFp5O4a2QF0cSBGsBX4qZmadPMvVqlLJBBci+WqGGOAPvcDeNSVg==", + "dev": true, + "license": "MIT" + }, "node_modules/type-check": { "version": "0.4.0", "resolved": "https://registry.npmjs.org/type-check/-/type-check-0.4.0.tgz", diff --git a/package.json b/package.json index ce7ce399..d1819cfc 100644 --- a/package.json +++ b/package.json @@ -22,10 +22,10 @@ "@octokit/plugin-throttling": "^9.3.0", "@yarnpkg/lockfile": "^1.1.0", "eslint": "^9.17.0", + "event-stream": "^4.0.1", "globals": "^15.13.0", + "json-2-csv": "^5.5.7", + "JSONStream": "^1.3.5", "octokit": "^4.0.2" - }, - "dependencies": { - "json-2-csv": "^5.5.7" } } From 3c84438a459aa544fadce7370e91ebfb05b8359c Mon Sep 17 00:00:00 2001 From: Brett Kyle Date: Fri, 20 Dec 2024 22:35:32 +0000 Subject: [PATCH 08/20] Add RepoData class --- NOTES.md | 5 + build-filtered-data.mjs | 267 +++++++------------------------- helpers/check-deny-list.mjs | 3 - helpers/check-service-owner.mjs | 3 - helpers/error-handling.mjs | 9 +- helpers/repo-data.mjs | 153 ++++++++++++++++++ 6 files changed, 218 insertions(+), 222 deletions(-) create mode 100644 NOTES.md delete mode 100644 helpers/check-deny-list.mjs delete mode 100644 helpers/check-service-owner.mjs create mode 100644 helpers/repo-data.mjs diff --git a/NOTES.md b/NOTES.md new file mode 100644 index 00000000..989cc129 --- /dev/null +++ b/NOTES.md @@ -0,0 +1,5 @@ +## Current status + +The new approach is working up to a point. There was a snafu with not awaiting async functions, and then there was a function I wasn't returning from. Then the rate limit hit! + +Next step is to run the script again and keep bugfixing. diff --git a/build-filtered-data.mjs b/build-filtered-data.mjs index 3b68dbce..25e392d9 100644 --- a/build-filtered-data.mjs +++ b/build-filtered-data.mjs @@ -1,26 +1,12 @@ -import { writeFileSync } from 'fs' -import { Readable } from 'stream' +import { appendFileSync } from 'fs' import { json2csv } from 'json-2-csv' import { RequestError } from 'octokit' -import JSONStream from 'JSONStream' -import es from 'event-stream' -import * as yarnLock from '@yarnpkg/lockfile' -import checkDenyList from './helpers/check-deny-list.mjs' -import checkServiceOwner from './helpers/check-service-owner.mjs' -import { - NoPackageJsonError, - CouldntReadPackageError, - IndirectDependencyError, - handleError, -} from './helpers/error-handling.mjs' -import { - getRepoMetaData, - getLatestCommit, - getFileContent, - getRepoTree, - getRemainingRateLimit, -} from './helpers/octokit.mjs' +import denyList from './helpers/data/deny-list.json' assert { type: 'json' } +import governmentServiceOwners from './helpers/data/service-owners.json' assert { type: 'json' } +import { handleError } from './helpers/error-handling.mjs' +import { getRemainingRateLimit } from './helpers/octokit.mjs' +import { RepoData } from './helpers/repo-data.mjs' import rawDeps from './data/raw-deps.json' assert { type: 'json' } @@ -33,17 +19,17 @@ async function filterDeps() { const builtData = [] const batchSize = 500 let batchCounter = 0 - console.log(`${performance.now()}: Analysis BEGIN`) + console.log(`Beginning dependency analysis...`) for (const repo of rawDeps.all_public_dependent_repos) { try { - console.log(`${performance.now()}: Getting repo data...`) + console.log(`${repo.name}: Getting repo data...`) const repoData = await analyseRepo(repo) if (repoData) { builtData.push(repoData) batchCounter++ } - console.log(`${performance.now()}: Analysis of ${repo.name} complete`) + console.log(`${repo.name}: Analysis complete`) const index = rawDeps.all_public_dependent_repos.findIndex( (item) => item === repo @@ -71,231 +57,86 @@ async function filterDeps() { if (builtData.length > 0) { await writeBatchToFiles(builtData) } - console.log(`${performance.now()}: We're done!`) + console.log(`We're done!`) } async function analyseRepo(repo) { - // Output data columns const repoOwner = repo.owner const repoName = repo.repo_name - let builtByGovernment = false - let indirectDependency = false - let isPrototype = false - let frontendVersion = null - let lockfileType = null - let versionDoubt = false - let couldntAccess = false - let lastUpdated = null - let repoCreated = null - let parentDependency = null + const repoData = new RepoData(repoOwner, repoName, governmentServiceOwners) try { - if (checkDenyList(repoName, repoOwner)) { - console.log( - `${performance.now()}: ${ - repo.name - } is on the 'deny' list and will not be processed` - ) - } - - builtByGovernment = checkServiceOwner(repoOwner) - if (builtByGovernment) { - console.log( - `${performance.now()}: ${repo.name} looks like a GOV.UK service.` - ) - } else { - console.log( - `${performance.now()}: ${ - repo.name - } looks like it ISN'T a GOV.UK service. This has been noted.` - ) + if (repoData.checkDenyList(denyList)) { + repoData.log(`on Deny List. Will not be processed.`) + return null } + repoData.log(`analyzing...`) - // Get repo data - const repoMetaData = await getRepoMetaData(repoOwner, repoName) - if (repoMetaData) { - lastUpdated = repoMetaData.data.pushed_at - repoCreated = repoMetaData.data.created_at - } - - const latestCommit = await getLatestCommit(repoOwner, repoName) - const repoTree = await getRepoTree(repoOwner, repoName, latestCommit.sha) - if (!repoTree.data.tree.find((file) => file.path == 'package.json')) { - indirectDependency = true - throw new NoPackageJsonError() - } + await repoData.fetchAndValidateMetaData() + repoData.log(`metadata fetched and validated.`) + await repoData.fetchAndValidateRepoTree() + repoData.log(`tree fetched and validated.`) - // Handle Package.json - // TODO: account for multiple package files - if (repoTree.data.tree.find((file) => file.path == 'package-lock.json')) { - lockfileType = 'package-lock.json' - } else if (repoTree.data.tree.find((file) => file.path == 'yarn.lock')) { - lockfileType = 'yarn.lock' + if (repoData.builtByGovernment) { + repoData.log(`looks like a GOV.UK service.`) + } else { + repoData.log(`looks like it ISN'T a GOV.UK service. This has been noted.`) } - const packageFile = await getFileContent( - repoOwner, - repoName, - 'package.json' - ) + const packageFile = await repoData.getRepoFileContent('package.json') const packageObject = JSON.parse(packageFile.data) - if (!('dependencies' in packageObject)) { - indirectDependency = true - throw new CouldntReadPackageError() - } - // Prototype checking - isPrototype = - repoTree.data.tree.find((file) => file.path == 'lib/usage_data.js') != - undefined || 'govuk-prototype-kit' in packageObject.dependencies - if (isPrototype) { - console.log( - `${performance.now()}: ${ - repo.name - } looks like an instance of the prototype kit. This has been noted.` - ) + if (await repoData.checkPrototype(packageObject)) { + repoData.log(`looks like an instance of the prototype kit.`) + repoData.isPrototype = true } // Handle indirect dependencies - if (!('govuk-frontend' in packageObject.dependencies)) { - indirectDependency = true - throw new IndirectDependencyError() - // TODO: Create a findIndirectDependencies function, add an array of the parents to the output column + if ( + !('dependencies' in packageObject) || + !('govuk-frontend' in packageObject.dependencies) + ) { + repoData.indirectDependency = true + repoData.log(`govuk-frontend is not a direct dependency.`) } - frontendVersion = packageObject.dependencies['govuk-frontend'] - console.log( - `${performance.now()}: ${ - repo.name - } is using GOV.UK Frontend version ${frontendVersion}` - ) - // TODO: Since we only search the Packagelock file if we find a frontend version - // we don't need to do anything but search for the `node_modules/govuk-frontend` entry - // in the getExactFrontendVersion function. - // If however, we don't find govuk-frontend in the dependencies, then we have an indirect dependency - // and we should search the lockfile for the govuk-frontend sub-dependencies - if (frontendVersion.includes('^') || frontendVersion.includes('~')) { - frontendVersion = await getExactFrontendVersion( - repoOwner, - repoName, - frontendVersion, - lockfileType, - parentDependency - ) - versionDoubt = - frontendVersion.includes('^') || frontendVersion.includes('~') + if (!repoData.indirectDependency) { + repoData.frontendVersion = packageObject.dependencies['govuk-frontend'] + repoData.log(`using GOV.UK Frontend version ${repoData.frontendVersion}`) + } + + if ( + !!repoData.frontendVersion?.startsWith('^') || + !!repoData.frontendVersion?.startsWith('~') || + repoData.indirectDependency + ) { + repoData.log(`searching for version in lockfile`) + repoData.versionDoubt = true + // @TODO: Get lockfile type here and log it. Pass to next function + repoData.frontendVersion = await repoData.getVersionFromLockfile() } } catch (error) { + repoData.errorThrown = error.toString() handleError(error, repoName) if (error instanceof RequestError) { - couldntAccess = true - versionDoubt = true + repoData.couldntAccess = true + repoData.versionDoubt = true } } - return { - repoOwner, - repoName, - couldntAccess, - frontendVersion, - versionDoubt, - builtByGovernment, - indirectDependency, - isPrototype, - lastUpdated, - repoCreated, - parentDependency, - } -} - -async function getExactFrontendVersion( - repoOwner, - repoName, - frontendVersion, - lockfileType, - parentDependency -) { - try { - if (lockfileType === 'package-lock.json') { - const packageLockFile = await getFileContent( - repoOwner, - repoName, - 'package-lock.json' - ) - const versionAndParent = await getFrontendVersionFromPackageLock( - packageLockFile.data - ) - // eslint-disable-next-line no-unused-vars - parentDependency = versionAndParent.parent - return versionAndParent.version || frontendVersion - } else if (lockfileType === 'yarn.lock') { - const yarnLockFile = await getFileContent( - repoOwner, - repoName, - 'yarn.lock' - ) - const yarnLockObject = yarnLock.default.parse(yarnLockFile.data) - return ( - yarnLockObject.object[`govuk-frontend@${frontendVersion}`]?.version || - frontendVersion - ) - } - } catch (error) { - console.log('There was a problem with processing the lockfile:', error) - } - return frontendVersion.replace('^', '').replace('~', '') -} - -// TODO: Streaming is probably overkill. -async function getFrontendVersionFromPackageLock(packageLockText) { - const stream = Readable.from([packageLockText]) - - // Parse top-level keys to track parents - const parser = JSONStream.parse('*') - - return new Promise((resolve, reject) => { - let result = { version: null, parent: null } - - stream.pipe(parser).pipe( - es - .mapSync((data) => { - Object.entries(data).forEach(([parentKey, value]) => { - if (parentKey === 'node_modules/govuk-frontend') { - console.log( - `${performance.now()}: Found the node_modules/govuk-frontend package entry, version ${ - data[parentKey].version - }` - ) - result = { version: data[parentKey].version, parent: null } - } else if (value.dependencies?.['govuk-frontend']) { - if (parentKey) { - console.log( - `${performance.now()}: Found govuk-frontend as a dependency of: ${parentKey}. This has been noted.` - ) - } - result = { - version: value.dependencies['govuk-frontend'].version, - parent: parentKey, - } - } - }) - }) - .on('end', () => resolve(result)) - .on('error', reject) - ) - }) + return repoData.getResult() } async function writeBatchToFiles(builtData) { // Write JSON file - await writeFileSync( + await appendFileSync( `data/${yyyymmdd}-${timestamp}-filtered-data.json`, JSON.stringify(builtData, null, 2) ) // Write CSV file const csv = json2csv(builtData) - await writeFileSync(`data/${yyyymmdd}-${timestamp}-filtered-data.csv`, csv) - console.log(`${performance.now()}: Data file updated with batch of entries`) + await appendFileSync(`data/${yyyymmdd}-${timestamp}-filtered-data.csv`, csv) + console.log(`Data file updated with batch of entries`) } filterDeps() diff --git a/helpers/check-deny-list.mjs b/helpers/check-deny-list.mjs deleted file mode 100644 index ed3282b4..00000000 --- a/helpers/check-deny-list.mjs +++ /dev/null @@ -1,3 +0,0 @@ -import denyList from './data/deny-list.json' assert {type: 'json'} - -export default (name, owner) => denyList.some(item => name == item.name && owner == item.owner) diff --git a/helpers/check-service-owner.mjs b/helpers/check-service-owner.mjs deleted file mode 100644 index 09a6ed2b..00000000 --- a/helpers/check-service-owner.mjs +++ /dev/null @@ -1,3 +0,0 @@ -import serviceOwners from './data/service-owners.json' assert {type: 'json'} - -export default (owner) => serviceOwners.includes(owner) diff --git a/helpers/error-handling.mjs b/helpers/error-handling.mjs index e14e8a93..2f106b6b 100644 --- a/helpers/error-handling.mjs +++ b/helpers/error-handling.mjs @@ -1,8 +1,9 @@ import { RequestError } from 'octokit' export class NoPackageJsonError extends Error {} -export class CouldntReadPackageError extends Error {} +export class NoDirectDependenciesError extends Error {} export class IndirectDependencyError extends Error {} +export class NoDataError extends Error {} /** * Logs errors @@ -19,11 +20,13 @@ export function handleError(error, repoName) { error.message }` ) + } else if (error instanceof NoDataError) { + console.log(`${performance.now()}: Couldn't fetch data for ${repoName}.`) } else if (error instanceof NoPackageJsonError) { console.log( - `${performance.now()}: ${repoName} doesn't have a package.json at its project root. Assuming indirect usage of GOV.UK Frontend.` + `${performance.now()}: ${repoName} doesn't have a package.json at its project root. This has been noted.` ) - } else if (error instanceof CouldntReadPackageError) { + } else if (error instanceof NoDirectDependenciesError) { console.log( `${performance.now()}: Couldn't find a direct dependencies list for ${repoName}. Assuming indirect usage of GOV.UK Frontend.` ) diff --git a/helpers/repo-data.mjs b/helpers/repo-data.mjs new file mode 100644 index 00000000..f60771ee --- /dev/null +++ b/helpers/repo-data.mjs @@ -0,0 +1,153 @@ +import { IndirectDependencyError } from './error-handling.mjs' +import { + getFileContent, + getLatestCommit, + getRepoMetaData, + getRepoTree, +} from './octokit.mjs' +import * as yarnLock from '@yarnpkg/lockfile' + +export class RepoData { + constructor(repoOwner, repoName, serviceOwners = []) { + this.repoOwner = repoOwner + this.repoName = repoName + this.couldntAccess = false + this.frontendVersion = null + this.versionDoubt = false + this.builtByGovernment = serviceOwners.includes(this.repoOwner) + this.indirectDependency = false + this.isPrototype = false + this.lastUpdated = null + this.repoCreated = null + this.parentDependency = null + this.errorThrown = null + this.repoTree = null + } + + checkDenyList(denyList) { + return denyList.some( + (item) => this.repoOwner === item.owner && this.repoName === item.name + ) + } + + async fetchAndValidateMetaData() { + const repoMetaData = await getRepoMetaData(this.repoOwner, this.repoName) + if (repoMetaData) { + this.lastUpdated = repoMetaData.data.pushed_at + this.repoCreated = repoMetaData.data.created_at + } + + // Some repos won't have a pushed_at + if (!this.repoCreated) { + throw new Error(`${this.repoName}: Couldn't fetch metadata`) + } + } + + async fetchAndValidateRepoTree() { + const latestCommitSha = await this.getLatestCommitSha() + this.repoTree = await getRepoTree( + this.repoOwner, + this.repoName, + latestCommitSha + ) + if (!this.repoTree) { + throw new Error(`${this.repoName}: Couldn't fetch git tree`) + } + } + + async getLatestCommitSha() { + const latestCommit = await getLatestCommit(this.repoOwner, this.repoName) + return latestCommit?.sha + } + + async checkPrototype(packageObject) { + return ( + this.repoTree.data.tree.some( + (file) => file.path == 'lib/usage_data.js' + ) || + (packageObject.dependencies && + 'govuk-prototype-kit' in packageObject.dependencies) + ) + } + + async getRepoFileContent(filePath) { + return await getFileContent(this.repoOwner, this.repoName, filePath) + } + + checkFileExists(filePath) { + return this.repoTree.data.tree.some((file) => file.path == filePath) + } + + log(message) { + console.log(`${this.repoOwner}/${this.repoName}: ${message}`) + } + + async getVersionFromLockfile() { + let lockfileType + if (this.checkFileExists('package-lock.json')) { + lockfileType = 'package-lock.json' + } else if (this.checkFileExists('yarn.lock')) { + lockfileType = 'yarn.lock' + } else { + // @TODO: support some package files - ruby (for GOV.UK) and maybe python? + throw new IndirectDependencyError( + `${this.repoName}: Couldn't find a supported lockfile` + ) + } + + const lockfile = await this.getRepoFileContent(lockfileType) + + if (lockfileType == 'package-lock.json') { + const lockfileObject = JSON.parse(lockfile.data) + if (this.frontendVersion) { + // If we found an ambiguous frontend version in the package.json file, + // all we have to do is get the package version from the lockfile + const packageVersion = + lockfileObject.packages?.['node_modules/govuk-frontend']?.version || + lockfileObject.dependencies?.['node_modules/govuk-frontend']?.version + if (packageVersion) { + this.frontendVersion = packageVersion + } + } else { + const deps = [] + // If we didn't find a frontend version in the package.json file, + // we have to search the lockfile for the govuk-frontend entries + for (const [packageName, packageData] of Object.entries({ + ...(lockfileObject.packages || {}), + ...(lockfileObject.dependencies || {}), + })) { + if (packageData.dependencies?.['govuk-frontend']) { + deps.push({ + parent: packageName, + version: packageData.dependencies['govuk-frontend'].version, + }) + } + } + this.parentDependency = deps + } + } else if (lockfileType === 'yarn.lock') { + const yarnLockObject = yarnLock.default.parse(lockfile.data) + this.frontendVersion = + yarnLockObject.object[`govuk-frontend@${this.frontendVersion}`] + ?.version || this.frontendVersion + } + return this.frontendVersion + } + + getResult() { + return { + repoOwner: this.repoOwner, + repoName: this.repoName, + couldntAccess: this.couldntAccess, + frontendVersion: this.frontendVersion, + versionDoubt: this.versionDoubt, + builtByGovernment: this.builtByGovernment, + indirectDependency: this.indirectDependency, + isPrototype: this.isPrototype, + lastUpdated: this.lastUpdated, + repoCreated: this.repoCreated, + parentDependency: this.parentDependency, + errorThrown: this.errorThrown, + } + } +} From c4eb6d6106e742d5ab5e958b87457e0164a33a4f Mon Sep 17 00:00:00 2001 From: Brett Kyle Date: Sat, 28 Dec 2024 22:07:21 +0000 Subject: [PATCH 09/20] Add known dependents file The idea is that if we find one of these packages as a direct dependency, we can infer govuk-frontend's version. This would rely on a function which checks which package manager we're dealing with in the first instance, and therefore which files to check for dependencies. We could include manual ports here, and potentially run separate dependents data for them to get an idea of who's using the ports that don't use our package underlying and how much of a market there is. --- .github/workflows/update.yml | 1 + build-filtered-data.mjs | 4 +- helpers/data/known-dependents.mjs | 13 ++++ helpers/error-handling.mjs | 40 ---------- helpers/octokit.mjs | 77 ++++++++----------- helpers/repo-data.mjs | 121 +++++++++++++++++++++++++++--- 6 files changed, 157 insertions(+), 99 deletions(-) create mode 100644 helpers/data/known-dependents.mjs delete mode 100644 helpers/error-handling.mjs diff --git a/.github/workflows/update.yml b/.github/workflows/update.yml index 0b69ff9e..cdfa6456 100644 --- a/.github/workflows/update.yml +++ b/.github/workflows/update.yml @@ -46,6 +46,7 @@ jobs: # gdi was built as a documentation tool. This is especially true of the # github action which doesn't give us the option to output this data as # something like json which we can programmatically interogate. + # TODO: Run a similar script for our known manual ports as well - name: Pipe our deps to a json file run: npm -v # run: github-dependents-info --repo alphagov/govuk-frontend --sort stars --json > ./data/raw-deps.json diff --git a/build-filtered-data.mjs b/build-filtered-data.mjs index 25e392d9..38c6526d 100644 --- a/build-filtered-data.mjs +++ b/build-filtered-data.mjs @@ -4,7 +4,6 @@ import { RequestError } from 'octokit' import denyList from './helpers/data/deny-list.json' assert { type: 'json' } import governmentServiceOwners from './helpers/data/service-owners.json' assert { type: 'json' } -import { handleError } from './helpers/error-handling.mjs' import { getRemainingRateLimit } from './helpers/octokit.mjs' import { RepoData } from './helpers/repo-data.mjs' @@ -73,7 +72,6 @@ async function analyseRepo(repo) { repoData.log(`analyzing...`) await repoData.fetchAndValidateMetaData() - repoData.log(`metadata fetched and validated.`) await repoData.fetchAndValidateRepoTree() repoData.log(`tree fetched and validated.`) @@ -117,7 +115,7 @@ async function analyseRepo(repo) { } } catch (error) { repoData.errorThrown = error.toString() - handleError(error, repoName) + repoData.handleError(error) if (error instanceof RequestError) { repoData.couldntAccess = true repoData.versionDoubt = true diff --git a/helpers/data/known-dependents.mjs b/helpers/data/known-dependents.mjs new file mode 100644 index 00000000..5ee6ab4d --- /dev/null +++ b/helpers/data/known-dependents.mjs @@ -0,0 +1,13 @@ +export default [ + { + package: 'govuk_publishing_components', + lang: 'ruby', + manager: 'gem', + }, + { + package: 'govuk-frontend-jinja', + lang: 'python', + manager: 'pip', + manual: true, // This package does not depend on govuk-frontend, but ports it + }, +] diff --git a/helpers/error-handling.mjs b/helpers/error-handling.mjs deleted file mode 100644 index 2f106b6b..00000000 --- a/helpers/error-handling.mjs +++ /dev/null @@ -1,40 +0,0 @@ -import { RequestError } from 'octokit' - -export class NoPackageJsonError extends Error {} -export class NoDirectDependenciesError extends Error {} -export class IndirectDependencyError extends Error {} -export class NoDataError extends Error {} - -/** - * Logs errors - * - * @param {Error} error - The error to log - * @param {string} repoName - the repo name - * - * @throws {Error} - If the error is not an expected type - */ -export function handleError(error, repoName) { - if (error instanceof RequestError) { - console.log( - `${performance.now()}: There was a problem accessing ${repoName}: ${ - error.message - }` - ) - } else if (error instanceof NoDataError) { - console.log(`${performance.now()}: Couldn't fetch data for ${repoName}.`) - } else if (error instanceof NoPackageJsonError) { - console.log( - `${performance.now()}: ${repoName} doesn't have a package.json at its project root. This has been noted.` - ) - } else if (error instanceof NoDirectDependenciesError) { - console.log( - `${performance.now()}: Couldn't find a direct dependencies list for ${repoName}. Assuming indirect usage of GOV.UK Frontend.` - ) - } else if (error instanceof IndirectDependencyError) { - console.log( - `${performance.now()}: ${repoName} doesn't list GOV.UK Frontend in its dependencies. Assuming indirect usage of GOV.UK Frontend.` - ) - } else { - throw error - } -} diff --git a/helpers/octokit.mjs b/helpers/octokit.mjs index 3809bb9e..a25b3a0e 100644 --- a/helpers/octokit.mjs +++ b/helpers/octokit.mjs @@ -1,8 +1,6 @@ import { Octokit } from 'octokit' import { throttling } from '@octokit/plugin-throttling' -import { handleError } from './error-handling.mjs' - const MyOctokit = Octokit.plugin(throttling) const octokit = new MyOctokit({ auth: process.env.GITHUB_AUTH_TOKEN, @@ -39,16 +37,13 @@ const octokit = new MyOctokit({ * @param {string} repoOwner - The owner of the repo * @param {string} repoName - The name of the repo * @returns {Promise>} + * @throws {RequestError} - If the request fails */ export async function getRepoMetaData(repoOwner, repoName) { - try { - return await octokit.rest.repos.get({ - owner: repoOwner, - repo: repoName, - }) - } catch (error) { - handleError(error, repoName) - } + return await octokit.rest.repos.get({ + owner: repoOwner, + repo: repoName, + }) } /** @@ -56,18 +51,15 @@ export async function getRepoMetaData(repoOwner, repoName) { * @param {string} repoOwner - The owner of the repo * @param {string} repoName - The name of the repo * @returns {Promise>} + * @throws {RequestError} - If the request fails */ export async function getLatestCommit(repoOwner, repoName) { - try { - const commits = await octokit.rest.repos.listCommits({ - owner: repoOwner, - repo: repoName, - per_page: 1, - }) - return commits.data[0] - } catch (error) { - handleError(error, repoName) - } + const commits = await octokit.rest.repos.listCommits({ + owner: repoOwner, + repo: repoName, + per_page: 1, + }) + return commits.data[0] } /** @@ -77,18 +69,15 @@ export async function getLatestCommit(repoOwner, repoName) { * @param {string} repoName - The name of the repo * @param {string} treeSha - The sha of the tree * @returns {Promise>} + * @throws {RequestError} - If the request fails */ export async function getRepoTree(repoOwner, repoName, treeSha) { - try { - return await octokit.rest.git.getTree({ - owner: repoOwner, - repo: repoName, - tree_sha: treeSha, - recursive: true, - }) - } catch (error) { - handleError(error, repoName) - } + return await octokit.rest.git.getTree({ + owner: repoOwner, + repo: repoName, + tree_sha: treeSha, + recursive: true, + }) } /** @@ -97,31 +86,25 @@ export async function getRepoTree(repoOwner, repoName, treeSha) { * @param {string} repoOwner - The owner of the repo * @param {string} repoName - The name of the repo * @param {string} filePath - The path to the file - * @returns {Promise>} + * @returns {Promise>} - the file content + * @throws {RequestError} - If the request fails */ export async function getFileContent(repoOwner, repoName, filePath) { - try { - return await octokit.rest.repos.getContent({ - owner: repoOwner, - repo: repoName, - path: filePath, - headers: { accept: 'application/vnd.github.raw+json' }, - }) - } catch (error) { - handleError(error, repoName) - } + return await octokit.rest.repos.getContent({ + owner: repoOwner, + repo: repoName, + path: filePath, + headers: { accept: 'application/vnd.github.raw+json' }, + }) } /** * Check rate limit * * @returns {number} - The number of remaining requests + * @throws {RequestError} - If the request fails */ export async function getRemainingRateLimit() { - try { - const rateLimit = await octokit.rest.rateLimit.get() - return rateLimit.data.rate.remaining - } catch (error) { - handleError(error, 'rate limit') - } + const rateLimit = await octokit.rest.rateLimit.get() + return rateLimit.data.rate.remaining } diff --git a/helpers/repo-data.mjs b/helpers/repo-data.mjs index f60771ee..ca43c450 100644 --- a/helpers/repo-data.mjs +++ b/helpers/repo-data.mjs @@ -1,4 +1,3 @@ -import { IndirectDependencyError } from './error-handling.mjs' import { getFileContent, getLatestCommit, @@ -6,8 +5,21 @@ import { getRepoTree, } from './octokit.mjs' import * as yarnLock from '@yarnpkg/lockfile' +import { RequestError } from 'octokit' + +class UnsupportedLockFileError extends Error {} +class NoMetaDataError extends Error {} +class NoRepoTreeError extends Error {} +class NoCommitsError extends Error {} export class RepoData { + /** + * Creates an instance of RepoData. + * + * @param {string} repoOwner - The owner of the repository. + * @param {string} repoName - The name of the repository. + * @param {Array} [serviceOwners=[]] - The list of service owners. + */ constructor(repoOwner, repoName, serviceOwners = []) { this.repoOwner = repoOwner this.repoName = repoName @@ -24,12 +36,25 @@ export class RepoData { this.repoTree = null } + /** + * Checks if repo on denyList + * + * @param {array} denyList - An array of objects with owner and name properties + * @returns {boolean} - Whether the repo is on the deny list + */ checkDenyList(denyList) { return denyList.some( (item) => this.repoOwner === item.owner && this.repoName === item.name ) } + /** + * Fetches and validates repo metadata + * + * @throws {NoMetaDataError} - If metadata could not be fetched + * @throws {RequestError} - If the request fails + * + */ async fetchAndValidateMetaData() { const repoMetaData = await getRepoMetaData(this.repoOwner, this.repoName) if (repoMetaData) { @@ -39,10 +64,17 @@ export class RepoData { // Some repos won't have a pushed_at if (!this.repoCreated) { - throw new Error(`${this.repoName}: Couldn't fetch metadata`) + throw new NoMetaDataError() } + this.log(`metadata fetched and validated.`) } + /** + * Fetches and validates repo tree + * + * @throws {NoRepoTreeError} - If the tree could not be fetched + * @throws {RequestError} - If the request fails + */ async fetchAndValidateRepoTree() { const latestCommitSha = await this.getLatestCommitSha() this.repoTree = await getRepoTree( @@ -51,15 +83,31 @@ export class RepoData { latestCommitSha ) if (!this.repoTree) { - throw new Error(`${this.repoName}: Couldn't fetch git tree`) + throw new NoRepoTreeError() } } + /** + * Gets the SHA of the latest commit + * + * @returns {string} - The SHA of the latest commit + * @throws {NoCommitsError} - If the repo has no commits + * @throws {RequestError} - If the request fails + */ async getLatestCommitSha() { const latestCommit = await getLatestCommit(this.repoOwner, this.repoName) - return latestCommit?.sha + if (latestCommit === undefined) { + throw new NoCommitsError() + } + return latestCommit.sha } + /** + * Checks if repo is a prototype + * + * @param {object} packageObject - package.json converted to an object + * @returns {boolean} - Whether the repo is a prototype + */ async checkPrototype(packageObject) { return ( this.repoTree.data.tree.some( @@ -70,18 +118,44 @@ export class RepoData { ) } + /** + * Gets the content of a file in the repo + * + * @param {string} filePath - The path to the file + * @returns {Promise>} - The file content + * @throws {RequestError} - If the request fails + */ async getRepoFileContent(filePath) { return await getFileContent(this.repoOwner, this.repoName, filePath) } + /** + * Checks if a file exists in the repo tree + * + * @param {string} filePath + * @returns {boolean} - Whether the file exists + */ checkFileExists(filePath) { return this.repoTree.data.tree.some((file) => file.path == filePath) } - log(message) { - console.log(`${this.repoOwner}/${this.repoName}: ${message}`) + /** + * Logs messages consistently + * + * @param {string} message - the message to log + * @param {[string]} type - type of message (error) + */ + log(message, type = '') { + const typeMsg = type === 'error' ? 'ERROR: ' : '' + console.log(`${this.repoOwner}/${this.repoName}: ${typeMsg} ${message}`) } + /** + * Gets the version of govuk-frontend from the lockfile + * @returns {string} - The version of govuk-frontend + * @throws {UnsupportedLockFileError} - If the lockfile is not supported + * @throws {RequestError} - If the request for the file data fails + */ async getVersionFromLockfile() { let lockfileType if (this.checkFileExists('package-lock.json')) { @@ -90,9 +164,7 @@ export class RepoData { lockfileType = 'yarn.lock' } else { // @TODO: support some package files - ruby (for GOV.UK) and maybe python? - throw new IndirectDependencyError( - `${this.repoName}: Couldn't find a supported lockfile` - ) + throw new UnsupportedLockFileError() } const lockfile = await this.getRepoFileContent(lockfileType) @@ -134,6 +206,37 @@ export class RepoData { return this.frontendVersion } + /** + * Logs errors + * + * @param {Error} error - The error to handle + * + * @throws {Error} - If the error is not an expected type + */ + handleError(error) { + if (error instanceof RequestError) { + this.log(`problem accessing repo: ${error.message}`, 'error') + } else if (error instanceof NoMetaDataError) { + this.log(`couldn't fetch metadata`, 'error') + } else if (error instanceof NoCommitsError) { + this.log(`couldn't fetch repo tree as repo has no commits`, 'error') + } else if (error instanceof NoRepoTreeError) { + this.log(`couldn't fetch repo tree`, 'error') + } else if (error instanceof UnsupportedLockFileError) { + this.log( + `couldn't find a supported lockfile. Skipping version check.`, + 'error' + ) + } else { + throw error + } + } + + /** + * Generates fields for output + * + * @returns {object} - The result of the analysis + */ getResult() { return { repoOwner: this.repoOwner, From 5c540001a2fcadc83e04fbf22731beec6b765910 Mon Sep 17 00:00:00 2001 From: Brett Kyle Date: Sun, 29 Dec 2024 16:44:21 +0000 Subject: [PATCH 10/20] Add unit tests --- helpers/repo-data.mjs | 13 +- helpers/repo-data.test.mjs | 193 ++++++ package-lock.json | 1289 +++++++++++++++++++++++++++++++++++- package.json | 6 +- vitest.config.js | 8 + 5 files changed, 1495 insertions(+), 14 deletions(-) create mode 100644 helpers/repo-data.test.mjs create mode 100644 vitest.config.js diff --git a/helpers/repo-data.mjs b/helpers/repo-data.mjs index ca43c450..4b6099f7 100644 --- a/helpers/repo-data.mjs +++ b/helpers/repo-data.mjs @@ -21,6 +21,14 @@ export class RepoData { * @param {Array} [serviceOwners=[]] - The list of service owners. */ constructor(repoOwner, repoName, serviceOwners = []) { + if (!repoOwner) { + this.log('repoOwner must be provided', 'error') + throw new Error('repoOwner must be provided') + } + if (!repoName) { + this.log('repoName must be provided', 'error') + throw new Error('repoName must be provided') + } this.repoOwner = repoOwner this.repoName = repoName this.couldntAccess = false @@ -97,6 +105,7 @@ export class RepoData { async getLatestCommitSha() { const latestCommit = await getLatestCommit(this.repoOwner, this.repoName) if (latestCommit === undefined) { + console.log('what') throw new NoCommitsError() } return latestCommit.sha @@ -146,8 +155,8 @@ export class RepoData { * @param {[string]} type - type of message (error) */ log(message, type = '') { - const typeMsg = type === 'error' ? 'ERROR: ' : '' - console.log(`${this.repoOwner}/${this.repoName}: ${typeMsg} ${message}`) + const typeMsg = type === 'error' ? ' ERROR:' : '' + console.log(`${this.repoOwner}/${this.repoName}:${typeMsg} ${message}`) } /** diff --git a/helpers/repo-data.test.mjs b/helpers/repo-data.test.mjs new file mode 100644 index 00000000..b8b009bd --- /dev/null +++ b/helpers/repo-data.test.mjs @@ -0,0 +1,193 @@ +import { describe, it, expect, vi } from 'vitest' +import { + RepoData, + NoMetaDataError, + NoRepoTreeError, + NoCommitsError, +} from './repo-data.mjs' +import { + getFileContent, + getLatestCommit, + getRepoMetaData, + getRepoTree, +} from './octokit.mjs' + +// Mock the octokit functions +vi.mock('./octokit.mjs', () => ({ + getFileContent: vi.fn(), + getLatestCommit: vi.fn(), + getRepoMetaData: vi.fn(), + getRepoTree: vi.fn(), +})) + +describe('RepoData', () => { + const repoOwner = 'test-owner' + const repoName = 'test-repo' + const serviceOwners = ['test-owner'] + + describe('constructor', () => { + it('should create an instance of RepoData', () => { + const repoData = new RepoData(repoOwner, repoName) + expect(repoData.repoOwner).toBe(repoOwner) + expect(repoData.repoName).toBe(repoName) + }) + + it('should throw an error if repoOwner is not provided', () => { + expect(() => new RepoData(null, repoName)).toThrow( + 'repoOwner must be provided' + ) + }) + + it('should throw an error if repoName is not provided', () => { + expect(() => new RepoData(repoOwner, null)).toThrow( + 'repoName must be provided' + ) + }) + }) + + describe('checkDenyList', () => { + it.each([ + { + denyList: [{ owner: 'test-owner', name: 'test-repo' }], + expected: true, + }, + { denyList: [], expected: false }, + ])( + 'should correctly check if repo is on deny list', + ({ denyList, expected }) => { + const repoData = new RepoData(repoOwner, repoName, serviceOwners) + const isOnDenyList = repoData.checkDenyList(denyList) + expect(isOnDenyList).toBe(expected) + } + ) + }) + + describe('fetchAndValidateMetaData', () => { + it('should throw a NoMetaDataError if metadata is missing', async () => { + const repoData = new RepoData(repoOwner, repoName, serviceOwners) + getRepoMetaData.mockResolvedValue({ + data: { + pushed_at: null, + created_at: null, + }, + }) + + await expect(repoData.fetchAndValidateMetaData()).rejects.toThrow( + NoMetaDataError + ) + }) + + it('should fetch and validate metadata', async () => { + const repoData = new RepoData(repoOwner, repoName, serviceOwners) + getRepoMetaData.mockResolvedValue({ + data: { + pushed_at: '2023-01-01T00:00:00Z', + created_at: '2022-01-01T00:00:00Z', + }, + }) + + await repoData.fetchAndValidateMetaData() + expect(repoData.lastUpdated).toBe('2023-01-01T00:00:00Z') + expect(repoData.repoCreated).toBe('2022-01-01T00:00:00Z') + }) + }) + + describe('fetchAndValidateRepoTree', () => { + it('should throw a NoRepoTreeError if metadata is missing', async () => { + const repoData = new RepoData(repoOwner, repoName, serviceOwners) + getRepoMetaData.mockResolvedValue({ + data: { + pushed_at: null, + created_at: null, + }, + }) + + await expect(repoData.fetchAndValidateMetaData()).rejects.toThrow( + NoRepoTreeError + ) + }) + + it('should fetch and validate repo tree', async () => { + const repoData = new RepoData(repoOwner, repoName, serviceOwners) + getLatestCommit.mockResolvedValue({ sha: 'test-sha' }) + getRepoTree.mockResolvedValue({ data: { tree: [] } }) + + await repoData.fetchAndValidateRepoTree() + expect(repoData.repoTree).toEqual({ data: { tree: [] } }) + }) + }) + + describe('getLatestCommitSha', () => { + it('should throw a NoCommitsError if the repo has no commits', async () => { + const repoData = new RepoData(repoOwner, repoName, serviceOwners) + getLatestCommit.mockResolvedValue(undefined) + + await expect(repoData.getLatestCommitSha()).rejects.toThrow( + NoCommitsError + ) + }) + + it('should get the SHA of the latest commit', async () => { + const repoData = new RepoData(repoOwner, repoName, serviceOwners) + getLatestCommit.mockResolvedValue({ sha: 'test-sha' }) + + const sha = await repoData.getLatestCommitSha() + expect(sha).toBe('test-sha') + }) + }) + + describe('checkPrototype', () => { + it('should assume prototype if usage_data.js present', async () => { + const repoData = new RepoData(repoOwner, repoName, serviceOwners) + repoData.repoTree = { data: { tree: [{ path: 'lib/usage_data.js' }] } } + const packageObject = { dependencies: { 'other-dependency': '1.0.0' } } + + const isPrototype = await repoData.checkPrototype(packageObject) + expect(isPrototype).toBe(true) + }) + + it('should assume prototype if dependency present', async () => { + const repoData = new RepoData(repoOwner, repoName, serviceOwners) + repoData.repoTree = { data: { tree: [{ path: 'other-file.js' }] } } + const packageObject = { dependencies: { 'govuk-prototype-kit': '1.0.0' } } + + const isPrototype = await repoData.checkPrototype(packageObject) + expect(isPrototype).toBe(true) + }) + + it('should not assume prototype kit if conditions not met', async () => { + const repoData = new RepoData(repoOwner, repoName, serviceOwners) + repoData.repoTree = { data: { tree: [{ path: 'other-file.js' }] } } + const packageObject = { dependencies: { 'other-dependency': '1.0.0' } } + + const isPrototype = await repoData.checkPrototype(packageObject) + expect(isPrototype).toBe(false) + }) + }) + + it('should get the content of a file in the repo', async () => { + const repoData = new RepoData(repoOwner, repoName, serviceOwners) + getFileContent.mockResolvedValue({ data: '{ test: "file content" }' }) + + const fileContent = await repoData.getRepoFileContent('package.json') + expect(fileContent).toEqual({ data: `{ test: "file content" }` }) + }) + + it('should check if a file exists in the repo tree', () => { + const repoData = new RepoData(repoOwner, repoName, serviceOwners) + repoData.repoTree = { data: { tree: [{ path: 'package.json' }] } } + + const fileExists = repoData.checkFileExists('package.json') + expect(fileExists).toBe(true) + }) + + it('should log messages consistently', () => { + const repoData = new RepoData(repoOwner, repoName, serviceOwners) + const consoleSpy = vi.spyOn(console, 'log') + + repoData.log('test message') + expect(consoleSpy).toHaveBeenCalledWith( + 'test-owner/test-repo: test message' + ) + }) +}) diff --git a/package-lock.json b/package-lock.json index ffb60d6b..8aaebbe5 100644 --- a/package-lock.json +++ b/package-lock.json @@ -17,7 +17,8 @@ "globals": "^15.13.0", "json-2-csv": "^5.5.7", "JSONStream": "^1.3.5", - "octokit": "^4.0.2" + "octokit": "^4.0.2", + "vitest": "^2.1.8" } }, "node_modules/@ampproject/remapping": { @@ -357,6 +358,397 @@ "node": ">=6.9.0" } }, + "node_modules/@esbuild/aix-ppc64": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/aix-ppc64/-/aix-ppc64-0.21.5.tgz", + "integrity": "sha512-1SDgH6ZSPTlggy1yI6+Dbkiz8xzpHJEVAlF/AM1tHPLsf5STom9rwtjE4hKAF20FfXXNTFqEYXyJNWh1GiZedQ==", + "cpu": [ + "ppc64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "aix" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/android-arm": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/android-arm/-/android-arm-0.21.5.tgz", + "integrity": "sha512-vCPvzSjpPHEi1siZdlvAlsPxXl7WbOVUBBAowWug4rJHb68Ox8KualB+1ocNvT5fjv6wpkX6o/iEpbDrf68zcg==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/android-arm64": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/android-arm64/-/android-arm64-0.21.5.tgz", + "integrity": "sha512-c0uX9VAUBQ7dTDCjq+wdyGLowMdtR/GoC2U5IYk/7D1H1JYC0qseD7+11iMP2mRLN9RcCMRcjC4YMclCzGwS/A==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/android-x64": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/android-x64/-/android-x64-0.21.5.tgz", + "integrity": "sha512-D7aPRUUNHRBwHxzxRvp856rjUHRFW1SdQATKXH2hqA0kAZb1hKmi02OpYRacl0TxIGz/ZmXWlbZgjwWYaCakTA==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/darwin-arm64": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/darwin-arm64/-/darwin-arm64-0.21.5.tgz", + "integrity": "sha512-DwqXqZyuk5AiWWf3UfLiRDJ5EDd49zg6O9wclZ7kUMv2WRFr4HKjXp/5t8JZ11QbQfUS6/cRCKGwYhtNAY88kQ==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/darwin-x64": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/darwin-x64/-/darwin-x64-0.21.5.tgz", + "integrity": "sha512-se/JjF8NlmKVG4kNIuyWMV/22ZaerB+qaSi5MdrXtd6R08kvs2qCN4C09miupktDitvh8jRFflwGFBQcxZRjbw==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/freebsd-arm64": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/freebsd-arm64/-/freebsd-arm64-0.21.5.tgz", + "integrity": "sha512-5JcRxxRDUJLX8JXp/wcBCy3pENnCgBR9bN6JsY4OmhfUtIHe3ZW0mawA7+RDAcMLrMIZaf03NlQiX9DGyB8h4g==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "freebsd" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/freebsd-x64": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/freebsd-x64/-/freebsd-x64-0.21.5.tgz", + "integrity": "sha512-J95kNBj1zkbMXtHVH29bBriQygMXqoVQOQYA+ISs0/2l3T9/kj42ow2mpqerRBxDJnmkUDCaQT/dfNXWX/ZZCQ==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "freebsd" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/linux-arm": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/linux-arm/-/linux-arm-0.21.5.tgz", + "integrity": "sha512-bPb5AHZtbeNGjCKVZ9UGqGwo8EUu4cLq68E95A53KlxAPRmUyYv2D6F0uUI65XisGOL1hBP5mTronbgo+0bFcA==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/linux-arm64": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/linux-arm64/-/linux-arm64-0.21.5.tgz", + "integrity": "sha512-ibKvmyYzKsBeX8d8I7MH/TMfWDXBF3db4qM6sy+7re0YXya+K1cem3on9XgdT2EQGMu4hQyZhan7TeQ8XkGp4Q==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/linux-ia32": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/linux-ia32/-/linux-ia32-0.21.5.tgz", + "integrity": "sha512-YvjXDqLRqPDl2dvRODYmmhz4rPeVKYvppfGYKSNGdyZkA01046pLWyRKKI3ax8fbJoK5QbxblURkwK/MWY18Tg==", + "cpu": [ + "ia32" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/linux-loong64": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/linux-loong64/-/linux-loong64-0.21.5.tgz", + "integrity": "sha512-uHf1BmMG8qEvzdrzAqg2SIG/02+4/DHB6a9Kbya0XDvwDEKCoC8ZRWI5JJvNdUjtciBGFQ5PuBlpEOXQj+JQSg==", + "cpu": [ + "loong64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/linux-mips64el": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/linux-mips64el/-/linux-mips64el-0.21.5.tgz", + "integrity": "sha512-IajOmO+KJK23bj52dFSNCMsz1QP1DqM6cwLUv3W1QwyxkyIWecfafnI555fvSGqEKwjMXVLokcV5ygHW5b3Jbg==", + "cpu": [ + "mips64el" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/linux-ppc64": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/linux-ppc64/-/linux-ppc64-0.21.5.tgz", + "integrity": "sha512-1hHV/Z4OEfMwpLO8rp7CvlhBDnjsC3CttJXIhBi+5Aj5r+MBvy4egg7wCbe//hSsT+RvDAG7s81tAvpL2XAE4w==", + "cpu": [ + "ppc64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/linux-riscv64": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/linux-riscv64/-/linux-riscv64-0.21.5.tgz", + "integrity": "sha512-2HdXDMd9GMgTGrPWnJzP2ALSokE/0O5HhTUvWIbD3YdjME8JwvSCnNGBnTThKGEB91OZhzrJ4qIIxk/SBmyDDA==", + "cpu": [ + "riscv64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/linux-s390x": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/linux-s390x/-/linux-s390x-0.21.5.tgz", + "integrity": "sha512-zus5sxzqBJD3eXxwvjN1yQkRepANgxE9lgOW2qLnmr8ikMTphkjgXu1HR01K4FJg8h1kEEDAqDcZQtbrRnB41A==", + "cpu": [ + "s390x" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/linux-x64": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/linux-x64/-/linux-x64-0.21.5.tgz", + "integrity": "sha512-1rYdTpyv03iycF1+BhzrzQJCdOuAOtaqHTWJZCWvijKD2N5Xu0TtVC8/+1faWqcP9iBCWOmjmhoH94dH82BxPQ==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/netbsd-x64": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/netbsd-x64/-/netbsd-x64-0.21.5.tgz", + "integrity": "sha512-Woi2MXzXjMULccIwMnLciyZH4nCIMpWQAs049KEeMvOcNADVxo0UBIQPfSmxB3CWKedngg7sWZdLvLczpe0tLg==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "netbsd" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/openbsd-x64": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/openbsd-x64/-/openbsd-x64-0.21.5.tgz", + "integrity": "sha512-HLNNw99xsvx12lFBUwoT8EVCsSvRNDVxNpjZ7bPn947b8gJPzeHWyNVhFsaerc0n3TsbOINvRP2byTZ5LKezow==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "openbsd" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/sunos-x64": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/sunos-x64/-/sunos-x64-0.21.5.tgz", + "integrity": "sha512-6+gjmFpfy0BHU5Tpptkuh8+uw3mnrvgs+dSPQXQOv3ekbordwnzTVEb4qnIvQcYXq6gzkyTnoZ9dZG+D4garKg==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "sunos" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/win32-arm64": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/win32-arm64/-/win32-arm64-0.21.5.tgz", + "integrity": "sha512-Z0gOTd75VvXqyq7nsl93zwahcTROgqvuAcYDUr+vOv8uHhNSKROyU961kgtCD1e95IqPKSQKH7tBTslnS3tA8A==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/win32-ia32": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/win32-ia32/-/win32-ia32-0.21.5.tgz", + "integrity": "sha512-SWXFF1CL2RVNMaVs+BBClwtfZSvDgtL//G/smwAc5oVK/UPu2Gu9tIaRgFmYFFKrmg3SyAjSrElf0TiJ1v8fYA==", + "cpu": [ + "ia32" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/win32-x64": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/win32-x64/-/win32-x64-0.21.5.tgz", + "integrity": "sha512-tQd/1efJuzPC6rCFwEvLtci/xNFcTZknmXs98FYDfGE4wP9ClFV98nyKrzJKVPMhdDnjzLhdUyMX4PsQAPjwIw==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=12" + } + }, "node_modules/@eslint-community/eslint-utils": { "version": "4.4.1", "resolved": "https://registry.npmjs.org/@eslint-community/eslint-utils/-/eslint-utils-4.4.1.tgz", @@ -606,8 +998,7 @@ "resolved": "https://registry.npmjs.org/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.5.0.tgz", "integrity": "sha512-gv3ZRaISU3fjPAgNsriBRqGWQL6quFx04YMPW/zD8XMLsU32mhCCbfbO6KZFLjvYpCZ8zyDEgqsgf+PwPaM7GQ==", "dev": true, - "license": "MIT", - "peer": true + "license": "MIT" }, "node_modules/@jridgewell/trace-mapping": { "version": "0.3.25", @@ -996,6 +1387,272 @@ "node": ">= 18" } }, + "node_modules/@rollup/rollup-android-arm-eabi": { + "version": "4.29.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm-eabi/-/rollup-android-arm-eabi-4.29.1.tgz", + "integrity": "sha512-ssKhA8RNltTZLpG6/QNkCSge+7mBQGUqJRisZ2MDQcEGaK93QESEgWK2iOpIDZ7k9zPVkG5AS3ksvD5ZWxmItw==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "android" + ] + }, + "node_modules/@rollup/rollup-android-arm64": { + "version": "4.29.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm64/-/rollup-android-arm64-4.29.1.tgz", + "integrity": "sha512-CaRfrV0cd+NIIcVVN/jx+hVLN+VRqnuzLRmfmlzpOzB87ajixsN/+9L5xNmkaUUvEbI5BmIKS+XTwXsHEb65Ew==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "android" + ] + }, + "node_modules/@rollup/rollup-darwin-arm64": { + "version": "4.29.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-arm64/-/rollup-darwin-arm64-4.29.1.tgz", + "integrity": "sha512-2ORr7T31Y0Mnk6qNuwtyNmy14MunTAMx06VAPI6/Ju52W10zk1i7i5U3vlDRWjhOI5quBcrvhkCHyF76bI7kEw==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ] + }, + "node_modules/@rollup/rollup-darwin-x64": { + "version": "4.29.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-x64/-/rollup-darwin-x64-4.29.1.tgz", + "integrity": "sha512-j/Ej1oanzPjmN0tirRd5K2/nncAhS9W6ICzgxV+9Y5ZsP0hiGhHJXZ2JQ53iSSjj8m6cRY6oB1GMzNn2EUt6Ng==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ] + }, + "node_modules/@rollup/rollup-freebsd-arm64": { + "version": "4.29.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-freebsd-arm64/-/rollup-freebsd-arm64-4.29.1.tgz", + "integrity": "sha512-91C//G6Dm/cv724tpt7nTyP+JdN12iqeXGFM1SqnljCmi5yTXriH7B1r8AD9dAZByHpKAumqP1Qy2vVNIdLZqw==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "freebsd" + ] + }, + "node_modules/@rollup/rollup-freebsd-x64": { + "version": "4.29.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-freebsd-x64/-/rollup-freebsd-x64-4.29.1.tgz", + "integrity": "sha512-hEioiEQ9Dec2nIRoeHUP6hr1PSkXzQaCUyqBDQ9I9ik4gCXQZjJMIVzoNLBRGet+hIUb3CISMh9KXuCcWVW/8w==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "freebsd" + ] + }, + "node_modules/@rollup/rollup-linux-arm-gnueabihf": { + "version": "4.29.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm-gnueabihf/-/rollup-linux-arm-gnueabihf-4.29.1.tgz", + "integrity": "sha512-Py5vFd5HWYN9zxBv3WMrLAXY3yYJ6Q/aVERoeUFwiDGiMOWsMs7FokXihSOaT/PMWUty/Pj60XDQndK3eAfE6A==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-arm-musleabihf": { + "version": "4.29.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm-musleabihf/-/rollup-linux-arm-musleabihf-4.29.1.tgz", + "integrity": "sha512-RiWpGgbayf7LUcuSNIbahr0ys2YnEERD4gYdISA06wa0i8RALrnzflh9Wxii7zQJEB2/Eh74dX4y/sHKLWp5uQ==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-arm64-gnu": { + "version": "4.29.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-gnu/-/rollup-linux-arm64-gnu-4.29.1.tgz", + "integrity": "sha512-Z80O+taYxTQITWMjm/YqNoe9d10OX6kDh8X5/rFCMuPqsKsSyDilvfg+vd3iXIqtfmp+cnfL1UrYirkaF8SBZA==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-arm64-musl": { + "version": "4.29.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-musl/-/rollup-linux-arm64-musl-4.29.1.tgz", + "integrity": "sha512-fOHRtF9gahwJk3QVp01a/GqS4hBEZCV1oKglVVq13kcK3NeVlS4BwIFzOHDbmKzt3i0OuHG4zfRP0YoG5OF/rA==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-loongarch64-gnu": { + "version": "4.29.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-loongarch64-gnu/-/rollup-linux-loongarch64-gnu-4.29.1.tgz", + "integrity": "sha512-5a7q3tnlbcg0OodyxcAdrrCxFi0DgXJSoOuidFUzHZ2GixZXQs6Tc3CHmlvqKAmOs5eRde+JJxeIf9DonkmYkw==", + "cpu": [ + "loong64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-powerpc64le-gnu": { + "version": "4.29.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-powerpc64le-gnu/-/rollup-linux-powerpc64le-gnu-4.29.1.tgz", + "integrity": "sha512-9b4Mg5Yfz6mRnlSPIdROcfw1BU22FQxmfjlp/CShWwO3LilKQuMISMTtAu/bxmmrE6A902W2cZJuzx8+gJ8e9w==", + "cpu": [ + "ppc64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-riscv64-gnu": { + "version": "4.29.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-riscv64-gnu/-/rollup-linux-riscv64-gnu-4.29.1.tgz", + "integrity": "sha512-G5pn0NChlbRM8OJWpJFMX4/i8OEU538uiSv0P6roZcbpe/WfhEO+AT8SHVKfp8qhDQzaz7Q+1/ixMy7hBRidnQ==", + "cpu": [ + "riscv64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-s390x-gnu": { + "version": "4.29.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-s390x-gnu/-/rollup-linux-s390x-gnu-4.29.1.tgz", + "integrity": "sha512-WM9lIkNdkhVwiArmLxFXpWndFGuOka4oJOZh8EP3Vb8q5lzdSCBuhjavJsw68Q9AKDGeOOIHYzYm4ZFvmWez5g==", + "cpu": [ + "s390x" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-x64-gnu": { + "version": "4.29.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-gnu/-/rollup-linux-x64-gnu-4.29.1.tgz", + "integrity": "sha512-87xYCwb0cPGZFoGiErT1eDcssByaLX4fc0z2nRM6eMtV9njAfEE6OW3UniAoDhX4Iq5xQVpE6qO9aJbCFumKYQ==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-x64-musl": { + "version": "4.29.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-musl/-/rollup-linux-x64-musl-4.29.1.tgz", + "integrity": "sha512-xufkSNppNOdVRCEC4WKvlR1FBDyqCSCpQeMMgv9ZyXqqtKBfkw1yfGMTUTs9Qsl6WQbJnsGboWCp7pJGkeMhKA==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-win32-arm64-msvc": { + "version": "4.29.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-arm64-msvc/-/rollup-win32-arm64-msvc-4.29.1.tgz", + "integrity": "sha512-F2OiJ42m77lSkizZQLuC+jiZ2cgueWQL5YC9tjo3AgaEw+KJmVxHGSyQfDUoYR9cci0lAywv2Clmckzulcq6ig==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ] + }, + "node_modules/@rollup/rollup-win32-ia32-msvc": { + "version": "4.29.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-ia32-msvc/-/rollup-win32-ia32-msvc-4.29.1.tgz", + "integrity": "sha512-rYRe5S0FcjlOBZQHgbTKNrqxCBUmgDJem/VQTCcTnA2KCabYSWQDrytOzX7avb79cAAweNmMUb/Zw18RNd4mng==", + "cpu": [ + "ia32" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ] + }, + "node_modules/@rollup/rollup-win32-x64-msvc": { + "version": "4.29.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-x64-msvc/-/rollup-win32-x64-msvc-4.29.1.tgz", + "integrity": "sha512-+10CMg9vt1MoHj6x1pxyjPSMjHTIlqs8/tBztXvPAx24SKs9jwVnKqHJumlH/IzhaPUaj3T6T6wfZr8okdXaIg==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ] + }, "node_modules/@types/aws-lambda": { "version": "8.10.138", "resolved": "https://registry.npmjs.org/@types/aws-lambda/-/aws-lambda-8.10.138.tgz", @@ -1009,12 +1666,125 @@ "dev": true, "license": "MIT" }, - "node_modules/@types/json-schema": { - "version": "7.0.15", - "resolved": "https://registry.npmjs.org/@types/json-schema/-/json-schema-7.0.15.tgz", - "integrity": "sha512-5+fP8P8MFNC+AyZCDxrB2pkZFPGzqQWUzpSeuuVLvm8VMcorNYavBqoFcxK8bQz4Qsbn4oUEEem4wDLfcysGHA==", + "node_modules/@types/json-schema": { + "version": "7.0.15", + "resolved": "https://registry.npmjs.org/@types/json-schema/-/json-schema-7.0.15.tgz", + "integrity": "sha512-5+fP8P8MFNC+AyZCDxrB2pkZFPGzqQWUzpSeuuVLvm8VMcorNYavBqoFcxK8bQz4Qsbn4oUEEem4wDLfcysGHA==", + "dev": true, + "license": "MIT" + }, + "node_modules/@vitest/expect": { + "version": "2.1.8", + "resolved": "https://registry.npmjs.org/@vitest/expect/-/expect-2.1.8.tgz", + "integrity": "sha512-8ytZ/fFHq2g4PJVAtDX57mayemKgDR6X3Oa2Foro+EygiOJHUXhCqBAAKQYYajZpFoIfvBCF1j6R6IYRSIUFuw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@vitest/spy": "2.1.8", + "@vitest/utils": "2.1.8", + "chai": "^5.1.2", + "tinyrainbow": "^1.2.0" + }, + "funding": { + "url": "https://opencollective.com/vitest" + } + }, + "node_modules/@vitest/mocker": { + "version": "2.1.8", + "resolved": "https://registry.npmjs.org/@vitest/mocker/-/mocker-2.1.8.tgz", + "integrity": "sha512-7guJ/47I6uqfttp33mgo6ga5Gr1VnL58rcqYKyShoRK9ebu8T5Rs6HN3s1NABiBeVTdWNrwUMcHH54uXZBN4zA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@vitest/spy": "2.1.8", + "estree-walker": "^3.0.3", + "magic-string": "^0.30.12" + }, + "funding": { + "url": "https://opencollective.com/vitest" + }, + "peerDependencies": { + "msw": "^2.4.9", + "vite": "^5.0.0" + }, + "peerDependenciesMeta": { + "msw": { + "optional": true + }, + "vite": { + "optional": true + } + } + }, + "node_modules/@vitest/pretty-format": { + "version": "2.1.8", + "resolved": "https://registry.npmjs.org/@vitest/pretty-format/-/pretty-format-2.1.8.tgz", + "integrity": "sha512-9HiSZ9zpqNLKlbIDRWOnAWqgcA7xu+8YxXSekhr0Ykab7PAYFkhkwoqVArPOtJhPmYeE2YHgKZlj3CP36z2AJQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "tinyrainbow": "^1.2.0" + }, + "funding": { + "url": "https://opencollective.com/vitest" + } + }, + "node_modules/@vitest/runner": { + "version": "2.1.8", + "resolved": "https://registry.npmjs.org/@vitest/runner/-/runner-2.1.8.tgz", + "integrity": "sha512-17ub8vQstRnRlIU5k50bG+QOMLHRhYPAna5tw8tYbj+jzjcspnwnwtPtiOlkuKC4+ixDPTuLZiqiWWQ2PSXHVg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@vitest/utils": "2.1.8", + "pathe": "^1.1.2" + }, + "funding": { + "url": "https://opencollective.com/vitest" + } + }, + "node_modules/@vitest/snapshot": { + "version": "2.1.8", + "resolved": "https://registry.npmjs.org/@vitest/snapshot/-/snapshot-2.1.8.tgz", + "integrity": "sha512-20T7xRFbmnkfcmgVEz+z3AU/3b0cEzZOt/zmnvZEctg64/QZbSDJEVm9fLnnlSi74KibmRsO9/Qabi+t0vCRPg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@vitest/pretty-format": "2.1.8", + "magic-string": "^0.30.12", + "pathe": "^1.1.2" + }, + "funding": { + "url": "https://opencollective.com/vitest" + } + }, + "node_modules/@vitest/spy": { + "version": "2.1.8", + "resolved": "https://registry.npmjs.org/@vitest/spy/-/spy-2.1.8.tgz", + "integrity": "sha512-5swjf2q95gXeYPevtW0BLk6H8+bPlMb4Vw/9Em4hFxDcaOxS+e0LOX4yqNxoHzMR2akEB2xfpnWUzkZokmgWDg==", + "dev": true, + "license": "MIT", + "dependencies": { + "tinyspy": "^3.0.2" + }, + "funding": { + "url": "https://opencollective.com/vitest" + } + }, + "node_modules/@vitest/utils": { + "version": "2.1.8", + "resolved": "https://registry.npmjs.org/@vitest/utils/-/utils-2.1.8.tgz", + "integrity": "sha512-dwSoui6djdwbfFmIgbIjX2ZhIoG7Ex/+xpxyiEgIGzjliY8xGkcpITKTlp6B4MgtGkF2ilvm97cPM96XZaAgcA==", "dev": true, - "license": "MIT" + "license": "MIT", + "dependencies": { + "@vitest/pretty-format": "2.1.8", + "loupe": "^3.1.2", + "tinyrainbow": "^1.2.0" + }, + "funding": { + "url": "https://opencollective.com/vitest" + } }, "node_modules/@yarnpkg/lockfile": { "version": "1.1.0", @@ -1101,6 +1871,16 @@ "dev": true, "license": "Python-2.0" }, + "node_modules/assertion-error": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/assertion-error/-/assertion-error-2.0.1.tgz", + "integrity": "sha512-Izi8RQcffqCeNVgFigKli1ssklIbpHnCYc6AknXGYoB6grJqyeby7jv12JUQgmTAnIDnbck1uxksT4dzN3PWBA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=12" + } + }, "node_modules/balanced-match": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.2.tgz", @@ -1165,6 +1945,16 @@ "node": "^6 || ^7 || ^8 || ^9 || ^10 || ^11 || ^12 || >=13.7" } }, + "node_modules/cac": { + "version": "6.7.14", + "resolved": "https://registry.npmjs.org/cac/-/cac-6.7.14.tgz", + "integrity": "sha512-b6Ilus+c3RrdDk+JhLKUAQfzzgLEPy6wcXqS7f/xe1EETvsDP6GORG7SFuOs6cID5YkqchW/LXZbX5bc8j7ZcQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, "node_modules/callsites": { "version": "3.1.0", "resolved": "https://registry.npmjs.org/callsites/-/callsites-3.1.0.tgz", @@ -1197,6 +1987,23 @@ "license": "CC-BY-4.0", "peer": true }, + "node_modules/chai": { + "version": "5.1.2", + "resolved": "https://registry.npmjs.org/chai/-/chai-5.1.2.tgz", + "integrity": "sha512-aGtmf24DW6MLHHG5gCx4zaI3uBq3KRtxeVs0DjFH6Z0rDNbsvTxFASFvdj79pxjxZ8/5u3PIiN3IwEIQkiiuPw==", + "dev": true, + "license": "MIT", + "dependencies": { + "assertion-error": "^2.0.1", + "check-error": "^2.1.1", + "deep-eql": "^5.0.1", + "loupe": "^3.1.0", + "pathval": "^2.0.0" + }, + "engines": { + "node": ">=12" + } + }, "node_modules/chalk": { "version": "4.1.2", "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", @@ -1214,6 +2021,16 @@ "url": "https://github.com/chalk/chalk?sponsor=1" } }, + "node_modules/check-error": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/check-error/-/check-error-2.1.1.tgz", + "integrity": "sha512-OAlb+T7V4Op9OwdkjmguYRqncdlx5JiofwOAUkmTF+jNdHwzTaTs4sRAGpzLF3oOz5xAyDGrPgeIDFQmDOTiJw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 16" + } + }, "node_modules/clean-stack": { "version": "5.2.0", "resolved": "https://registry.npmjs.org/clean-stack/-/clean-stack-5.2.0.tgz", @@ -1307,6 +2124,16 @@ "node": ">= 16" } }, + "node_modules/deep-eql": { + "version": "5.0.2", + "resolved": "https://registry.npmjs.org/deep-eql/-/deep-eql-5.0.2.tgz", + "integrity": "sha512-h5k/5U50IJJFpzfL6nO9jaaumfjO/f2NjK/oYB2Djzm4p9L+3T9qWpZqZ2hAbLPuuYq9wrU08WQyBTL5GbPk5Q==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6" + } + }, "node_modules/deep-is": { "version": "0.1.4", "resolved": "https://registry.npmjs.org/deep-is/-/deep-is-0.1.4.tgz", @@ -1339,6 +2166,52 @@ "license": "ISC", "peer": true }, + "node_modules/es-module-lexer": { + "version": "1.6.0", + "resolved": "https://registry.npmjs.org/es-module-lexer/-/es-module-lexer-1.6.0.tgz", + "integrity": "sha512-qqnD1yMU6tk/jnaMosogGySTZP8YtUgAffA9nMN+E/rjxcfRQ6IEk7IiozUjgxKoFHBGjTLnrHB/YC45r/59EQ==", + "dev": true, + "license": "MIT" + }, + "node_modules/esbuild": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/esbuild/-/esbuild-0.21.5.tgz", + "integrity": "sha512-mg3OPMV4hXywwpoDxu3Qda5xCKQi+vCTZq8S9J/EpkhB2HzKXq4SNFZE3+NK93JYxc8VMSep+lOUSC/RVKaBqw==", + "dev": true, + "hasInstallScript": true, + "license": "MIT", + "bin": { + "esbuild": "bin/esbuild" + }, + "engines": { + "node": ">=12" + }, + "optionalDependencies": { + "@esbuild/aix-ppc64": "0.21.5", + "@esbuild/android-arm": "0.21.5", + "@esbuild/android-arm64": "0.21.5", + "@esbuild/android-x64": "0.21.5", + "@esbuild/darwin-arm64": "0.21.5", + "@esbuild/darwin-x64": "0.21.5", + "@esbuild/freebsd-arm64": "0.21.5", + "@esbuild/freebsd-x64": "0.21.5", + "@esbuild/linux-arm": "0.21.5", + "@esbuild/linux-arm64": "0.21.5", + "@esbuild/linux-ia32": "0.21.5", + "@esbuild/linux-loong64": "0.21.5", + "@esbuild/linux-mips64el": "0.21.5", + "@esbuild/linux-ppc64": "0.21.5", + "@esbuild/linux-riscv64": "0.21.5", + "@esbuild/linux-s390x": "0.21.5", + "@esbuild/linux-x64": "0.21.5", + "@esbuild/netbsd-x64": "0.21.5", + "@esbuild/openbsd-x64": "0.21.5", + "@esbuild/sunos-x64": "0.21.5", + "@esbuild/win32-arm64": "0.21.5", + "@esbuild/win32-ia32": "0.21.5", + "@esbuild/win32-x64": "0.21.5" + } + }, "node_modules/escalade": { "version": "3.2.0", "resolved": "https://registry.npmjs.org/escalade/-/escalade-3.2.0.tgz", @@ -1519,6 +2392,16 @@ "node": ">=4.0" } }, + "node_modules/estree-walker": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/estree-walker/-/estree-walker-3.0.3.tgz", + "integrity": "sha512-7RUKfXgSMMkzt6ZuXmqapOurLGPPfgj6l9uRZ7lRGolvk0y2yocc35LdcxKC5PQZdn2DMqioAQ2NoWcrTKmm6g==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/estree": "^1.0.0" + } + }, "node_modules/esutils": { "version": "2.0.3", "resolved": "https://registry.npmjs.org/esutils/-/esutils-2.0.3.tgz", @@ -1545,6 +2428,16 @@ "through": "^2.3.8" } }, + "node_modules/expect-type": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/expect-type/-/expect-type-1.1.0.tgz", + "integrity": "sha512-bFi65yM+xZgk+u/KRIpekdSYkTB5W1pEf0Lt8Q8Msh7b+eQ7LXVtIB1Bkm4fvclDEL1b2CZkMhv2mOeF8tMdkA==", + "dev": true, + "license": "Apache-2.0", + "engines": { + "node": ">=12.0.0" + } + }, "node_modules/fast-deep-equal": { "version": "3.1.3", "resolved": "https://registry.npmjs.org/fast-deep-equal/-/fast-deep-equal-3.1.3.tgz", @@ -1624,6 +2517,21 @@ "dev": true, "license": "MIT" }, + "node_modules/fsevents": { + "version": "2.3.3", + "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.3.tgz", + "integrity": "sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw==", + "dev": true, + "hasInstallScript": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": "^8.16.0 || ^10.6.0 || >=11.0.0" + } + }, "node_modules/gensync": { "version": "1.0.0-beta.2", "resolved": "https://registry.npmjs.org/gensync/-/gensync-1.0.0-beta.2.tgz", @@ -1908,6 +2816,13 @@ "dev": true, "license": "MIT" }, + "node_modules/loupe": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/loupe/-/loupe-3.1.2.tgz", + "integrity": "sha512-23I4pFZHmAemUnz8WZXbYRSKYj801VDaNv9ETuMh7IrMc7VuVVSo+Z9iLE3ni30+U48iDWfi30d3twAXBYmnCg==", + "dev": true, + "license": "MIT" + }, "node_modules/lru-cache": { "version": "10.2.2", "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-10.2.2.tgz", @@ -1917,6 +2832,16 @@ "node": "14 || >=16.14" } }, + "node_modules/magic-string": { + "version": "0.30.17", + "resolved": "https://registry.npmjs.org/magic-string/-/magic-string-0.30.17.tgz", + "integrity": "sha512-sNPKHvyjVf7gyjwS4xGTaW/mCnF8wnjtifKBEhxfZ7E/S8tQ0rssrwGNn6q8JH/ohItJfSQp9mBtQYuTlH5QnA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jridgewell/sourcemap-codec": "^1.5.0" + } + }, "node_modules/map-stream": { "version": "0.0.7", "resolved": "https://registry.npmjs.org/map-stream/-/map-stream-0.0.7.tgz", @@ -1944,6 +2869,25 @@ "dev": true, "license": "MIT" }, + "node_modules/nanoid": { + "version": "3.3.8", + "resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.3.8.tgz", + "integrity": "sha512-WNLf5Sd8oZxOm+TzppcYk8gVOgP+l58xNy58D0nbUnOxOWRWvlcCV4kUF7ltmI6PsrLl/BgKEyS4mqsGChFN0w==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], + "license": "MIT", + "bin": { + "nanoid": "bin/nanoid.cjs" + }, + "engines": { + "node": "^10 || ^12 || ^13.7 || ^14 || >=15.0.1" + } + }, "node_modules/natural-compare": { "version": "1.4.0", "resolved": "https://registry.npmjs.org/natural-compare/-/natural-compare-1.4.0.tgz", @@ -2063,6 +3007,23 @@ "node": ">=8" } }, + "node_modules/pathe": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/pathe/-/pathe-1.1.2.tgz", + "integrity": "sha512-whLdWMYL2TwI08hn8/ZqAbrVemu0LNaNNJZX73O6qaIdCTfXutsLhMkjdENX0qhsQ9uIimo4/aQOmXkoon2nDQ==", + "dev": true, + "license": "MIT" + }, + "node_modules/pathval": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/pathval/-/pathval-2.0.0.tgz", + "integrity": "sha512-vE7JKRyES09KiunauX7nd2Q9/L7lhok4smP9RZTDeD4MVs72Dp2qNFVz39Nz5a0FVEW0BJR6C0DYrq6unoziZA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 14.16" + } + }, "node_modules/pause-stream": { "version": "0.0.11", "resolved": "https://registry.npmjs.org/pause-stream/-/pause-stream-0.0.11.tgz", @@ -2081,8 +3042,36 @@ "resolved": "https://registry.npmjs.org/picocolors/-/picocolors-1.1.1.tgz", "integrity": "sha512-xceH2snhtb5M9liqDsmEw56le376mTZkEX/jEb/RxNFyegNul7eNslCXP9FDj/Lcu0X8KEyMceP2ntpaHrDEVA==", "dev": true, - "license": "ISC", - "peer": true + "license": "ISC" + }, + "node_modules/postcss": { + "version": "8.4.49", + "resolved": "https://registry.npmjs.org/postcss/-/postcss-8.4.49.tgz", + "integrity": "sha512-OCVPnIObs4N29kxTjzLfUryOkvZEq+pf8jTF0lg8E7uETuWHA+v7j3c/xJmiqpX450191LlmZfUKkXxkTry7nA==", + "dev": true, + "funding": [ + { + "type": "opencollective", + "url": "https://opencollective.com/postcss/" + }, + { + "type": "tidelift", + "url": "https://tidelift.com/funding/github/npm/postcss" + }, + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], + "license": "MIT", + "dependencies": { + "nanoid": "^3.3.7", + "picocolors": "^1.1.1", + "source-map-js": "^1.2.1" + }, + "engines": { + "node": "^10 || ^12 || >=14" + } }, "node_modules/prelude-ls": { "version": "1.2.1", @@ -2114,6 +3103,45 @@ "node": ">=4" } }, + "node_modules/rollup": { + "version": "4.29.1", + "resolved": "https://registry.npmjs.org/rollup/-/rollup-4.29.1.tgz", + "integrity": "sha512-RaJ45M/kmJUzSWDs1Nnd5DdV4eerC98idtUOVr6FfKcgxqvjwHmxc5upLF9qZU9EpsVzzhleFahrT3shLuJzIw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/estree": "1.0.6" + }, + "bin": { + "rollup": "dist/bin/rollup" + }, + "engines": { + "node": ">=18.0.0", + "npm": ">=8.0.0" + }, + "optionalDependencies": { + "@rollup/rollup-android-arm-eabi": "4.29.1", + "@rollup/rollup-android-arm64": "4.29.1", + "@rollup/rollup-darwin-arm64": "4.29.1", + "@rollup/rollup-darwin-x64": "4.29.1", + "@rollup/rollup-freebsd-arm64": "4.29.1", + "@rollup/rollup-freebsd-x64": "4.29.1", + "@rollup/rollup-linux-arm-gnueabihf": "4.29.1", + "@rollup/rollup-linux-arm-musleabihf": "4.29.1", + "@rollup/rollup-linux-arm64-gnu": "4.29.1", + "@rollup/rollup-linux-arm64-musl": "4.29.1", + "@rollup/rollup-linux-loongarch64-gnu": "4.29.1", + "@rollup/rollup-linux-powerpc64le-gnu": "4.29.1", + "@rollup/rollup-linux-riscv64-gnu": "4.29.1", + "@rollup/rollup-linux-s390x-gnu": "4.29.1", + "@rollup/rollup-linux-x64-gnu": "4.29.1", + "@rollup/rollup-linux-x64-musl": "4.29.1", + "@rollup/rollup-win32-arm64-msvc": "4.29.1", + "@rollup/rollup-win32-ia32-msvc": "4.29.1", + "@rollup/rollup-win32-x64-msvc": "4.29.1", + "fsevents": "~2.3.2" + } + }, "node_modules/semver": { "version": "6.3.1", "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.1.tgz", @@ -2147,6 +3175,23 @@ "node": ">=8" } }, + "node_modules/siginfo": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/siginfo/-/siginfo-2.0.0.tgz", + "integrity": "sha512-ybx0WO1/8bSBLEWXZvEd7gMW3Sn3JFlW3TvX1nREbDLRNQNaeNN8WK0meBwPdAaOI7TtRRRJn/Es1zhrrCHu7g==", + "dev": true, + "license": "ISC" + }, + "node_modules/source-map-js": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/source-map-js/-/source-map-js-1.2.1.tgz", + "integrity": "sha512-UXWMKhLOwVKb728IUtQPXxfYU+usdybtUrK/8uGE8CQMvrhOpwvzDBwj0QhSL7MQc7vIsISBG8VQ8+IDQxpfQA==", + "dev": true, + "license": "BSD-3-Clause", + "engines": { + "node": ">=0.10.0" + } + }, "node_modules/split": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/split/-/split-1.0.1.tgz", @@ -2160,6 +3205,20 @@ "node": "*" } }, + "node_modules/stackback": { + "version": "0.0.2", + "resolved": "https://registry.npmjs.org/stackback/-/stackback-0.0.2.tgz", + "integrity": "sha512-1XMJE5fQo1jGH6Y/7ebnwPOBEkIEnT4QF32d5R1+VXdXveM0IBMJt8zfaxX1P3QhVwrYe+576+jkANtSS2mBbw==", + "dev": true, + "license": "MIT" + }, + "node_modules/std-env": { + "version": "3.8.0", + "resolved": "https://registry.npmjs.org/std-env/-/std-env-3.8.0.tgz", + "integrity": "sha512-Bc3YwwCB+OzldMxOXJIIvC6cPRWr/LxOp48CdQTOkPyk/t4JWWJbrilwBd7RJzKV8QW7tJkcgAmeuLLJugl5/w==", + "dev": true, + "license": "MIT" + }, "node_modules/stream-combiner": { "version": "0.2.2", "resolved": "https://registry.npmjs.org/stream-combiner/-/stream-combiner-0.2.2.tgz", @@ -2204,6 +3263,50 @@ "dev": true, "license": "MIT" }, + "node_modules/tinybench": { + "version": "2.9.0", + "resolved": "https://registry.npmjs.org/tinybench/-/tinybench-2.9.0.tgz", + "integrity": "sha512-0+DUvqWMValLmha6lr4kD8iAMK1HzV0/aKnCtWb9v9641TnP/MFb7Pc2bxoxQjTXAErryXVgUOfv2YqNllqGeg==", + "dev": true, + "license": "MIT" + }, + "node_modules/tinyexec": { + "version": "0.3.2", + "resolved": "https://registry.npmjs.org/tinyexec/-/tinyexec-0.3.2.tgz", + "integrity": "sha512-KQQR9yN7R5+OSwaK0XQoj22pwHoTlgYqmUscPYoknOoWCWfj/5/ABTMRi69FrKU5ffPVh5QcFikpWJI/P1ocHA==", + "dev": true, + "license": "MIT" + }, + "node_modules/tinypool": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/tinypool/-/tinypool-1.0.2.tgz", + "integrity": "sha512-al6n+QEANGFOMf/dmUMsuS5/r9B06uwlyNjZZql/zv8J7ybHCgoihBNORZCY2mzUuAnomQa2JdhyHKzZxPCrFA==", + "dev": true, + "license": "MIT", + "engines": { + "node": "^18.0.0 || >=20.0.0" + } + }, + "node_modules/tinyrainbow": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/tinyrainbow/-/tinyrainbow-1.2.0.tgz", + "integrity": "sha512-weEDEq7Z5eTHPDh4xjX789+fHfF+P8boiFB+0vbWzpbnbsEr/GRaohi/uMKxg8RZMXnl1ItAi/IUHWMsjDV7kQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=14.0.0" + } + }, + "node_modules/tinyspy": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/tinyspy/-/tinyspy-3.0.2.tgz", + "integrity": "sha512-n1cw8k1k0x4pgA2+9XrOkFydTerNcJ1zWCO5Nn9scWHTD+5tp8dghT2x1uduQePZTZgd3Tupf+x9BxJjeJi77Q==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=14.0.0" + } + }, "node_modules/type-check": { "version": "0.4.0", "resolved": "https://registry.npmjs.org/type-check/-/type-check-0.4.0.tgz", @@ -2271,6 +3374,155 @@ "punycode": "^2.1.0" } }, + "node_modules/vite": { + "version": "5.4.11", + "resolved": "https://registry.npmjs.org/vite/-/vite-5.4.11.tgz", + "integrity": "sha512-c7jFQRklXua0mTzneGW9QVyxFjUgwcihC4bXEtujIo2ouWCe1Ajt/amn2PCxYnhYfd5k09JX3SB7OYWFKYqj8Q==", + "dev": true, + "license": "MIT", + "dependencies": { + "esbuild": "^0.21.3", + "postcss": "^8.4.43", + "rollup": "^4.20.0" + }, + "bin": { + "vite": "bin/vite.js" + }, + "engines": { + "node": "^18.0.0 || >=20.0.0" + }, + "funding": { + "url": "https://github.com/vitejs/vite?sponsor=1" + }, + "optionalDependencies": { + "fsevents": "~2.3.3" + }, + "peerDependencies": { + "@types/node": "^18.0.0 || >=20.0.0", + "less": "*", + "lightningcss": "^1.21.0", + "sass": "*", + "sass-embedded": "*", + "stylus": "*", + "sugarss": "*", + "terser": "^5.4.0" + }, + "peerDependenciesMeta": { + "@types/node": { + "optional": true + }, + "less": { + "optional": true + }, + "lightningcss": { + "optional": true + }, + "sass": { + "optional": true + }, + "sass-embedded": { + "optional": true + }, + "stylus": { + "optional": true + }, + "sugarss": { + "optional": true + }, + "terser": { + "optional": true + } + } + }, + "node_modules/vite-node": { + "version": "2.1.8", + "resolved": "https://registry.npmjs.org/vite-node/-/vite-node-2.1.8.tgz", + "integrity": "sha512-uPAwSr57kYjAUux+8E2j0q0Fxpn8M9VoyfGiRI8Kfktz9NcYMCenwY5RnZxnF1WTu3TGiYipirIzacLL3VVGFg==", + "dev": true, + "license": "MIT", + "dependencies": { + "cac": "^6.7.14", + "debug": "^4.3.7", + "es-module-lexer": "^1.5.4", + "pathe": "^1.1.2", + "vite": "^5.0.0" + }, + "bin": { + "vite-node": "vite-node.mjs" + }, + "engines": { + "node": "^18.0.0 || >=20.0.0" + }, + "funding": { + "url": "https://opencollective.com/vitest" + } + }, + "node_modules/vitest": { + "version": "2.1.8", + "resolved": "https://registry.npmjs.org/vitest/-/vitest-2.1.8.tgz", + "integrity": "sha512-1vBKTZskHw/aosXqQUlVWWlGUxSJR8YtiyZDJAFeW2kPAeX6S3Sool0mjspO+kXLuxVWlEDDowBAeqeAQefqLQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@vitest/expect": "2.1.8", + "@vitest/mocker": "2.1.8", + "@vitest/pretty-format": "^2.1.8", + "@vitest/runner": "2.1.8", + "@vitest/snapshot": "2.1.8", + "@vitest/spy": "2.1.8", + "@vitest/utils": "2.1.8", + "chai": "^5.1.2", + "debug": "^4.3.7", + "expect-type": "^1.1.0", + "magic-string": "^0.30.12", + "pathe": "^1.1.2", + "std-env": "^3.8.0", + "tinybench": "^2.9.0", + "tinyexec": "^0.3.1", + "tinypool": "^1.0.1", + "tinyrainbow": "^1.2.0", + "vite": "^5.0.0", + "vite-node": "2.1.8", + "why-is-node-running": "^2.3.0" + }, + "bin": { + "vitest": "vitest.mjs" + }, + "engines": { + "node": "^18.0.0 || >=20.0.0" + }, + "funding": { + "url": "https://opencollective.com/vitest" + }, + "peerDependencies": { + "@edge-runtime/vm": "*", + "@types/node": "^18.0.0 || >=20.0.0", + "@vitest/browser": "2.1.8", + "@vitest/ui": "2.1.8", + "happy-dom": "*", + "jsdom": "*" + }, + "peerDependenciesMeta": { + "@edge-runtime/vm": { + "optional": true + }, + "@types/node": { + "optional": true + }, + "@vitest/browser": { + "optional": true + }, + "@vitest/ui": { + "optional": true + }, + "happy-dom": { + "optional": true + }, + "jsdom": { + "optional": true + } + } + }, "node_modules/which": { "version": "2.0.2", "resolved": "https://registry.npmjs.org/which/-/which-2.0.2.tgz", @@ -2287,6 +3539,23 @@ "node": ">= 8" } }, + "node_modules/why-is-node-running": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/why-is-node-running/-/why-is-node-running-2.3.0.tgz", + "integrity": "sha512-hUrmaWBdVDcxvYqnyh09zunKzROWjbZTiNy8dBEjkS7ehEDQibXJ7XvlmtbwuTclUiIyN+CyXQD4Vmko8fNm8w==", + "dev": true, + "license": "MIT", + "dependencies": { + "siginfo": "^2.0.0", + "stackback": "0.0.2" + }, + "bin": { + "why-is-node-running": "cli.js" + }, + "engines": { + "node": ">=8" + } + }, "node_modules/word-wrap": { "version": "1.2.5", "resolved": "https://registry.npmjs.org/word-wrap/-/word-wrap-1.2.5.tgz", diff --git a/package.json b/package.json index d1819cfc..c792260c 100644 --- a/package.json +++ b/package.json @@ -12,7 +12,8 @@ }, "scripts": { "build-filtered-data": "node build-filtered-data.mjs", - "lint": "eslint" + "lint": "eslint", + "test": "npm run lint && vitest" }, "homepage": "https://github.com/alphagov/design-system-github-stats#readme", "devDependencies": { @@ -26,6 +27,7 @@ "globals": "^15.13.0", "json-2-csv": "^5.5.7", "JSONStream": "^1.3.5", - "octokit": "^4.0.2" + "octokit": "^4.0.2", + "vitest": "^2.1.8" } } diff --git a/vitest.config.js b/vitest.config.js new file mode 100644 index 00000000..d87fc4a6 --- /dev/null +++ b/vitest.config.js @@ -0,0 +1,8 @@ +import { defineConfig } from 'vitest/config' + +export default defineConfig({ + test: { + globals: true, + environment: 'node', + }, +}) From 413a1fef86ff24bb4b12e2b9e0da9d1c70d1344f Mon Sep 17 00:00:00 2001 From: Brett Kyle Date: Mon, 30 Dec 2024 22:24:40 +0000 Subject: [PATCH 11/20] handle multiple package files? --- .github/workflows/tests.yml | 8 ++--- build-filtered-data.mjs | 69 ++++++++++++++++++++++-------------- build-filtered-data.test.mjs | 25 +++++++++++++ helpers/repo-data.mjs | 60 +++++++++++++++++++++++++------ helpers/repo-data.test.mjs | 48 +++++++++++++++++++++---- todo.md | 33 +++++++++++++++++ 6 files changed, 196 insertions(+), 47 deletions(-) create mode 100644 build-filtered-data.test.mjs create mode 100644 todo.md diff --git a/.github/workflows/tests.yml b/.github/workflows/tests.yml index d8fc21d3..2b0c9609 100644 --- a/.github/workflows/tests.yml +++ b/.github/workflows/tests.yml @@ -12,8 +12,8 @@ concurrency: cancel-in-progress: true jobs: - lint: - name: lint + test: + name: test runs-on: ubuntu-latest steps: @@ -30,5 +30,5 @@ jobs: - name: Install dependencies run: npm ci - - name: Run eslint - run: npm run lint + - name: Run linting and testing + run: npm run test diff --git a/build-filtered-data.mjs b/build-filtered-data.mjs index 38c6526d..4d60f239 100644 --- a/build-filtered-data.mjs +++ b/build-filtered-data.mjs @@ -59,7 +59,7 @@ async function filterDeps() { console.log(`We're done!`) } -async function analyseRepo(repo) { +export async function analyseRepo(repo) { const repoOwner = repo.owner const repoName = repo.repo_name const repoData = new RepoData(repoOwner, repoName, governmentServiceOwners) @@ -81,37 +81,52 @@ async function analyseRepo(repo) { repoData.log(`looks like it ISN'T a GOV.UK service. This has been noted.`) } - const packageFile = await repoData.getRepoFileContent('package.json') - const packageObject = JSON.parse(packageFile.data) + const packageFiles = await repoData.getAllFilesContent('package.json') + let packageObjects = [] + if (packageFiles.length === 0) { + repoData.log(`no package files found.`) + repoData.versionDoubt = true + } else { + packageObjects = packageFiles.map((file) => { + return { + content: JSON.parse(file.content), + path: file.path, + } + }) + repoData.log( + `${packageObjects.length} package file${ + packageObjects.length > 1 ? 's' : '' + } found.` + ) + } - if (await repoData.checkPrototype(packageObject)) { + // @TODO: several repos are failing silently somewhere after this log + + if (repoData.checkPrototype(packageObjects)) { repoData.log(`looks like an instance of the prototype kit.`) repoData.isPrototype = true } - - // Handle indirect dependencies - if ( - !('dependencies' in packageObject) || - !('govuk-frontend' in packageObject.dependencies) - ) { - repoData.indirectDependency = true + if (!repoData.checkDirectDependency(packageObjects)) { repoData.log(`govuk-frontend is not a direct dependency.`) - } - - if (!repoData.indirectDependency) { - repoData.frontendVersion = packageObject.dependencies['govuk-frontend'] - repoData.log(`using GOV.UK Frontend version ${repoData.frontendVersion}`) - } - - if ( - !!repoData.frontendVersion?.startsWith('^') || - !!repoData.frontendVersion?.startsWith('~') || - repoData.indirectDependency - ) { - repoData.log(`searching for version in lockfile`) - repoData.versionDoubt = true - // @TODO: Get lockfile type here and log it. Pass to next function - repoData.frontendVersion = await repoData.getVersionFromLockfile() + } else { + for (const versionData of repoData.frontendVersions) { + repoData.log( + `${versionData.packagePath} specifies GOV.UK Frontend version ${versionData.frontendVersion}` + ) + if ( + !!versionData.frontendVersion.startsWith('^') || + !!versionData.frontendVersion.startsWith('~') || + repoData.indirectDependency + ) { + repoData.log(`searching for version in lockfile`) + repoData.versionDoubt = true + // @TODO: Get lockfile type here and log it. Pass to next function + repoData.frontendVersion = await repoData.getVersionFromLockfile() + repoData.log( + `using GOV.UK Frontend version ${repoData.frontendVersion}` + ) + } + } } } catch (error) { repoData.errorThrown = error.toString() diff --git a/build-filtered-data.test.mjs b/build-filtered-data.test.mjs new file mode 100644 index 00000000..fa82af5f --- /dev/null +++ b/build-filtered-data.test.mjs @@ -0,0 +1,25 @@ +import { describe, it, expect, vi } from 'vitest' +import { analyseRepo } from './build-filtered-data.mjs' + +vi.mock('./helpers/repo-data.mjs', async (importOriginal) => { + const actual = await importOriginal() + return { + ...actual, + fetchAndValidateMetaData: vi.fn(), + fetchAndValidateRepoTree: vi.fn(), + getAllFilesContent: vi.fn(), + checkPrototype: vi.fn(), + getVersionFromLockfile: vi.fn(), + getResult: vi.fn(), + handleError: vi.fn(), + } +}) + +describe('analyseRepo', () => { + it('should return null if repo is on deny list', async () => { + const repo = { owner: 'alphagov', repo_name: 'govuk-frontend' } + + const result = await analyseRepo(repo) + expect(result).toBeNull() + }) +}) diff --git a/helpers/repo-data.mjs b/helpers/repo-data.mjs index 4b6099f7..57238075 100644 --- a/helpers/repo-data.mjs +++ b/helpers/repo-data.mjs @@ -42,6 +42,7 @@ export class RepoData { this.parentDependency = null this.errorThrown = null this.repoTree = null + this.frontendVersions = [] } /** @@ -105,7 +106,6 @@ export class RepoData { async getLatestCommitSha() { const latestCommit = await getLatestCommit(this.repoOwner, this.repoName) if (latestCommit === undefined) { - console.log('what') throw new NoCommitsError() } return latestCommit.sha @@ -114,17 +114,42 @@ export class RepoData { /** * Checks if repo is a prototype * - * @param {object} packageObject - package.json converted to an object + * @param {array} packageObjects - an array of packageObjects * @returns {boolean} - Whether the repo is a prototype */ - async checkPrototype(packageObject) { - return ( - this.repoTree.data.tree.some( - (file) => file.path == 'lib/usage_data.js' - ) || - (packageObject.dependencies && - 'govuk-prototype-kit' in packageObject.dependencies) - ) + checkPrototype(packageObjects) { + if ( + this.repoTree.data.tree.some((file) => file.path == 'lib/usage_data.js') + ) { + return true + } else if (packageObjects.length === 0) { + return false + } else { + for (const packageObject of packageObjects) { + if ( + packageObject.content.dependencies && + 'govuk-prototype-kit' in packageObject.content.dependencies + ) { + return true + } + } + } + return false + } + + checkDirectDependency(packageObjects) { + for (const packageObject of packageObjects) { + if ('govuk-frontend' in packageObject.content.dependencies) { + this.frontendVersions.push({ + packagePath: packageObject.path, + frontendVersion: packageObject.content.dependencies['govuk-frontend'], + }) + } + } + if (this.frontendVersions.length === 0) { + this.indirectDependency = true + } + return this.frontendVersions.length > 0 } /** @@ -138,6 +163,20 @@ export class RepoData { return await getFileContent(this.repoOwner, this.repoName, filePath) } + async getAllFilesContent(fileName) { + const files = this.repoTree.data.tree.filter( + (file) => + file.path.endsWith(fileName) && !file.path.includes('node_modules') + ) + const fileContents = await Promise.all( + files.map(async (file) => { + const content = await this.getRepoFileContent(file.path) + return { path: file.path, content: content.data } + }) + ) + return fileContents + } + /** * Checks if a file exists in the repo tree * @@ -252,6 +291,7 @@ export class RepoData { repoName: this.repoName, couldntAccess: this.couldntAccess, frontendVersion: this.frontendVersion, + directDependencyVersions: this.frontendVersions, versionDoubt: this.versionDoubt, builtByGovernment: this.builtByGovernment, indirectDependency: this.indirectDependency, diff --git a/helpers/repo-data.test.mjs b/helpers/repo-data.test.mjs index b8b009bd..f2c39d2f 100644 --- a/helpers/repo-data.test.mjs +++ b/helpers/repo-data.test.mjs @@ -140,31 +140,67 @@ describe('RepoData', () => { it('should assume prototype if usage_data.js present', async () => { const repoData = new RepoData(repoOwner, repoName, serviceOwners) repoData.repoTree = { data: { tree: [{ path: 'lib/usage_data.js' }] } } - const packageObject = { dependencies: { 'other-dependency': '1.0.0' } } + const packageObjects = [ + { + content: { dependencies: { 'other-dependency': '1.0.0' } }, + }, + ] - const isPrototype = await repoData.checkPrototype(packageObject) + const isPrototype = repoData.checkPrototype(packageObjects) expect(isPrototype).toBe(true) }) it('should assume prototype if dependency present', async () => { const repoData = new RepoData(repoOwner, repoName, serviceOwners) repoData.repoTree = { data: { tree: [{ path: 'other-file.js' }] } } - const packageObject = { dependencies: { 'govuk-prototype-kit': '1.0.0' } } + const packageObjects = [ + { + content: { dependencies: { 'govuk-prototype-kit': '1.0.0' } }, + }, + ] - const isPrototype = await repoData.checkPrototype(packageObject) + const isPrototype = repoData.checkPrototype(packageObjects) expect(isPrototype).toBe(true) }) it('should not assume prototype kit if conditions not met', async () => { const repoData = new RepoData(repoOwner, repoName, serviceOwners) repoData.repoTree = { data: { tree: [{ path: 'other-file.js' }] } } - const packageObject = { dependencies: { 'other-dependency': '1.0.0' } } + const packageObjects = [ + { + content: { dependencies: { 'other-dependency': '1.0.0' } }, + }, + ] - const isPrototype = await repoData.checkPrototype(packageObject) + const isPrototype = repoData.checkPrototype(packageObjects) expect(isPrototype).toBe(false) }) }) + describe('getAllFilesContent', () => { + it('should get the content of all files with a given name', async () => { + const repoData = new RepoData(repoOwner, repoName, serviceOwners) + repoData.repoTree = { + data: { + tree: [ + { path: 'package.json' }, + { path: 'src/package.json' }, + { path: 'lib/package.json' }, + { path: 'test/package.json' }, + ], + }, + } + getFileContent.mockResolvedValue({ data: '{ test: "file content" }' }) + const fileContents = await repoData.getAllFilesContent('package.json') + expect(fileContents).toEqual([ + { content: '{ test: "file content" }', path: 'package.json' }, + { content: '{ test: "file content" }', path: 'src/package.json' }, + { content: '{ test: "file content" }', path: 'lib/package.json' }, + { content: '{ test: "file content" }', path: 'test/package.json' }, + ]) + }) + }) + it('should get the content of a file in the repo', async () => { const repoData = new RepoData(repoOwner, repoName, serviceOwners) getFileContent.mockResolvedValue({ data: '{ test: "file content" }' }) diff --git a/todo.md b/todo.md new file mode 100644 index 00000000..49d698fc --- /dev/null +++ b/todo.md @@ -0,0 +1,33 @@ +# TODOs + +## Optimization + +- Investigate using dependency graph/SBOM instead of searching files + - Won't need to fetch repo tree + - Won't need to get file contents + - ISSUE: checking for prototype + - ISSUE: doesn't define whether dependency is indirect or not + +### If that's not a goer + +- Handle multiple packagefiles +- Handle other language package managers + - here's what GitHub supports: https://docs.github.com/en/code-security/supply-chain-security/understanding-your-software-supply-chain/dependency-graph-supported-package-ecosystems#supported-package-ecosystems + +## Manual ports + +- Run get dependents for known manual ports and include them somehow on larger script + +## Testing + +- More and better + +## Linting + +- Neostandard + - for import assertions, get rid of the problem by making the current JSON files into js files + +## Presentation + +- Rejig refactoring into clear commits +- Update README From edc336089d5cc009ca50a1c372b49d5829eb8789 Mon Sep 17 00:00:00 2001 From: Brett Kyle Date: Mon, 30 Dec 2024 22:55:28 +0000 Subject: [PATCH 12/20] Linting fixes --- build-filtered-data.mjs | 43 +- eslint.config.mjs | 7 +- helpers/data/known-dependents.json | 13 + helpers/data/known-dependents.mjs | 13 - helpers/octokit.mjs | 10 +- helpers/repo-data.mjs | 103 +- helpers/repo-data.test.mjs | 212 +- package-lock.json | 3305 +++++++++++++++++++++++++--- package.json | 4 +- todo.md | 47 +- 10 files changed, 3334 insertions(+), 423 deletions(-) create mode 100644 helpers/data/known-dependents.json delete mode 100644 helpers/data/known-dependents.mjs diff --git a/build-filtered-data.mjs b/build-filtered-data.mjs index 4d60f239..f5884499 100644 --- a/build-filtered-data.mjs +++ b/build-filtered-data.mjs @@ -2,23 +2,23 @@ import { appendFileSync } from 'fs' import { json2csv } from 'json-2-csv' import { RequestError } from 'octokit' -import denyList from './helpers/data/deny-list.json' assert { type: 'json' } -import governmentServiceOwners from './helpers/data/service-owners.json' assert { type: 'json' } +import denyList from './helpers/data/deny-list.json' with { type: 'json' } +import governmentServiceOwners from './helpers/data/service-owners.json' with { type: 'json' } import { getRemainingRateLimit } from './helpers/octokit.mjs' import { RepoData } from './helpers/repo-data.mjs' -import rawDeps from './data/raw-deps.json' assert { type: 'json' } +import rawDeps from './data/raw-deps.json' with { type: 'json' } // Set up date for file naming const currentDate = new Date() const yyyymmdd = currentDate.toISOString().split('T')[0] const timestamp = currentDate.getTime() -async function filterDeps() { +async function filterDeps () { const builtData = [] const batchSize = 500 let batchCounter = 0 - console.log(`Beginning dependency analysis...`) + console.log('Beginning dependency analysis...') for (const repo of rawDeps.all_public_dependent_repos) { try { @@ -56,35 +56,35 @@ async function filterDeps() { if (builtData.length > 0) { await writeBatchToFiles(builtData) } - console.log(`We're done!`) + console.log("We're done!") } -export async function analyseRepo(repo) { +export async function analyseRepo (repo) { const repoOwner = repo.owner const repoName = repo.repo_name const repoData = new RepoData(repoOwner, repoName, governmentServiceOwners) try { if (repoData.checkDenyList(denyList)) { - repoData.log(`on Deny List. Will not be processed.`) + repoData.log('on Deny List. Will not be processed.') return null } - repoData.log(`analyzing...`) + repoData.log('analyzing...') await repoData.fetchAndValidateMetaData() await repoData.fetchAndValidateRepoTree() - repoData.log(`tree fetched and validated.`) + repoData.log('tree fetched and validated.') if (repoData.builtByGovernment) { - repoData.log(`looks like a GOV.UK service.`) + repoData.log('looks like a GOV.UK service.') } else { - repoData.log(`looks like it ISN'T a GOV.UK service. This has been noted.`) + repoData.log("looks like it ISN'T a GOV.UK service. This has been noted.") } const packageFiles = await repoData.getAllFilesContent('package.json') let packageObjects = [] if (packageFiles.length === 0) { - repoData.log(`no package files found.`) + repoData.log('no package files found.') repoData.versionDoubt = true } else { packageObjects = packageFiles.map((file) => { @@ -103,11 +103,11 @@ export async function analyseRepo(repo) { // @TODO: several repos are failing silently somewhere after this log if (repoData.checkPrototype(packageObjects)) { - repoData.log(`looks like an instance of the prototype kit.`) + repoData.log('looks like an instance of the prototype kit.') repoData.isPrototype = true } if (!repoData.checkDirectDependency(packageObjects)) { - repoData.log(`govuk-frontend is not a direct dependency.`) + repoData.log('govuk-frontend is not a direct dependency.') } else { for (const versionData of repoData.frontendVersions) { repoData.log( @@ -118,12 +118,13 @@ export async function analyseRepo(repo) { !!versionData.frontendVersion.startsWith('~') || repoData.indirectDependency ) { - repoData.log(`searching for version in lockfile`) + repoData.log('searching for version in lockfile') repoData.versionDoubt = true - // @TODO: Get lockfile type here and log it. Pass to next function - repoData.frontendVersion = await repoData.getVersionFromLockfile() + const lockfileType = repoData.getLockfileType() + repoData.log(`using ${lockfileType}`) + repoData.lockfileFrontendVersion = await repoData.getVersionFromLockfile(lockfileType) repoData.log( - `using GOV.UK Frontend version ${repoData.frontendVersion}` + `using GOV.UK Frontend version ${repoData.lockfileFrontendVersion}` ) } } @@ -140,7 +141,7 @@ export async function analyseRepo(repo) { return repoData.getResult() } -async function writeBatchToFiles(builtData) { +async function writeBatchToFiles (builtData) { // Write JSON file await appendFileSync( `data/${yyyymmdd}-${timestamp}-filtered-data.json`, @@ -149,7 +150,7 @@ async function writeBatchToFiles(builtData) { // Write CSV file const csv = json2csv(builtData) await appendFileSync(`data/${yyyymmdd}-${timestamp}-filtered-data.csv`, csv) - console.log(`Data file updated with batch of entries`) + console.log('Data file updated with batch of entries') } filterDeps() diff --git a/eslint.config.mjs b/eslint.config.mjs index 19e0bc98..d3cd0c95 100644 --- a/eslint.config.mjs +++ b/eslint.config.mjs @@ -1,12 +1,10 @@ -import globals from 'globals' -import pluginJs from '@eslint/js' +import neostandard from 'neostandard' import babelParser from '@babel/eslint-parser' -/** @type {import('eslint').Linter.Config[]} */ export default [ + ...neostandard(), { languageOptions: { - globals: globals.node, parser: babelParser, parserOptions: { requireConfigFile: false, @@ -16,5 +14,4 @@ export default [ }, }, }, - pluginJs.configs.recommended, ] diff --git a/helpers/data/known-dependents.json b/helpers/data/known-dependents.json new file mode 100644 index 00000000..6acf8ffe --- /dev/null +++ b/helpers/data/known-dependents.json @@ -0,0 +1,13 @@ +[ + { + "package": "govuk_publishing_components", + "lang": "ruby", + "manager": "gem" + }, + { + "package": "govuk-frontend-jinja", + "lang": "python", + "manager": "pip", + "manual": true + } +] diff --git a/helpers/data/known-dependents.mjs b/helpers/data/known-dependents.mjs deleted file mode 100644 index 5ee6ab4d..00000000 --- a/helpers/data/known-dependents.mjs +++ /dev/null @@ -1,13 +0,0 @@ -export default [ - { - package: 'govuk_publishing_components', - lang: 'ruby', - manager: 'gem', - }, - { - package: 'govuk-frontend-jinja', - lang: 'python', - manager: 'pip', - manual: true, // This package does not depend on govuk-frontend, but ports it - }, -] diff --git a/helpers/octokit.mjs b/helpers/octokit.mjs index a25b3a0e..b27605dc 100644 --- a/helpers/octokit.mjs +++ b/helpers/octokit.mjs @@ -39,7 +39,7 @@ const octokit = new MyOctokit({ * @returns {Promise>} * @throws {RequestError} - If the request fails */ -export async function getRepoMetaData(repoOwner, repoName) { +export async function getRepoMetaData (repoOwner, repoName) { return await octokit.rest.repos.get({ owner: repoOwner, repo: repoName, @@ -53,7 +53,7 @@ export async function getRepoMetaData(repoOwner, repoName) { * @returns {Promise>} * @throws {RequestError} - If the request fails */ -export async function getLatestCommit(repoOwner, repoName) { +export async function getLatestCommit (repoOwner, repoName) { const commits = await octokit.rest.repos.listCommits({ owner: repoOwner, repo: repoName, @@ -71,7 +71,7 @@ export async function getLatestCommit(repoOwner, repoName) { * @returns {Promise>} * @throws {RequestError} - If the request fails */ -export async function getRepoTree(repoOwner, repoName, treeSha) { +export async function getRepoTree (repoOwner, repoName, treeSha) { return await octokit.rest.git.getTree({ owner: repoOwner, repo: repoName, @@ -89,7 +89,7 @@ export async function getRepoTree(repoOwner, repoName, treeSha) { * @returns {Promise>} - the file content * @throws {RequestError} - If the request fails */ -export async function getFileContent(repoOwner, repoName, filePath) { +export async function getFileContent (repoOwner, repoName, filePath) { return await octokit.rest.repos.getContent({ owner: repoOwner, repo: repoName, @@ -104,7 +104,7 @@ export async function getFileContent(repoOwner, repoName, filePath) { * @returns {number} - The number of remaining requests * @throws {RequestError} - If the request fails */ -export async function getRemainingRateLimit() { +export async function getRemainingRateLimit () { const rateLimit = await octokit.rest.rateLimit.get() return rateLimit.data.rate.remaining } diff --git a/helpers/repo-data.mjs b/helpers/repo-data.mjs index 57238075..a3a70f5c 100644 --- a/helpers/repo-data.mjs +++ b/helpers/repo-data.mjs @@ -7,10 +7,10 @@ import { import * as yarnLock from '@yarnpkg/lockfile' import { RequestError } from 'octokit' -class UnsupportedLockFileError extends Error {} -class NoMetaDataError extends Error {} -class NoRepoTreeError extends Error {} -class NoCommitsError extends Error {} +export class UnsupportedLockFileError extends Error {} +export class NoMetaDataError extends Error {} +export class NoRepoTreeError extends Error {} +export class NoCommitsError extends Error {} export class RepoData { /** @@ -20,7 +20,7 @@ export class RepoData { * @param {string} repoName - The name of the repository. * @param {Array} [serviceOwners=[]] - The list of service owners. */ - constructor(repoOwner, repoName, serviceOwners = []) { + constructor (repoOwner, repoName, serviceOwners = []) { if (!repoOwner) { this.log('repoOwner must be provided', 'error') throw new Error('repoOwner must be provided') @@ -32,7 +32,7 @@ export class RepoData { this.repoOwner = repoOwner this.repoName = repoName this.couldntAccess = false - this.frontendVersion = null + this.lockfileFrontendVersion = null this.versionDoubt = false this.builtByGovernment = serviceOwners.includes(this.repoOwner) this.indirectDependency = false @@ -51,7 +51,7 @@ export class RepoData { * @param {array} denyList - An array of objects with owner and name properties * @returns {boolean} - Whether the repo is on the deny list */ - checkDenyList(denyList) { + checkDenyList (denyList) { return denyList.some( (item) => this.repoOwner === item.owner && this.repoName === item.name ) @@ -64,7 +64,7 @@ export class RepoData { * @throws {RequestError} - If the request fails * */ - async fetchAndValidateMetaData() { + async fetchAndValidateMetaData () { const repoMetaData = await getRepoMetaData(this.repoOwner, this.repoName) if (repoMetaData) { this.lastUpdated = repoMetaData.data.pushed_at @@ -75,7 +75,7 @@ export class RepoData { if (!this.repoCreated) { throw new NoMetaDataError() } - this.log(`metadata fetched and validated.`) + this.log('metadata fetched and validated.') } /** @@ -84,14 +84,14 @@ export class RepoData { * @throws {NoRepoTreeError} - If the tree could not be fetched * @throws {RequestError} - If the request fails */ - async fetchAndValidateRepoTree() { + async fetchAndValidateRepoTree () { const latestCommitSha = await this.getLatestCommitSha() this.repoTree = await getRepoTree( this.repoOwner, this.repoName, latestCommitSha ) - if (!this.repoTree) { + if (!this.repoTree || !this.repoTree.data || !this.repoTree.data.tree) { throw new NoRepoTreeError() } } @@ -103,7 +103,7 @@ export class RepoData { * @throws {NoCommitsError} - If the repo has no commits * @throws {RequestError} - If the request fails */ - async getLatestCommitSha() { + async getLatestCommitSha () { const latestCommit = await getLatestCommit(this.repoOwner, this.repoName) if (latestCommit === undefined) { throw new NoCommitsError() @@ -117,9 +117,9 @@ export class RepoData { * @param {array} packageObjects - an array of packageObjects * @returns {boolean} - Whether the repo is a prototype */ - checkPrototype(packageObjects) { + checkPrototype (packageObjects) { if ( - this.repoTree.data.tree.some((file) => file.path == 'lib/usage_data.js') + this.repoTree.data.tree.some((file) => file.path === 'lib/usage_data.js') ) { return true } else if (packageObjects.length === 0) { @@ -137,7 +137,7 @@ export class RepoData { return false } - checkDirectDependency(packageObjects) { + checkDirectDependency (packageObjects) { for (const packageObject of packageObjects) { if ('govuk-frontend' in packageObject.content.dependencies) { this.frontendVersions.push({ @@ -159,11 +159,11 @@ export class RepoData { * @returns {Promise>} - The file content * @throws {RequestError} - If the request fails */ - async getRepoFileContent(filePath) { + async getRepoFileContent (filePath) { return await getFileContent(this.repoOwner, this.repoName, filePath) } - async getAllFilesContent(fileName) { + async getAllFilesContent (fileName) { const files = this.repoTree.data.tree.filter( (file) => file.path.endsWith(fileName) && !file.path.includes('node_modules') @@ -183,8 +183,8 @@ export class RepoData { * @param {string} filePath * @returns {boolean} - Whether the file exists */ - checkFileExists(filePath) { - return this.repoTree.data.tree.some((file) => file.path == filePath) + checkFileExists (filePath) { + return this.repoTree.data.tree.some((file) => file.path === filePath) } /** @@ -193,18 +193,17 @@ export class RepoData { * @param {string} message - the message to log * @param {[string]} type - type of message (error) */ - log(message, type = '') { + log (message, type = '') { const typeMsg = type === 'error' ? ' ERROR:' : '' console.log(`${this.repoOwner}/${this.repoName}:${typeMsg} ${message}`) } /** - * Gets the version of govuk-frontend from the lockfile - * @returns {string} - The version of govuk-frontend - * @throws {UnsupportedLockFileError} - If the lockfile is not supported - * @throws {RequestError} - If the request for the file data fails + * Checks for the type of lockfile + * + * @returns {string} - the lockfile type */ - async getVersionFromLockfile() { + getLockfileType () { let lockfileType if (this.checkFileExists('package-lock.json')) { lockfileType = 'package-lock.json' @@ -214,19 +213,28 @@ export class RepoData { // @TODO: support some package files - ruby (for GOV.UK) and maybe python? throw new UnsupportedLockFileError() } + return lockfileType + } + /** + * Gets the version of govuk-frontend from the lockfile + * @returns {string} - The version of govuk-frontend + * @throws {UnsupportedLockFileError} - If the lockfile is not supported + * @throws {RequestError} - If the request for the file data fails + */ + async getVersionFromLockfile (lockfileType) { const lockfile = await this.getRepoFileContent(lockfileType) - if (lockfileType == 'package-lock.json') { + if (lockfileType === 'package-lock.json') { const lockfileObject = JSON.parse(lockfile.data) - if (this.frontendVersion) { + if (this.frontendVersions.length > 0) { // If we found an ambiguous frontend version in the package.json file, // all we have to do is get the package version from the lockfile const packageVersion = lockfileObject.packages?.['node_modules/govuk-frontend']?.version || - lockfileObject.dependencies?.['node_modules/govuk-frontend']?.version + lockfileObject.dependencies?.['govuk-frontend']?.version if (packageVersion) { - this.frontendVersion = packageVersion + this.lockfileFrontendVersion = packageVersion } } else { const deps = [] @@ -234,7 +242,7 @@ export class RepoData { // we have to search the lockfile for the govuk-frontend entries for (const [packageName, packageData] of Object.entries({ ...(lockfileObject.packages || {}), - ...(lockfileObject.dependencies || {}), + ...(lockfileObject.dependencies || {}) })) { if (packageData.dependencies?.['govuk-frontend']) { deps.push({ @@ -244,14 +252,25 @@ export class RepoData { } } this.parentDependency = deps + // Set highest dependency number to frontendVersion. + // Not sure this is the right approach, but we'll still have dependency data + if (deps.length > 0) { + const versions = deps.map(dep => dep.version) + this.lockfileFrontendVersion = versions.sort((a, b) => b.localeCompare(a, undefined, { numeric: true }))[0] + } } } else if (lockfileType === 'yarn.lock') { - const yarnLockObject = yarnLock.default.parse(lockfile.data) - this.frontendVersion = - yarnLockObject.object[`govuk-frontend@${this.frontendVersion}`] - ?.version || this.frontendVersion + const yarnLockObject = yarnLock.parse(lockfile.data) + let yarnLockVersion = '0' + + for (const [key, value] of Object.entries(yarnLockObject.object)) { + if (key.startsWith('govuk-frontend@') && value.version > yarnLockVersion) { + yarnLockVersion = value.version + } + } + this.lockfileFrontendVersion = yarnLockVersion || this.lockfileFrontendVersion } - return this.frontendVersion + return this.lockfileFrontendVersion } /** @@ -261,18 +280,18 @@ export class RepoData { * * @throws {Error} - If the error is not an expected type */ - handleError(error) { + handleError (error) { if (error instanceof RequestError) { this.log(`problem accessing repo: ${error.message}`, 'error') } else if (error instanceof NoMetaDataError) { - this.log(`couldn't fetch metadata`, 'error') + this.log("couldn't fetch metadata", 'error') } else if (error instanceof NoCommitsError) { - this.log(`couldn't fetch repo tree as repo has no commits`, 'error') + this.log("couldn't fetch repo tree as repo has no commits", 'error') } else if (error instanceof NoRepoTreeError) { - this.log(`couldn't fetch repo tree`, 'error') + this.log("couldn't fetch repo tree", 'error') } else if (error instanceof UnsupportedLockFileError) { this.log( - `couldn't find a supported lockfile. Skipping version check.`, + "couldn't find a supported lockfile. Skipping version check.", 'error' ) } else { @@ -285,12 +304,12 @@ export class RepoData { * * @returns {object} - The result of the analysis */ - getResult() { + getResult () { return { repoOwner: this.repoOwner, repoName: this.repoName, couldntAccess: this.couldntAccess, - frontendVersion: this.frontendVersion, + lockfileFrontendVersion: this.lockfileFrontendVersion, directDependencyVersions: this.frontendVersions, versionDoubt: this.versionDoubt, builtByGovernment: this.builtByGovernment, diff --git a/helpers/repo-data.test.mjs b/helpers/repo-data.test.mjs index f2c39d2f..a4382f55 100644 --- a/helpers/repo-data.test.mjs +++ b/helpers/repo-data.test.mjs @@ -1,10 +1,5 @@ import { describe, it, expect, vi } from 'vitest' -import { - RepoData, - NoMetaDataError, - NoRepoTreeError, - NoCommitsError, -} from './repo-data.mjs' +import { RepoData, NoMetaDataError, NoRepoTreeError, NoCommitsError, UnsupportedLockFileError } from './repo-data.mjs' import { getFileContent, getLatestCommit, @@ -93,16 +88,16 @@ describe('RepoData', () => { }) describe('fetchAndValidateRepoTree', () => { - it('should throw a NoRepoTreeError if metadata is missing', async () => { + it('should throw a NoRepoTreeError if repo tree is missing', async () => { const repoData = new RepoData(repoOwner, repoName, serviceOwners) - getRepoMetaData.mockResolvedValue({ + vi.spyOn(repoData, 'getLatestCommitSha').mockResolvedValue('test-sha') + getRepoTree.mockResolvedValue({ data: { - pushed_at: null, - created_at: null, + tree: null, }, }) - await expect(repoData.fetchAndValidateMetaData()).rejects.toThrow( + await expect(repoData.fetchAndValidateRepoTree()).rejects.toThrow( NoRepoTreeError ) }) @@ -177,6 +172,31 @@ describe('RepoData', () => { }) }) + describe('getLockfileType', () => { + it('should return package-lock.json if it exists', () => { + const repoData = new RepoData(repoOwner, repoName, serviceOwners) + repoData.repoTree = { data: { tree: [{ path: 'package-lock.json' }] } } + + const lockfileType = repoData.getLockfileType() + expect(lockfileType).toBe('package-lock.json') + }) + + it('should return yarn.lock if it exists', () => { + const repoData = new RepoData(repoOwner, repoName, serviceOwners) + repoData.repoTree = { data: { tree: [{ path: 'yarn.lock' }] } } + + const lockfileType = repoData.getLockfileType() + expect(lockfileType).toBe('yarn.lock') + }) + + it('should throw UnsupportedLockFileError if no supported lockfile exists', () => { + const repoData = new RepoData(repoOwner, repoName, serviceOwners) + repoData.repoTree = { data: { tree: [{ path: 'other-file.lock' }] } } + + expect(() => repoData.getLockfileType()).toThrow(UnsupportedLockFileError) + }) + }) + describe('getAllFilesContent', () => { it('should get the content of all files with a given name', async () => { const repoData = new RepoData(repoOwner, repoName, serviceOwners) @@ -200,13 +220,181 @@ describe('RepoData', () => { ]) }) }) + describe('checkDirectDependency', () => { + it('should detect direct dependency on govuk-frontend', () => { + const repoData = new RepoData(repoOwner, repoName, serviceOwners) + const packageObjects = [ + { + content: { dependencies: { 'govuk-frontend': '3.11.0' } }, + path: 'package.json', + }, + ] + + const hasDirectDependency = repoData.checkDirectDependency(packageObjects) + expect(hasDirectDependency).toBe(true) + expect(repoData.frontendVersions).toEqual([ + { packagePath: 'package.json', frontendVersion: '3.11.0' }, + ]) + }) + + it('should detect indirect dependency on govuk-frontend', () => { + const repoData = new RepoData(repoOwner, repoName, serviceOwners) + const packageObjects = [ + { + content: { dependencies: { 'other-dependency': '1.0.0' } }, + path: 'package.json', + }, + ] + + const hasDirectDependency = repoData.checkDirectDependency(packageObjects) + expect(hasDirectDependency).toBe(false) + expect(repoData.indirectDependency).toBe(true) + }) + }) + describe('getVersionFromLockfile', () => { + it('should get version from package-lock.json if ambiguous version in package.json', async () => { + const repoData = new RepoData(repoOwner, repoName, serviceOwners) + repoData.frontendVersions = [{ packagePath: 'package.json', frontendVersion: '3.11.0' }] + const lockfileContent = { + data: JSON.stringify({ + packages: { + 'node_modules/govuk-frontend': { version: '3.11.0' }, + }, + }), + } + getFileContent.mockResolvedValue(lockfileContent) + + const version = await repoData.getVersionFromLockfile('package-lock.json') + expect(version).toBe('3.11.0') + }) + + it('should get version from package-lock.json if no versions in package.json', async () => { + const repoData = new RepoData(repoOwner, repoName, serviceOwners) + const lockfileContent = { + data: JSON.stringify( + { + packages: { + 'parent-dependency': { + dependencies: { + 'govuk-frontend': { version: '3.11.0' }, + }, + } + } + }), + } + getFileContent.mockResolvedValue(lockfileContent) + + const version = await repoData.getVersionFromLockfile('package-lock.json') + expect(version).toBe('3.11.0') + }) + + it('should get version from yarn.lock', async () => { + const repoData = new RepoData(repoOwner, repoName, serviceOwners) + const lockfileContent = { + data: ` +govuk-frontend@^3.11.0: + version "3.11.0" + resolved "https://registry.yarnpkg.com/govuk-frontend/-/govuk-frontend-3.11.0.tgz#hash" + integrity sha512-hash +` + } + getFileContent.mockResolvedValue(lockfileContent) + + const version = await repoData.getVersionFromLockfile('yarn.lock') + expect(version).toBe('3.11.0') + }) + }) + describe('handleError', () => { + it('should log NoMetaDataError', () => { + const repoData = new RepoData(repoOwner, repoName, serviceOwners) + const consoleSpy = vi.spyOn(console, 'log') + const error = new NoMetaDataError() + + repoData.handleError(error) + expect(consoleSpy).toHaveBeenCalledWith( + "test-owner/test-repo: ERROR: couldn't fetch metadata" + ) + }) + + it('should log NoCommitsError', () => { + const repoData = new RepoData(repoOwner, repoName, serviceOwners) + const consoleSpy = vi.spyOn(console, 'log') + const error = new NoCommitsError() + + repoData.handleError(error) + expect(consoleSpy).toHaveBeenCalledWith( + "test-owner/test-repo: ERROR: couldn't fetch repo tree as repo has no commits" + ) + }) + + it('should log NoRepoTreeError', () => { + const repoData = new RepoData(repoOwner, repoName, serviceOwners) + const consoleSpy = vi.spyOn(console, 'log') + const error = new NoRepoTreeError() + + repoData.handleError(error) + expect(consoleSpy).toHaveBeenCalledWith( + "test-owner/test-repo: ERROR: couldn't fetch repo tree" + ) + }) + + it('should log UnsupportedLockFileError', () => { + const repoData = new RepoData(repoOwner, repoName, serviceOwners) + const consoleSpy = vi.spyOn(console, 'log') + const error = new UnsupportedLockFileError() + + repoData.handleError(error) + expect(consoleSpy).toHaveBeenCalledWith( + "test-owner/test-repo: ERROR: couldn't find a supported lockfile. Skipping version check." + ) + }) + + it('should rethrow unknown errors', () => { + const repoData = new RepoData(repoOwner, repoName, serviceOwners) + const error = new Error('Unknown error') + + expect(() => repoData.handleError(error)).toThrow('Unknown error') + }) + }) + describe('getResult', () => { + it('should return the result of the analysis', () => { + const repoData = new RepoData(repoOwner, repoName, serviceOwners) + repoData.couldntAccess = true + repoData.lockfileFrontendVersion = '3.11.0' + repoData.versionDoubt = true + repoData.builtByGovernment = true + repoData.indirectDependency = true + repoData.isPrototype = true + repoData.lastUpdated = '2023-01-01T00:00:00Z' + repoData.repoCreated = '2022-01-01T00:00:00Z' + repoData.parentDependency = [{ parent: 'test-parent', version: '1.0.0' }] + repoData.errorThrown = 'Some error' + + const result = repoData.getResult() + expect(result).toEqual({ + repoOwner: 'test-owner', + repoName: 'test-repo', + couldntAccess: true, + lockfileFrontendVersion: '3.11.0', + directDependencyVersions: [], + versionDoubt: true, + builtByGovernment: true, + indirectDependency: true, + isPrototype: true, + lastUpdated: '2023-01-01T00:00:00Z', + repoCreated: '2022-01-01T00:00:00Z', + parentDependency: [{ parent: 'test-parent', version: '1.0.0' }], + errorThrown: 'Some error', + }) + }) + }) it('should get the content of a file in the repo', async () => { const repoData = new RepoData(repoOwner, repoName, serviceOwners) getFileContent.mockResolvedValue({ data: '{ test: "file content" }' }) const fileContent = await repoData.getRepoFileContent('package.json') - expect(fileContent).toEqual({ data: `{ test: "file content" }` }) + expect(fileContent).toEqual({ data: '{ test: "file content" }' }) }) it('should check if a file exists in the repo tree', () => { diff --git a/package-lock.json b/package-lock.json index 8aaebbe5..ff007f84 100644 --- a/package-lock.json +++ b/package-lock.json @@ -13,10 +13,8 @@ "@octokit/plugin-throttling": "^9.3.0", "@yarnpkg/lockfile": "^1.1.0", "eslint": "^9.17.0", - "event-stream": "^4.0.1", - "globals": "^15.13.0", "json-2-csv": "^5.5.7", - "JSONStream": "^1.3.5", + "neostandard": "^0.12.0", "octokit": "^4.0.2", "vitest": "^2.1.8" } @@ -927,6 +925,17 @@ "url": "https://github.com/sponsors/nzakas" } }, + "node_modules/@humanwhocodes/gitignore-to-minimatch": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/@humanwhocodes/gitignore-to-minimatch/-/gitignore-to-minimatch-1.0.2.tgz", + "integrity": "sha512-rSqmMJDdLFUsyxR6FMtD00nfQKKLFb1kv+qBbOVKqErvloEIJLo5bDTJTQNTYgeyp78JsA7u/NPi5jT1GR/MuA==", + "dev": true, + "license": "Apache-2.0", + "funding": { + "type": "github", + "url": "https://github.com/sponsors/nzakas" + } + }, "node_modules/@humanwhocodes/module-importer": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/@humanwhocodes/module-importer/-/module-importer-1.0.1.tgz", @@ -1046,6 +1055,54 @@ "node": ">=4.0" } }, + "node_modules/@nodelib/fs.scandir": { + "version": "2.1.5", + "resolved": "https://registry.npmjs.org/@nodelib/fs.scandir/-/fs.scandir-2.1.5.tgz", + "integrity": "sha512-vq24Bq3ym5HEQm2NKCr3yXDwjc7vTsEThRDnkp2DK9p1uqLR+DHurm/NOTo0KG7HYHU7eppKZj3MyqYuMBf62g==", + "dev": true, + "license": "MIT", + "dependencies": { + "@nodelib/fs.stat": "2.0.5", + "run-parallel": "^1.1.9" + }, + "engines": { + "node": ">= 8" + } + }, + "node_modules/@nodelib/fs.stat": { + "version": "2.0.5", + "resolved": "https://registry.npmjs.org/@nodelib/fs.stat/-/fs.stat-2.0.5.tgz", + "integrity": "sha512-RkhPPp2zrqDAQA/2jNhnztcPAlv64XdhIp7a7454A5ovI7Bukxgt7MX7udwAu3zg1DcpPU0rz3VV1SeaqvY4+A==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 8" + } + }, + "node_modules/@nodelib/fs.walk": { + "version": "1.2.8", + "resolved": "https://registry.npmjs.org/@nodelib/fs.walk/-/fs.walk-1.2.8.tgz", + "integrity": "sha512-oGB+UxlgWcgQkgwo8GcEGwemoTFt3FIO9ababBmaGwXIoBKZ+GTy0pP185beGg7Llih/NSHSV2XAs1lnznocSg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@nodelib/fs.scandir": "2.1.5", + "fastq": "^1.6.0" + }, + "engines": { + "node": ">= 8" + } + }, + "node_modules/@nolyfill/is-core-module": { + "version": "1.0.39", + "resolved": "https://registry.npmjs.org/@nolyfill/is-core-module/-/is-core-module-1.0.39.tgz", + "integrity": "sha512-nn5ozdjYQpUCZlWGuxcJY/KpxkWQs4DcbMCmKojjyrYDEAGy4Ce19NN4v5MduafTwJlbKc99UA8YhSVqq9yPZA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=12.4.0" + } + }, "node_modules/@octokit/app": { "version": "15.1.0", "resolved": "https://registry.npmjs.org/@octokit/app/-/app-15.1.0.tgz", @@ -1653,12 +1710,39 @@ "win32" ] }, + "node_modules/@stylistic/eslint-plugin": { + "version": "2.11.0", + "resolved": "https://registry.npmjs.org/@stylistic/eslint-plugin/-/eslint-plugin-2.11.0.tgz", + "integrity": "sha512-PNRHbydNG5EH8NK4c+izdJlxajIR6GxcUhzsYNRsn6Myep4dsZt0qFCz3rCPnkvgO5FYibDcMqgNHUT+zvjYZw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@typescript-eslint/utils": "^8.13.0", + "eslint-visitor-keys": "^4.2.0", + "espree": "^10.3.0", + "estraverse": "^5.3.0", + "picomatch": "^4.0.2" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "peerDependencies": { + "eslint": ">=8.40.0" + } + }, "node_modules/@types/aws-lambda": { "version": "8.10.138", "resolved": "https://registry.npmjs.org/@types/aws-lambda/-/aws-lambda-8.10.138.tgz", "integrity": "sha512-71EHMl70TPWIAsFuHd85NHq6S6T2OOjiisPTrH7RgcjzpJpPh4RQJv7PvVvIxc6PIp8CLV7F9B+TdjcAES5vcA==", "dev": true }, + "node_modules/@types/doctrine": { + "version": "0.0.9", + "resolved": "https://registry.npmjs.org/@types/doctrine/-/doctrine-0.0.9.tgz", + "integrity": "sha512-eOIHzCUSH7SMfonMG1LsC2f8vxBFtho6NGBznK41R84YzPuvSBzrhEps33IsQiOW9+VL6NQ9DbjQJznk/S4uRA==", + "dev": true, + "license": "MIT" + }, "node_modules/@types/estree": { "version": "1.0.6", "resolved": "https://registry.npmjs.org/@types/estree/-/estree-1.0.6.tgz", @@ -1673,6 +1757,225 @@ "dev": true, "license": "MIT" }, + "node_modules/@typescript-eslint/eslint-plugin": { + "version": "8.19.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/eslint-plugin/-/eslint-plugin-8.19.0.tgz", + "integrity": "sha512-NggSaEZCdSrFddbctrVjkVZvFC6KGfKfNK0CU7mNK/iKHGKbzT4Wmgm08dKpcZECBu9f5FypndoMyRHkdqfT1Q==", + "dev": true, + "license": "MIT", + "dependencies": { + "@eslint-community/regexpp": "^4.10.0", + "@typescript-eslint/scope-manager": "8.19.0", + "@typescript-eslint/type-utils": "8.19.0", + "@typescript-eslint/utils": "8.19.0", + "@typescript-eslint/visitor-keys": "8.19.0", + "graphemer": "^1.4.0", + "ignore": "^5.3.1", + "natural-compare": "^1.4.0", + "ts-api-utils": "^1.3.0" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + }, + "peerDependencies": { + "@typescript-eslint/parser": "^8.0.0 || ^8.0.0-alpha.0", + "eslint": "^8.57.0 || ^9.0.0", + "typescript": ">=4.8.4 <5.8.0" + } + }, + "node_modules/@typescript-eslint/parser": { + "version": "8.19.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/parser/-/parser-8.19.0.tgz", + "integrity": "sha512-6M8taKyOETY1TKHp0x8ndycipTVgmp4xtg5QpEZzXxDhNvvHOJi5rLRkLr8SK3jTgD5l4fTlvBiRdfsuWydxBw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@typescript-eslint/scope-manager": "8.19.0", + "@typescript-eslint/types": "8.19.0", + "@typescript-eslint/typescript-estree": "8.19.0", + "@typescript-eslint/visitor-keys": "8.19.0", + "debug": "^4.3.4" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + }, + "peerDependencies": { + "eslint": "^8.57.0 || ^9.0.0", + "typescript": ">=4.8.4 <5.8.0" + } + }, + "node_modules/@typescript-eslint/scope-manager": { + "version": "8.19.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/scope-manager/-/scope-manager-8.19.0.tgz", + "integrity": "sha512-hkoJiKQS3GQ13TSMEiuNmSCvhz7ujyqD1x3ShbaETATHrck+9RaDdUbt+osXaUuns9OFwrDTTrjtwsU8gJyyRA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@typescript-eslint/types": "8.19.0", + "@typescript-eslint/visitor-keys": "8.19.0" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + } + }, + "node_modules/@typescript-eslint/type-utils": { + "version": "8.19.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/type-utils/-/type-utils-8.19.0.tgz", + "integrity": "sha512-TZs0I0OSbd5Aza4qAMpp1cdCYVnER94IziudE3JU328YUHgWu9gwiwhag+fuLeJ2LkWLXI+F/182TbG+JaBdTg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@typescript-eslint/typescript-estree": "8.19.0", + "@typescript-eslint/utils": "8.19.0", + "debug": "^4.3.4", + "ts-api-utils": "^1.3.0" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + }, + "peerDependencies": { + "eslint": "^8.57.0 || ^9.0.0", + "typescript": ">=4.8.4 <5.8.0" + } + }, + "node_modules/@typescript-eslint/types": { + "version": "8.19.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/types/-/types-8.19.0.tgz", + "integrity": "sha512-8XQ4Ss7G9WX8oaYvD4OOLCjIQYgRQxO+qCiR2V2s2GxI9AUpo7riNwo6jDhKtTcaJjT8PY54j2Yb33kWtSJsmA==", + "dev": true, + "license": "MIT", + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + } + }, + "node_modules/@typescript-eslint/typescript-estree": { + "version": "8.19.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/typescript-estree/-/typescript-estree-8.19.0.tgz", + "integrity": "sha512-WW9PpDaLIFW9LCbucMSdYUuGeFUz1OkWYS/5fwZwTA+l2RwlWFdJvReQqMUMBw4yJWJOfqd7An9uwut2Oj8sLw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@typescript-eslint/types": "8.19.0", + "@typescript-eslint/visitor-keys": "8.19.0", + "debug": "^4.3.4", + "fast-glob": "^3.3.2", + "is-glob": "^4.0.3", + "minimatch": "^9.0.4", + "semver": "^7.6.0", + "ts-api-utils": "^1.3.0" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + }, + "peerDependencies": { + "typescript": ">=4.8.4 <5.8.0" + } + }, + "node_modules/@typescript-eslint/typescript-estree/node_modules/brace-expansion": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.0.1.tgz", + "integrity": "sha512-XnAIvQ8eM+kC6aULx6wuQiwVsnzsi9d3WxzV3FpWTGA19F621kwdbsAcFKXgKUHZWsy+mY6iL1sHTxWEFCytDA==", + "dev": true, + "license": "MIT", + "dependencies": { + "balanced-match": "^1.0.0" + } + }, + "node_modules/@typescript-eslint/typescript-estree/node_modules/minimatch": { + "version": "9.0.5", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-9.0.5.tgz", + "integrity": "sha512-G6T0ZX48xgozx7587koeX9Ys2NYy6Gmv//P89sEte9V9whIapMNF4idKxnW2QtCcLiTWlb/wfCabAtAFWhhBow==", + "dev": true, + "license": "ISC", + "dependencies": { + "brace-expansion": "^2.0.1" + }, + "engines": { + "node": ">=16 || 14 >=14.17" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/@typescript-eslint/typescript-estree/node_modules/semver": { + "version": "7.6.3", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.6.3.tgz", + "integrity": "sha512-oVekP1cKtI+CTDvHWYFUcMtsK/00wmAEfyqKfNdARm8u1wNVhSgaX7A8d4UuIlUI5e84iEwOhs7ZPYRmzU9U6A==", + "dev": true, + "license": "ISC", + "bin": { + "semver": "bin/semver.js" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/@typescript-eslint/utils": { + "version": "8.19.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/utils/-/utils-8.19.0.tgz", + "integrity": "sha512-PTBG+0oEMPH9jCZlfg07LCB2nYI0I317yyvXGfxnvGvw4SHIOuRnQ3kadyyXY6tGdChusIHIbM5zfIbp4M6tCg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@eslint-community/eslint-utils": "^4.4.0", + "@typescript-eslint/scope-manager": "8.19.0", + "@typescript-eslint/types": "8.19.0", + "@typescript-eslint/typescript-estree": "8.19.0" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + }, + "peerDependencies": { + "eslint": "^8.57.0 || ^9.0.0", + "typescript": ">=4.8.4 <5.8.0" + } + }, + "node_modules/@typescript-eslint/visitor-keys": { + "version": "8.19.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/visitor-keys/-/visitor-keys-8.19.0.tgz", + "integrity": "sha512-mCFtBbFBJDCNCWUl5y6sZSCHXw1DEFEk3c/M3nRK2a4XUB8StGFtmcEMizdjKuBzB6e/smJAAWYug3VrdLMr1w==", + "dev": true, + "license": "MIT", + "dependencies": { + "@typescript-eslint/types": "8.19.0", + "eslint-visitor-keys": "^4.2.0" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + } + }, "node_modules/@vitest/expect": { "version": "2.1.8", "resolved": "https://registry.npmjs.org/@vitest/expect/-/expect-2.1.8.tgz", @@ -1871,6 +2174,142 @@ "dev": true, "license": "Python-2.0" }, + "node_modules/array-buffer-byte-length": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/array-buffer-byte-length/-/array-buffer-byte-length-1.0.2.tgz", + "integrity": "sha512-LHE+8BuR7RYGDKvnrmcuSq3tDcKv9OFEXQt/HpbZhY7V6h0zlUXutnAD82GiFx9rdieCMjkvtcsPqBwgUl1Iiw==", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bound": "^1.0.3", + "is-array-buffer": "^3.0.5" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/array-includes": { + "version": "3.1.8", + "resolved": "https://registry.npmjs.org/array-includes/-/array-includes-3.1.8.tgz", + "integrity": "sha512-itaWrbYbqpGXkGhZPGUulwnhVf5Hpy1xiCFsGqyIGglbBxmG5vSjxQen3/WGOjPpNEv1RtBLKxbmVXm8HpJStQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bind": "^1.0.7", + "define-properties": "^1.2.1", + "es-abstract": "^1.23.2", + "es-object-atoms": "^1.0.0", + "get-intrinsic": "^1.2.4", + "is-string": "^1.0.7" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/array.prototype.findlast": { + "version": "1.2.5", + "resolved": "https://registry.npmjs.org/array.prototype.findlast/-/array.prototype.findlast-1.2.5.tgz", + "integrity": "sha512-CVvd6FHg1Z3POpBLxO6E6zr+rSKEQ9L6rZHAaY7lLfhKsWYUBBOuMs0e9o24oopj6H+geRCX0YJ+TJLBK2eHyQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bind": "^1.0.7", + "define-properties": "^1.2.1", + "es-abstract": "^1.23.2", + "es-errors": "^1.3.0", + "es-object-atoms": "^1.0.0", + "es-shim-unscopables": "^1.0.2" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/array.prototype.flat": { + "version": "1.3.3", + "resolved": "https://registry.npmjs.org/array.prototype.flat/-/array.prototype.flat-1.3.3.tgz", + "integrity": "sha512-rwG/ja1neyLqCuGZ5YYrznA62D4mZXg0i1cIskIUKSiqF3Cje9/wXAls9B9s1Wa2fomMsIv8czB8jZcPmxCXFg==", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bind": "^1.0.8", + "define-properties": "^1.2.1", + "es-abstract": "^1.23.5", + "es-shim-unscopables": "^1.0.2" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/array.prototype.flatmap": { + "version": "1.3.3", + "resolved": "https://registry.npmjs.org/array.prototype.flatmap/-/array.prototype.flatmap-1.3.3.tgz", + "integrity": "sha512-Y7Wt51eKJSyi80hFrJCePGGNo5ktJCslFuboqJsbf57CCPcm5zztluPlc4/aD8sWsKvlwatezpV4U1efk8kpjg==", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bind": "^1.0.8", + "define-properties": "^1.2.1", + "es-abstract": "^1.23.5", + "es-shim-unscopables": "^1.0.2" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/array.prototype.tosorted": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/array.prototype.tosorted/-/array.prototype.tosorted-1.1.4.tgz", + "integrity": "sha512-p6Fx8B7b7ZhL/gmUsAy0D15WhvDccw3mnGNbZpi3pmeJdxtWsj2jEaI4Y6oo3XiHfzuSgPwKc04MYt6KgvC/wA==", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bind": "^1.0.7", + "define-properties": "^1.2.1", + "es-abstract": "^1.23.3", + "es-errors": "^1.3.0", + "es-shim-unscopables": "^1.0.2" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/arraybuffer.prototype.slice": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/arraybuffer.prototype.slice/-/arraybuffer.prototype.slice-1.0.4.tgz", + "integrity": "sha512-BNoCY6SXXPQ7gF2opIP4GBE+Xw7U+pHMYKuzjgCN3GwiaIR09UUeKfheyIry77QtrCBlC0KK0q5/TER/tYh3PQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "array-buffer-byte-length": "^1.0.1", + "call-bind": "^1.0.8", + "define-properties": "^1.2.1", + "es-abstract": "^1.23.5", + "es-errors": "^1.3.0", + "get-intrinsic": "^1.2.6", + "is-array-buffer": "^3.0.4" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, "node_modules/assertion-error": { "version": "2.0.1", "resolved": "https://registry.npmjs.org/assertion-error/-/assertion-error-2.0.1.tgz", @@ -1881,6 +2320,22 @@ "node": ">=12" } }, + "node_modules/available-typed-arrays": { + "version": "1.0.7", + "resolved": "https://registry.npmjs.org/available-typed-arrays/-/available-typed-arrays-1.0.7.tgz", + "integrity": "sha512-wvUjBtSGN7+7SjNpq/9M2Tg350UZD3q62IFZLbRAR1bSMlCo1ZaeW+BJ+D090e4hIIZLBcTDWe4Mh4jvUDajzQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "possible-typed-array-names": "^1.0.0" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, "node_modules/balanced-match": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.2.tgz", @@ -1911,6 +2366,19 @@ "concat-map": "0.0.1" } }, + "node_modules/braces": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/braces/-/braces-3.0.3.tgz", + "integrity": "sha512-yQbXgO/OSZVD2IsiLlro+7Hf6Q18EJrKSEsdoMzKePKXct3gvD8oLcOQdIzGupr5Fj+EDe8gO/lxc1BzfMpxvA==", + "dev": true, + "license": "MIT", + "dependencies": { + "fill-range": "^7.1.1" + }, + "engines": { + "node": ">=8" + } + }, "node_modules/browserslist": { "version": "4.24.3", "resolved": "https://registry.npmjs.org/browserslist/-/browserslist-4.24.3.tgz", @@ -1955,6 +2423,56 @@ "node": ">=8" } }, + "node_modules/call-bind": { + "version": "1.0.8", + "resolved": "https://registry.npmjs.org/call-bind/-/call-bind-1.0.8.tgz", + "integrity": "sha512-oKlSFMcMwpUg2ednkhQ454wfWiU/ul3CkJe/PEHcTKuiX6RpbehUiFMXu13HalGZxfUwCQzZG747YXBn1im9ww==", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bind-apply-helpers": "^1.0.0", + "es-define-property": "^1.0.0", + "get-intrinsic": "^1.2.4", + "set-function-length": "^1.2.2" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/call-bind-apply-helpers": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/call-bind-apply-helpers/-/call-bind-apply-helpers-1.0.1.tgz", + "integrity": "sha512-BhYE+WDaywFg2TBWYNXAE+8B1ATnThNBqXHP5nQu0jWJdVvY2hvkpyB3qOmtmDePiS5/BDQ8wASEWGMWRG148g==", + "dev": true, + "license": "MIT", + "dependencies": { + "es-errors": "^1.3.0", + "function-bind": "^1.1.2" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/call-bound": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/call-bound/-/call-bound-1.0.3.tgz", + "integrity": "sha512-YTd+6wGlNlPxSuri7Y6X8tY2dmm12UMH66RpKMhiX6rsk5wXXnYgbUcOt8kiS31/AjfoTOvCsE+w8nZQLQnzHA==", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bind-apply-helpers": "^1.0.1", + "get-intrinsic": "^1.2.6" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, "node_modules/callsites": { "version": "3.1.0", "resolved": "https://registry.npmjs.org/callsites/-/callsites-3.1.0.tgz", @@ -1966,9 +2484,9 @@ } }, "node_modules/caniuse-lite": { - "version": "1.0.30001689", - "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001689.tgz", - "integrity": "sha512-CmeR2VBycfa+5/jOfnp/NpWPGd06nf1XYiefUvhXFfZE4GkRc9jv+eGPS4nT558WS/8lYCzV8SlANCIPvbWP1g==", + "version": "1.0.30001690", + "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001690.tgz", + "integrity": "sha512-5ExiE3qQN6oF8Clf8ifIDcMRCRE/dMGcETG/XGMD8/XiXm6HXQgQTh1yZYLXXpSOsEUlJm1Xr7kGULZTuGtP/w==", "dev": true, "funding": [ { @@ -2096,6 +2614,60 @@ "node": ">= 8" } }, + "node_modules/data-view-buffer": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/data-view-buffer/-/data-view-buffer-1.0.2.tgz", + "integrity": "sha512-EmKO5V3OLXh1rtK2wgXRansaK1/mtVdTUEiEI0W8RkvgT05kfxaH29PliLnpLP73yYO6142Q72QNa8Wx/A5CqQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bound": "^1.0.3", + "es-errors": "^1.3.0", + "is-data-view": "^1.0.2" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/data-view-byte-length": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/data-view-byte-length/-/data-view-byte-length-1.0.2.tgz", + "integrity": "sha512-tuhGbE6CfTM9+5ANGf+oQb72Ky/0+s3xKUpHvShfiz2RxMFgFPjsXuRLBVMtvMs15awe45SRb83D6wH4ew6wlQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bound": "^1.0.3", + "es-errors": "^1.3.0", + "is-data-view": "^1.0.2" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/inspect-js" + } + }, + "node_modules/data-view-byte-offset": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/data-view-byte-offset/-/data-view-byte-offset-1.0.1.tgz", + "integrity": "sha512-BS8PfmtDGnrgYdOonGZQdLZslWIeCGFP9tpan0hi1Co2Zr2NKADsvGYA8XxuG/4UWgJ6Cjtv+YJnB6MM69QGlQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bound": "^1.0.2", + "es-errors": "^1.3.0", + "is-data-view": "^1.0.1" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, "node_modules/debug": { "version": "4.4.0", "resolved": "https://registry.npmjs.org/debug/-/debug-4.4.0.tgz", @@ -2141,6 +2713,42 @@ "dev": true, "license": "MIT" }, + "node_modules/define-data-property": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/define-data-property/-/define-data-property-1.1.4.tgz", + "integrity": "sha512-rBMvIzlpA8v6E+SJZoo++HAYqsLrkg7MSfIinMPFhmkorw7X+dOXVJQs+QT69zGkzMyfDnIMN2Wid1+NbL3T+A==", + "dev": true, + "license": "MIT", + "dependencies": { + "es-define-property": "^1.0.0", + "es-errors": "^1.3.0", + "gopd": "^1.0.1" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/define-properties": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/define-properties/-/define-properties-1.2.1.tgz", + "integrity": "sha512-8QmQKqEASLd5nx0U1B1okLElbUuuttJ/AnYmRXbbbGDWh6uS208EjD4Xqq/I9wK7u0v6O08XhTWnt5XtEbR6Dg==", + "dev": true, + "license": "MIT", + "dependencies": { + "define-data-property": "^1.0.1", + "has-property-descriptors": "^1.0.0", + "object-keys": "^1.1.1" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, "node_modules/doc-path": { "version": "4.1.1", "resolved": "https://registry.npmjs.org/doc-path/-/doc-path-4.1.1.tgz", @@ -2151,21 +2759,168 @@ "node": ">=16" } }, - "node_modules/duplexer": { - "version": "0.1.2", - "resolved": "https://registry.npmjs.org/duplexer/-/duplexer-0.1.2.tgz", - "integrity": "sha512-jtD6YG370ZCIi/9GTaJKQxWTZD045+4R4hTk/x1UyoqadyJ9x9CgSi1RlVDQF8U2sxLLSnFkCaMihqljHIWgMg==", + "node_modules/doctrine": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/doctrine/-/doctrine-3.0.0.tgz", + "integrity": "sha512-yS+Q5i3hBf7GBkd4KG8a7eBNNWNGLTaEwwYWUijIYM7zrlYDM0BFXHjjPWlWZ1Rg7UaddZeIDmi9jF3HmqiQ2w==", "dev": true, - "license": "MIT" + "license": "Apache-2.0", + "dependencies": { + "esutils": "^2.0.2" + }, + "engines": { + "node": ">=6.0.0" + } + }, + "node_modules/dunder-proto": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/dunder-proto/-/dunder-proto-1.0.1.tgz", + "integrity": "sha512-KIN/nDJBQRcXw0MLVhZE9iQHmG68qAVIBg9CqmUYjmQIhgij9U5MFvrqkUL5FbtyyzZuOeOt0zdeRe4UY7ct+A==", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bind-apply-helpers": "^1.0.1", + "es-errors": "^1.3.0", + "gopd": "^1.2.0" + }, + "engines": { + "node": ">= 0.4" + } }, "node_modules/electron-to-chromium": { - "version": "1.5.73", - "resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.5.73.tgz", - "integrity": "sha512-8wGNxG9tAG5KhGd3eeA0o6ixhiNdgr0DcHWm85XPCphwZgD1lIEoi6t3VERayWao7SF7AAZTw6oARGJeVjH8Kg==", + "version": "1.5.76", + "resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.5.76.tgz", + "integrity": "sha512-CjVQyG7n7Sr+eBXE86HIulnL5N8xZY1sgmOPGuq/F0Rr0FJq63lg0kEtOIDfZBk44FnDLf6FUJ+dsJcuiUDdDQ==", "dev": true, "license": "ISC", "peer": true }, + "node_modules/enhanced-resolve": { + "version": "5.18.0", + "resolved": "https://registry.npmjs.org/enhanced-resolve/-/enhanced-resolve-5.18.0.tgz", + "integrity": "sha512-0/r0MySGYG8YqlayBZ6MuCfECmHFdJ5qyPh8s8wa5Hnm6SaFLSK1VYCbj+NKp090Nm1caZhD+QTnmxO7esYGyQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "graceful-fs": "^4.2.4", + "tapable": "^2.2.0" + }, + "engines": { + "node": ">=10.13.0" + } + }, + "node_modules/es-abstract": { + "version": "1.23.8", + "resolved": "https://registry.npmjs.org/es-abstract/-/es-abstract-1.23.8.tgz", + "integrity": "sha512-lfab8IzDn6EpI1ibZakcgS6WsfEBiB+43cuJo+wgylx1xKXf+Sp+YR3vFuQwC/u3sxYwV8Cxe3B0DpVUu/WiJQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "array-buffer-byte-length": "^1.0.2", + "arraybuffer.prototype.slice": "^1.0.4", + "available-typed-arrays": "^1.0.7", + "call-bind": "^1.0.8", + "call-bound": "^1.0.3", + "data-view-buffer": "^1.0.2", + "data-view-byte-length": "^1.0.2", + "data-view-byte-offset": "^1.0.1", + "es-define-property": "^1.0.1", + "es-errors": "^1.3.0", + "es-object-atoms": "^1.0.0", + "es-set-tostringtag": "^2.0.3", + "es-to-primitive": "^1.3.0", + "function.prototype.name": "^1.1.8", + "get-intrinsic": "^1.2.6", + "get-symbol-description": "^1.1.0", + "globalthis": "^1.0.4", + "gopd": "^1.2.0", + "has-property-descriptors": "^1.0.2", + "has-proto": "^1.2.0", + "has-symbols": "^1.1.0", + "hasown": "^2.0.2", + "internal-slot": "^1.1.0", + "is-array-buffer": "^3.0.5", + "is-callable": "^1.2.7", + "is-data-view": "^1.0.2", + "is-regex": "^1.2.1", + "is-shared-array-buffer": "^1.0.4", + "is-string": "^1.1.1", + "is-typed-array": "^1.1.15", + "is-weakref": "^1.1.0", + "math-intrinsics": "^1.1.0", + "object-inspect": "^1.13.3", + "object-keys": "^1.1.1", + "object.assign": "^4.1.7", + "own-keys": "^1.0.0", + "regexp.prototype.flags": "^1.5.3", + "safe-array-concat": "^1.1.3", + "safe-push-apply": "^1.0.0", + "safe-regex-test": "^1.1.0", + "string.prototype.trim": "^1.2.10", + "string.prototype.trimend": "^1.0.9", + "string.prototype.trimstart": "^1.0.8", + "typed-array-buffer": "^1.0.3", + "typed-array-byte-length": "^1.0.3", + "typed-array-byte-offset": "^1.0.4", + "typed-array-length": "^1.0.7", + "unbox-primitive": "^1.1.0", + "which-typed-array": "^1.1.18" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/es-define-property": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/es-define-property/-/es-define-property-1.0.1.tgz", + "integrity": "sha512-e3nRfgfUZ4rNGL232gUgX06QNyyez04KdjFrF+LTRoOXmrOgFKDg4BCdsjW8EnT69eqdYGmRpJwiPVYNrCaW3g==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/es-errors": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/es-errors/-/es-errors-1.3.0.tgz", + "integrity": "sha512-Zf5H2Kxt2xjTvbJvP2ZWLEICxA6j+hAmMzIlypy4xcBg1vKVnx89Wy0GbS+kf5cwCVFFzdCFh2XSCFNULS6csw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/es-iterator-helpers": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/es-iterator-helpers/-/es-iterator-helpers-1.2.1.tgz", + "integrity": "sha512-uDn+FE1yrDzyC0pCo961B2IHbdM8y/ACZsKD4dG6WqrjV53BADjwa7D+1aom2rsNVfLyDgU/eigvlJGJ08OQ4w==", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bind": "^1.0.8", + "call-bound": "^1.0.3", + "define-properties": "^1.2.1", + "es-abstract": "^1.23.6", + "es-errors": "^1.3.0", + "es-set-tostringtag": "^2.0.3", + "function-bind": "^1.1.2", + "get-intrinsic": "^1.2.6", + "globalthis": "^1.0.4", + "gopd": "^1.2.0", + "has-property-descriptors": "^1.0.2", + "has-proto": "^1.2.0", + "has-symbols": "^1.1.0", + "internal-slot": "^1.1.0", + "iterator.prototype": "^1.1.4", + "safe-array-concat": "^1.1.3" + }, + "engines": { + "node": ">= 0.4" + } + }, "node_modules/es-module-lexer": { "version": "1.6.0", "resolved": "https://registry.npmjs.org/es-module-lexer/-/es-module-lexer-1.6.0.tgz", @@ -2173,6 +2928,62 @@ "dev": true, "license": "MIT" }, + "node_modules/es-object-atoms": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/es-object-atoms/-/es-object-atoms-1.0.0.tgz", + "integrity": "sha512-MZ4iQ6JwHOBQjahnjwaC1ZtIBH+2ohjamzAO3oaHcXYup7qxjF2fixyH+Q71voWHeOkI2q/TnJao/KfXYIZWbw==", + "dev": true, + "license": "MIT", + "dependencies": { + "es-errors": "^1.3.0" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/es-set-tostringtag": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/es-set-tostringtag/-/es-set-tostringtag-2.0.3.tgz", + "integrity": "sha512-3T8uNMC3OQTHkFUsFq8r/BwAXLHvU/9O9mE0fBc/MY5iq/8H7ncvO947LmYA6ldWw9Uh8Yhf25zu6n7nML5QWQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "get-intrinsic": "^1.2.4", + "has-tostringtag": "^1.0.2", + "hasown": "^2.0.1" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/es-shim-unscopables": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/es-shim-unscopables/-/es-shim-unscopables-1.0.2.tgz", + "integrity": "sha512-J3yBRXCzDu4ULnQwxyToo/OjdMx6akgVC7K6few0a7F/0wLtmKKN7I73AH5T2836UuXRqN7Qg+IIUw/+YJksRw==", + "dev": true, + "license": "MIT", + "dependencies": { + "hasown": "^2.0.0" + } + }, + "node_modules/es-to-primitive": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/es-to-primitive/-/es-to-primitive-1.3.0.tgz", + "integrity": "sha512-w+5mJ3GuFL+NjVtJlvydShqE1eN3h3PbI7/5LAsYJP/2qtuMXjfL2LpHSRqo4b4eSF5K/DH1JXKUAHSB2UW50g==", + "dev": true, + "license": "MIT", + "dependencies": { + "is-callable": "^1.2.7", + "is-date-object": "^1.0.5", + "is-symbol": "^1.0.4" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, "node_modules/esbuild": { "version": "0.21.5", "resolved": "https://registry.npmjs.org/esbuild/-/esbuild-0.21.5.tgz", @@ -2295,362 +3106,1344 @@ } } }, - "node_modules/eslint-scope": { - "version": "8.2.0", - "resolved": "https://registry.npmjs.org/eslint-scope/-/eslint-scope-8.2.0.tgz", - "integrity": "sha512-PHlWUfG6lvPc3yvP5A4PNyBL1W8fkDUccmI21JUu/+GKZBoH/W5u6usENXUrWFRsyoW5ACUjFGgAFQp5gUlb/A==", + "node_modules/eslint-compat-utils": { + "version": "0.5.1", + "resolved": "https://registry.npmjs.org/eslint-compat-utils/-/eslint-compat-utils-0.5.1.tgz", + "integrity": "sha512-3z3vFexKIEnjHE3zCMRo6fn/e44U7T1khUjg+Hp0ZQMCigh28rALD0nPFBcGZuiLC5rLZa2ubQHDRln09JfU2Q==", "dev": true, - "license": "BSD-2-Clause", + "license": "MIT", "dependencies": { - "esrecurse": "^4.3.0", - "estraverse": "^5.2.0" + "semver": "^7.5.4" }, "engines": { - "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + "node": ">=12" }, - "funding": { - "url": "https://opencollective.com/eslint" + "peerDependencies": { + "eslint": ">=6.0.0" } }, - "node_modules/eslint-visitor-keys": { - "version": "4.2.0", - "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-4.2.0.tgz", - "integrity": "sha512-UyLnSehNt62FFhSwjZlHmeokpRK59rcz29j+F1/aDgbkbRTk7wIc9XzdoasMUbRNKDM0qQt/+BJ4BrpFeABemw==", + "node_modules/eslint-compat-utils/node_modules/semver": { + "version": "7.6.3", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.6.3.tgz", + "integrity": "sha512-oVekP1cKtI+CTDvHWYFUcMtsK/00wmAEfyqKfNdARm8u1wNVhSgaX7A8d4UuIlUI5e84iEwOhs7ZPYRmzU9U6A==", "dev": true, - "license": "Apache-2.0", - "engines": { - "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + "license": "ISC", + "bin": { + "semver": "bin/semver.js" }, - "funding": { - "url": "https://opencollective.com/eslint" + "engines": { + "node": ">=10" } }, - "node_modules/eslint/node_modules/escape-string-regexp": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-4.0.0.tgz", - "integrity": "sha512-TtpcNJ3XAzx3Gq8sWRzJaVajRs0uVxA2YAkdb1jm2YkPz4G6egUFAyA3n5vtEIZefPk5Wa4UXbKuS5fKkJWdgA==", + "node_modules/eslint-import-resolver-node": { + "version": "0.3.9", + "resolved": "https://registry.npmjs.org/eslint-import-resolver-node/-/eslint-import-resolver-node-0.3.9.tgz", + "integrity": "sha512-WFj2isz22JahUv+B788TlO3N6zL3nNJGU8CcZbPZvVEkBPaJdCV4vy5wyghty5ROFbCRnm132v8BScu5/1BQ8g==", "dev": true, "license": "MIT", - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" + "dependencies": { + "debug": "^3.2.7", + "is-core-module": "^2.13.0", + "resolve": "^1.22.4" } }, - "node_modules/espree": { - "version": "10.3.0", - "resolved": "https://registry.npmjs.org/espree/-/espree-10.3.0.tgz", - "integrity": "sha512-0QYC8b24HWY8zjRnDTL6RiHfDbAWn63qb4LMj1Z4b076A4une81+z03Kg7l7mn/48PUTqoLptSXez8oknU8Clg==", + "node_modules/eslint-import-resolver-node/node_modules/debug": { + "version": "3.2.7", + "resolved": "https://registry.npmjs.org/debug/-/debug-3.2.7.tgz", + "integrity": "sha512-CFjzYYAi4ThfiQvizrFQevTTXHtnCqWfe7x1AhgEscTz6ZbLbfoLRLPugTQyBth6f8ZERVUSyWHFD/7Wu4t1XQ==", "dev": true, - "license": "BSD-2-Clause", + "license": "MIT", "dependencies": { - "acorn": "^8.14.0", - "acorn-jsx": "^5.3.2", - "eslint-visitor-keys": "^4.2.0" - }, - "engines": { - "node": "^18.18.0 || ^20.9.0 || >=21.1.0" - }, - "funding": { - "url": "https://opencollective.com/eslint" + "ms": "^2.1.1" } }, - "node_modules/esquery": { - "version": "1.6.0", - "resolved": "https://registry.npmjs.org/esquery/-/esquery-1.6.0.tgz", - "integrity": "sha512-ca9pw9fomFcKPvFLXhBKUK90ZvGibiGOvRJNbjljY7s7uq/5YO4BOzcYtJqExdx99rF6aAcnRxHmcUHcz6sQsg==", + "node_modules/eslint-import-resolver-typescript": { + "version": "3.7.0", + "resolved": "https://registry.npmjs.org/eslint-import-resolver-typescript/-/eslint-import-resolver-typescript-3.7.0.tgz", + "integrity": "sha512-Vrwyi8HHxY97K5ebydMtffsWAn1SCR9eol49eCd5fJS4O1WV7PaAjbcjmbfJJSMz/t4Mal212Uz/fQZrOB8mow==", "dev": true, - "license": "BSD-3-Clause", + "license": "ISC", "dependencies": { - "estraverse": "^5.1.0" + "@nolyfill/is-core-module": "1.0.39", + "debug": "^4.3.7", + "enhanced-resolve": "^5.15.0", + "fast-glob": "^3.3.2", + "get-tsconfig": "^4.7.5", + "is-bun-module": "^1.0.2", + "is-glob": "^4.0.3", + "stable-hash": "^0.0.4" }, "engines": { - "node": ">=0.10" + "node": "^14.18.0 || >=16.0.0" + }, + "funding": { + "url": "https://opencollective.com/unts/projects/eslint-import-resolver-ts" + }, + "peerDependencies": { + "eslint": "*", + "eslint-plugin-import": "*", + "eslint-plugin-import-x": "*" + }, + "peerDependenciesMeta": { + "eslint-plugin-import": { + "optional": true + }, + "eslint-plugin-import-x": { + "optional": true + } } }, - "node_modules/esrecurse": { - "version": "4.3.0", - "resolved": "https://registry.npmjs.org/esrecurse/-/esrecurse-4.3.0.tgz", - "integrity": "sha512-KmfKL3b6G+RXvP8N1vr3Tq1kL/oCFgn2NYXEtqP8/L3pKapUA4G8cFVaoF3SU323CD4XypR/ffioHmkti6/Tag==", + "node_modules/eslint-plugin-es-x": { + "version": "7.8.0", + "resolved": "https://registry.npmjs.org/eslint-plugin-es-x/-/eslint-plugin-es-x-7.8.0.tgz", + "integrity": "sha512-7Ds8+wAAoV3T+LAKeu39Y5BzXCrGKrcISfgKEqTS4BDN8SFEDQd0S43jiQ8vIa3wUKD07qitZdfzlenSi8/0qQ==", "dev": true, - "license": "BSD-2-Clause", + "funding": [ + "https://github.com/sponsors/ota-meshi", + "https://opencollective.com/eslint" + ], + "license": "MIT", "dependencies": { - "estraverse": "^5.2.0" + "@eslint-community/eslint-utils": "^4.1.2", + "@eslint-community/regexpp": "^4.11.0", + "eslint-compat-utils": "^0.5.1" }, "engines": { - "node": ">=4.0" + "node": "^14.18.0 || >=16.0.0" + }, + "peerDependencies": { + "eslint": ">=8" } }, - "node_modules/estraverse": { - "version": "5.3.0", - "resolved": "https://registry.npmjs.org/estraverse/-/estraverse-5.3.0.tgz", - "integrity": "sha512-MMdARuVEQziNTeJD8DgMqmhwR11BRQ/cBP+pLtYdSTnf3MIO8fFeiINEbX36ZdNlfU/7A9f3gUw49B3oQsvwBA==", + "node_modules/eslint-plugin-import-x": { + "version": "4.6.1", + "resolved": "https://registry.npmjs.org/eslint-plugin-import-x/-/eslint-plugin-import-x-4.6.1.tgz", + "integrity": "sha512-wluSUifMIb7UfwWXqx7Yx0lE/SGCcGXECLx/9bCmbY2nneLwvAZ4vkd1IXDjPKFvdcdUgr1BaRnaRpx3k2+Pfw==", "dev": true, - "license": "BSD-2-Clause", + "license": "MIT", + "dependencies": { + "@types/doctrine": "^0.0.9", + "@typescript-eslint/scope-manager": "^8.1.0", + "@typescript-eslint/utils": "^8.1.0", + "debug": "^4.3.4", + "doctrine": "^3.0.0", + "enhanced-resolve": "^5.17.1", + "eslint-import-resolver-node": "^0.3.9", + "get-tsconfig": "^4.7.3", + "is-glob": "^4.0.3", + "minimatch": "^9.0.3", + "semver": "^7.6.3", + "stable-hash": "^0.0.4", + "tslib": "^2.6.3" + }, "engines": { - "node": ">=4.0" + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "peerDependencies": { + "eslint": "^8.57.0 || ^9.0.0" } }, - "node_modules/estree-walker": { - "version": "3.0.3", - "resolved": "https://registry.npmjs.org/estree-walker/-/estree-walker-3.0.3.tgz", - "integrity": "sha512-7RUKfXgSMMkzt6ZuXmqapOurLGPPfgj6l9uRZ7lRGolvk0y2yocc35LdcxKC5PQZdn2DMqioAQ2NoWcrTKmm6g==", + "node_modules/eslint-plugin-import-x/node_modules/brace-expansion": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.0.1.tgz", + "integrity": "sha512-XnAIvQ8eM+kC6aULx6wuQiwVsnzsi9d3WxzV3FpWTGA19F621kwdbsAcFKXgKUHZWsy+mY6iL1sHTxWEFCytDA==", "dev": true, "license": "MIT", "dependencies": { - "@types/estree": "^1.0.0" + "balanced-match": "^1.0.0" } }, - "node_modules/esutils": { + "node_modules/eslint-plugin-import-x/node_modules/minimatch": { + "version": "9.0.5", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-9.0.5.tgz", + "integrity": "sha512-G6T0ZX48xgozx7587koeX9Ys2NYy6Gmv//P89sEte9V9whIapMNF4idKxnW2QtCcLiTWlb/wfCabAtAFWhhBow==", + "dev": true, + "license": "ISC", + "dependencies": { + "brace-expansion": "^2.0.1" + }, + "engines": { + "node": ">=16 || 14 >=14.17" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/eslint-plugin-import-x/node_modules/semver": { + "version": "7.6.3", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.6.3.tgz", + "integrity": "sha512-oVekP1cKtI+CTDvHWYFUcMtsK/00wmAEfyqKfNdARm8u1wNVhSgaX7A8d4UuIlUI5e84iEwOhs7ZPYRmzU9U6A==", + "dev": true, + "license": "ISC", + "bin": { + "semver": "bin/semver.js" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/eslint-plugin-n": { + "version": "17.15.1", + "resolved": "https://registry.npmjs.org/eslint-plugin-n/-/eslint-plugin-n-17.15.1.tgz", + "integrity": "sha512-KFw7x02hZZkBdbZEFQduRGH4VkIH4MW97ClsbAM4Y4E6KguBJWGfWG1P4HEIpZk2bkoWf0bojpnjNAhYQP8beA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@eslint-community/eslint-utils": "^4.4.1", + "enhanced-resolve": "^5.17.1", + "eslint-plugin-es-x": "^7.8.0", + "get-tsconfig": "^4.8.1", + "globals": "^15.11.0", + "ignore": "^5.3.2", + "minimatch": "^9.0.5", + "semver": "^7.6.3" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "url": "https://opencollective.com/eslint" + }, + "peerDependencies": { + "eslint": ">=8.23.0" + } + }, + "node_modules/eslint-plugin-n/node_modules/brace-expansion": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.0.1.tgz", + "integrity": "sha512-XnAIvQ8eM+kC6aULx6wuQiwVsnzsi9d3WxzV3FpWTGA19F621kwdbsAcFKXgKUHZWsy+mY6iL1sHTxWEFCytDA==", + "dev": true, + "license": "MIT", + "dependencies": { + "balanced-match": "^1.0.0" + } + }, + "node_modules/eslint-plugin-n/node_modules/minimatch": { + "version": "9.0.5", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-9.0.5.tgz", + "integrity": "sha512-G6T0ZX48xgozx7587koeX9Ys2NYy6Gmv//P89sEte9V9whIapMNF4idKxnW2QtCcLiTWlb/wfCabAtAFWhhBow==", + "dev": true, + "license": "ISC", + "dependencies": { + "brace-expansion": "^2.0.1" + }, + "engines": { + "node": ">=16 || 14 >=14.17" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/eslint-plugin-n/node_modules/semver": { + "version": "7.6.3", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.6.3.tgz", + "integrity": "sha512-oVekP1cKtI+CTDvHWYFUcMtsK/00wmAEfyqKfNdARm8u1wNVhSgaX7A8d4UuIlUI5e84iEwOhs7ZPYRmzU9U6A==", + "dev": true, + "license": "ISC", + "bin": { + "semver": "bin/semver.js" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/eslint-plugin-promise": { + "version": "7.2.1", + "resolved": "https://registry.npmjs.org/eslint-plugin-promise/-/eslint-plugin-promise-7.2.1.tgz", + "integrity": "sha512-SWKjd+EuvWkYaS+uN2csvj0KoP43YTu7+phKQ5v+xw6+A0gutVX2yqCeCkC3uLCJFiPfR2dD8Es5L7yUsmvEaA==", + "dev": true, + "license": "ISC", + "dependencies": { + "@eslint-community/eslint-utils": "^4.4.0" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "url": "https://opencollective.com/eslint" + }, + "peerDependencies": { + "eslint": "^7.0.0 || ^8.0.0 || ^9.0.0" + } + }, + "node_modules/eslint-plugin-react": { + "version": "7.37.3", + "resolved": "https://registry.npmjs.org/eslint-plugin-react/-/eslint-plugin-react-7.37.3.tgz", + "integrity": "sha512-DomWuTQPFYZwF/7c9W2fkKkStqZmBd3uugfqBYLdkZ3Hii23WzZuOLUskGxB8qkSKqftxEeGL1TB2kMhrce0jA==", + "dev": true, + "license": "MIT", + "dependencies": { + "array-includes": "^3.1.8", + "array.prototype.findlast": "^1.2.5", + "array.prototype.flatmap": "^1.3.3", + "array.prototype.tosorted": "^1.1.4", + "doctrine": "^2.1.0", + "es-iterator-helpers": "^1.2.1", + "estraverse": "^5.3.0", + "hasown": "^2.0.2", + "jsx-ast-utils": "^2.4.1 || ^3.0.0", + "minimatch": "^3.1.2", + "object.entries": "^1.1.8", + "object.fromentries": "^2.0.8", + "object.values": "^1.2.1", + "prop-types": "^15.8.1", + "resolve": "^2.0.0-next.5", + "semver": "^6.3.1", + "string.prototype.matchall": "^4.0.12", + "string.prototype.repeat": "^1.0.0" + }, + "engines": { + "node": ">=4" + }, + "peerDependencies": { + "eslint": "^3 || ^4 || ^5 || ^6 || ^7 || ^8 || ^9.7" + } + }, + "node_modules/eslint-plugin-react/node_modules/doctrine": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/doctrine/-/doctrine-2.1.0.tgz", + "integrity": "sha512-35mSku4ZXK0vfCuHEDAwt55dg2jNajHZ1odvF+8SSr82EsZY4QmXfuWso8oEd8zRhVObSN18aM0CjSdoBX7zIw==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "esutils": "^2.0.2" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/eslint-plugin-react/node_modules/resolve": { + "version": "2.0.0-next.5", + "resolved": "https://registry.npmjs.org/resolve/-/resolve-2.0.0-next.5.tgz", + "integrity": "sha512-U7WjGVG9sH8tvjW5SmGbQuui75FiyjAX72HX15DwBBwF9dNiQZRQAg9nnPhYy+TUnE0+VcrttuvNI8oSxZcocA==", + "dev": true, + "license": "MIT", + "dependencies": { + "is-core-module": "^2.13.0", + "path-parse": "^1.0.7", + "supports-preserve-symlinks-flag": "^1.0.0" + }, + "bin": { + "resolve": "bin/resolve" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/eslint-scope": { + "version": "8.2.0", + "resolved": "https://registry.npmjs.org/eslint-scope/-/eslint-scope-8.2.0.tgz", + "integrity": "sha512-PHlWUfG6lvPc3yvP5A4PNyBL1W8fkDUccmI21JUu/+GKZBoH/W5u6usENXUrWFRsyoW5ACUjFGgAFQp5gUlb/A==", + "dev": true, + "license": "BSD-2-Clause", + "dependencies": { + "esrecurse": "^4.3.0", + "estraverse": "^5.2.0" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "url": "https://opencollective.com/eslint" + } + }, + "node_modules/eslint-visitor-keys": { + "version": "4.2.0", + "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-4.2.0.tgz", + "integrity": "sha512-UyLnSehNt62FFhSwjZlHmeokpRK59rcz29j+F1/aDgbkbRTk7wIc9XzdoasMUbRNKDM0qQt/+BJ4BrpFeABemw==", + "dev": true, + "license": "Apache-2.0", + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "url": "https://opencollective.com/eslint" + } + }, + "node_modules/eslint/node_modules/escape-string-regexp": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-4.0.0.tgz", + "integrity": "sha512-TtpcNJ3XAzx3Gq8sWRzJaVajRs0uVxA2YAkdb1jm2YkPz4G6egUFAyA3n5vtEIZefPk5Wa4UXbKuS5fKkJWdgA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/espree": { + "version": "10.3.0", + "resolved": "https://registry.npmjs.org/espree/-/espree-10.3.0.tgz", + "integrity": "sha512-0QYC8b24HWY8zjRnDTL6RiHfDbAWn63qb4LMj1Z4b076A4une81+z03Kg7l7mn/48PUTqoLptSXez8oknU8Clg==", + "dev": true, + "license": "BSD-2-Clause", + "dependencies": { + "acorn": "^8.14.0", + "acorn-jsx": "^5.3.2", + "eslint-visitor-keys": "^4.2.0" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "url": "https://opencollective.com/eslint" + } + }, + "node_modules/esquery": { + "version": "1.6.0", + "resolved": "https://registry.npmjs.org/esquery/-/esquery-1.6.0.tgz", + "integrity": "sha512-ca9pw9fomFcKPvFLXhBKUK90ZvGibiGOvRJNbjljY7s7uq/5YO4BOzcYtJqExdx99rF6aAcnRxHmcUHcz6sQsg==", + "dev": true, + "license": "BSD-3-Clause", + "dependencies": { + "estraverse": "^5.1.0" + }, + "engines": { + "node": ">=0.10" + } + }, + "node_modules/esrecurse": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/esrecurse/-/esrecurse-4.3.0.tgz", + "integrity": "sha512-KmfKL3b6G+RXvP8N1vr3Tq1kL/oCFgn2NYXEtqP8/L3pKapUA4G8cFVaoF3SU323CD4XypR/ffioHmkti6/Tag==", + "dev": true, + "license": "BSD-2-Clause", + "dependencies": { + "estraverse": "^5.2.0" + }, + "engines": { + "node": ">=4.0" + } + }, + "node_modules/estraverse": { + "version": "5.3.0", + "resolved": "https://registry.npmjs.org/estraverse/-/estraverse-5.3.0.tgz", + "integrity": "sha512-MMdARuVEQziNTeJD8DgMqmhwR11BRQ/cBP+pLtYdSTnf3MIO8fFeiINEbX36ZdNlfU/7A9f3gUw49B3oQsvwBA==", + "dev": true, + "license": "BSD-2-Clause", + "engines": { + "node": ">=4.0" + } + }, + "node_modules/estree-walker": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/estree-walker/-/estree-walker-3.0.3.tgz", + "integrity": "sha512-7RUKfXgSMMkzt6ZuXmqapOurLGPPfgj6l9uRZ7lRGolvk0y2yocc35LdcxKC5PQZdn2DMqioAQ2NoWcrTKmm6g==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/estree": "^1.0.0" + } + }, + "node_modules/esutils": { "version": "2.0.3", "resolved": "https://registry.npmjs.org/esutils/-/esutils-2.0.3.tgz", "integrity": "sha512-kVscqXk4OCp68SZ0dkgEKVi6/8ij300KBWTJq32P/dYeWTSwK41WyTxalN1eRmA5Z9UU/LX9D7FWSmV9SAYx6g==", "dev": true, - "license": "BSD-2-Clause", + "license": "BSD-2-Clause", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/expect-type": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/expect-type/-/expect-type-1.1.0.tgz", + "integrity": "sha512-bFi65yM+xZgk+u/KRIpekdSYkTB5W1pEf0Lt8Q8Msh7b+eQ7LXVtIB1Bkm4fvclDEL1b2CZkMhv2mOeF8tMdkA==", + "dev": true, + "license": "Apache-2.0", + "engines": { + "node": ">=12.0.0" + } + }, + "node_modules/fast-deep-equal": { + "version": "3.1.3", + "resolved": "https://registry.npmjs.org/fast-deep-equal/-/fast-deep-equal-3.1.3.tgz", + "integrity": "sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q==", + "dev": true, + "license": "MIT" + }, + "node_modules/fast-glob": { + "version": "3.3.2", + "resolved": "https://registry.npmjs.org/fast-glob/-/fast-glob-3.3.2.tgz", + "integrity": "sha512-oX2ruAFQwf/Orj8m737Y5adxDQO0LAB7/S5MnxCdTNDd4p6BsyIVsv9JQsATbTSq8KHRpLwIHbVlUNatxd+1Ow==", + "dev": true, + "license": "MIT", + "dependencies": { + "@nodelib/fs.stat": "^2.0.2", + "@nodelib/fs.walk": "^1.2.3", + "glob-parent": "^5.1.2", + "merge2": "^1.3.0", + "micromatch": "^4.0.4" + }, + "engines": { + "node": ">=8.6.0" + } + }, + "node_modules/fast-glob/node_modules/glob-parent": { + "version": "5.1.2", + "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-5.1.2.tgz", + "integrity": "sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow==", + "dev": true, + "license": "ISC", + "dependencies": { + "is-glob": "^4.0.1" + }, + "engines": { + "node": ">= 6" + } + }, + "node_modules/fast-json-stable-stringify": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/fast-json-stable-stringify/-/fast-json-stable-stringify-2.1.0.tgz", + "integrity": "sha512-lhd/wF+Lk98HZoTCtlVraHtfh5XYijIjalXck7saUtuanSDyLMxnHhSXEDJqHxD7msR8D0uCmqlkwjCV8xvwHw==", + "dev": true, + "license": "MIT" + }, + "node_modules/fast-levenshtein": { + "version": "2.0.6", + "resolved": "https://registry.npmjs.org/fast-levenshtein/-/fast-levenshtein-2.0.6.tgz", + "integrity": "sha512-DCXu6Ifhqcks7TZKY3Hxp3y6qphY5SJZmrWMDrKcERSOXWQdMhU9Ig/PYrzyw/ul9jOIyh0N4M0tbC5hodg8dw==", + "dev": true, + "license": "MIT" + }, + "node_modules/fastq": { + "version": "1.18.0", + "resolved": "https://registry.npmjs.org/fastq/-/fastq-1.18.0.tgz", + "integrity": "sha512-QKHXPW0hD8g4UET03SdOdunzSouc9N4AuHdsX8XNcTsuz+yYFILVNIX4l9yHABMhiEI9Db0JTTIpu0wB+Y1QQw==", + "dev": true, + "license": "ISC", + "dependencies": { + "reusify": "^1.0.4" + } + }, + "node_modules/file-entry-cache": { + "version": "8.0.0", + "resolved": "https://registry.npmjs.org/file-entry-cache/-/file-entry-cache-8.0.0.tgz", + "integrity": "sha512-XXTUwCvisa5oacNGRP9SfNtYBNAMi+RPwBFmblZEF7N7swHYQS6/Zfk7SRwx4D5j3CH211YNRco1DEMNVfZCnQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "flat-cache": "^4.0.0" + }, + "engines": { + "node": ">=16.0.0" + } + }, + "node_modules/fill-range": { + "version": "7.1.1", + "resolved": "https://registry.npmjs.org/fill-range/-/fill-range-7.1.1.tgz", + "integrity": "sha512-YsGpe3WHLK8ZYi4tWDg2Jy3ebRz2rXowDxnld4bkQB00cc/1Zw9AWnC0i9ztDJitivtQvaI9KaLyKrc+hBW0yg==", + "dev": true, + "license": "MIT", + "dependencies": { + "to-regex-range": "^5.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/find-up": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/find-up/-/find-up-5.0.0.tgz", + "integrity": "sha512-78/PXT1wlLLDgTzDs7sjq9hzz0vXD+zn+7wypEe4fXQxCmdmqfGsEPQxmiCSQI3ajFV91bVSsvNtrJRiW6nGng==", + "dev": true, + "license": "MIT", + "dependencies": { + "locate-path": "^6.0.0", + "path-exists": "^4.0.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/flat-cache": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/flat-cache/-/flat-cache-4.0.1.tgz", + "integrity": "sha512-f7ccFPK3SXFHpx15UIGyRJ/FJQctuKZ0zVuN3frBo4HnK3cay9VEW0R6yPYFHC0AgqhukPzKjq22t5DmAyqGyw==", + "dev": true, + "license": "MIT", + "dependencies": { + "flatted": "^3.2.9", + "keyv": "^4.5.4" + }, + "engines": { + "node": ">=16" + } + }, + "node_modules/flatted": { + "version": "3.3.2", + "resolved": "https://registry.npmjs.org/flatted/-/flatted-3.3.2.tgz", + "integrity": "sha512-AiwGJM8YcNOaobumgtng+6NHuOqC3A7MixFeDafM3X9cIUM+xUXoS5Vfgf+OihAYe20fxqNM9yPBXJzRtZ/4eA==", + "dev": true, + "license": "ISC" + }, + "node_modules/for-each": { + "version": "0.3.3", + "resolved": "https://registry.npmjs.org/for-each/-/for-each-0.3.3.tgz", + "integrity": "sha512-jqYfLp7mo9vIyQf8ykW2v7A+2N4QjeCeI5+Dz9XraiO1ign81wjiH7Fb9vSOWvQfNtmSa4H2RoQTrrXivdUZmw==", + "dev": true, + "license": "MIT", + "dependencies": { + "is-callable": "^1.1.3" + } + }, + "node_modules/fsevents": { + "version": "2.3.3", + "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.3.tgz", + "integrity": "sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw==", + "dev": true, + "hasInstallScript": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": "^8.16.0 || ^10.6.0 || >=11.0.0" + } + }, + "node_modules/function-bind": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/function-bind/-/function-bind-1.1.2.tgz", + "integrity": "sha512-7XHNxH7qX9xG5mIwxkhumTox/MIRNcOgDrxWsMt2pAr23WHp6MrRlN7FBSFpCpr+oVO0F744iUgR82nJMfG2SA==", + "dev": true, + "license": "MIT", + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/function.prototype.name": { + "version": "1.1.8", + "resolved": "https://registry.npmjs.org/function.prototype.name/-/function.prototype.name-1.1.8.tgz", + "integrity": "sha512-e5iwyodOHhbMr/yNrc7fDYG4qlbIvI5gajyzPnb5TCwyhjApznQh1BMFou9b30SevY43gCJKXycoCBjMbsuW0Q==", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bind": "^1.0.8", + "call-bound": "^1.0.3", + "define-properties": "^1.2.1", + "functions-have-names": "^1.2.3", + "hasown": "^2.0.2", + "is-callable": "^1.2.7" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/functions-have-names": { + "version": "1.2.3", + "resolved": "https://registry.npmjs.org/functions-have-names/-/functions-have-names-1.2.3.tgz", + "integrity": "sha512-xckBUXyTIqT97tq2x2AMb+g163b5JFysYk0x4qxNFwbfQkmNZoiRHb6sPzI9/QV33WeuvVYBUIiD4NzNIyqaRQ==", + "dev": true, + "license": "MIT", + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/gensync": { + "version": "1.0.0-beta.2", + "resolved": "https://registry.npmjs.org/gensync/-/gensync-1.0.0-beta.2.tgz", + "integrity": "sha512-3hN7NaskYvMDLQY55gnW3NQ+mesEAepTqlg+VEbj7zzqEMBVNhzcGYYeqFo/TlYz6eQiFcp1HcsCZO+nGgS8zg==", + "dev": true, + "license": "MIT", + "peer": true, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/get-intrinsic": { + "version": "1.2.6", + "resolved": "https://registry.npmjs.org/get-intrinsic/-/get-intrinsic-1.2.6.tgz", + "integrity": "sha512-qxsEs+9A+u85HhllWJJFicJfPDhRmjzoYdl64aMWW9yRIJmSyxdn8IEkuIM530/7T+lv0TIHd8L6Q/ra0tEoeA==", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bind-apply-helpers": "^1.0.1", + "dunder-proto": "^1.0.0", + "es-define-property": "^1.0.1", + "es-errors": "^1.3.0", + "es-object-atoms": "^1.0.0", + "function-bind": "^1.1.2", + "gopd": "^1.2.0", + "has-symbols": "^1.1.0", + "hasown": "^2.0.2", + "math-intrinsics": "^1.0.0" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/get-symbol-description": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/get-symbol-description/-/get-symbol-description-1.1.0.tgz", + "integrity": "sha512-w9UMqWwJxHNOvoNzSJ2oPF5wvYcvP7jUvYzhp67yEhTi17ZDBBC1z9pTdGuzjD+EFIqLSYRweZjqfiPzQ06Ebg==", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bound": "^1.0.3", + "es-errors": "^1.3.0", + "get-intrinsic": "^1.2.6" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/get-tsconfig": { + "version": "4.8.1", + "resolved": "https://registry.npmjs.org/get-tsconfig/-/get-tsconfig-4.8.1.tgz", + "integrity": "sha512-k9PN+cFBmaLWtVz29SkUoqU5O0slLuHJXt/2P+tMVFT+phsSGXGkp9t3rQIqdz0e+06EHNGs3oM6ZX1s2zHxRg==", + "dev": true, + "license": "MIT", + "dependencies": { + "resolve-pkg-maps": "^1.0.0" + }, + "funding": { + "url": "https://github.com/privatenumber/get-tsconfig?sponsor=1" + } + }, + "node_modules/glob-parent": { + "version": "6.0.2", + "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-6.0.2.tgz", + "integrity": "sha512-XxwI8EOhVQgWp6iDL+3b0r86f4d6AX6zSU55HfB4ydCEuXLXc5FcYeOu+nnGftS4TEju/11rt4KJPTMgbfmv4A==", + "dev": true, + "license": "ISC", + "dependencies": { + "is-glob": "^4.0.3" + }, + "engines": { + "node": ">=10.13.0" + } + }, + "node_modules/globals": { + "version": "15.13.0", + "resolved": "https://registry.npmjs.org/globals/-/globals-15.13.0.tgz", + "integrity": "sha512-49TewVEz0UxZjr1WYYsWpPrhyC/B/pA8Bq0fUmet2n+eR7yn0IvNzNaoBwnK6mdkzcN+se7Ez9zUgULTz2QH4g==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/globalthis": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/globalthis/-/globalthis-1.0.4.tgz", + "integrity": "sha512-DpLKbNU4WylpxJykQujfCcwYWiV/Jhm50Goo0wrVILAv5jOr9d+H+UR3PhSCD2rCCEIg0uc+G+muBTwD54JhDQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "define-properties": "^1.2.1", + "gopd": "^1.0.1" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/gopd": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/gopd/-/gopd-1.2.0.tgz", + "integrity": "sha512-ZUKRh6/kUFoAiTAtTYPZJ3hw9wNxx+BIBOijnlG9PnrJsCcSjs1wyyD6vJpaYtgnzDrKYRSqf3OO6Rfa93xsRg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/graceful-fs": { + "version": "4.2.11", + "resolved": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-4.2.11.tgz", + "integrity": "sha512-RbJ5/jmFcNNCcDV5o9eTnBLJ/HszWV0P73bc+Ff4nS/rJj+YaS6IGyiOL0VoBYX+l1Wrl3k63h/KrH+nhJ0XvQ==", + "dev": true, + "license": "ISC" + }, + "node_modules/graphemer": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/graphemer/-/graphemer-1.4.0.tgz", + "integrity": "sha512-EtKwoO6kxCL9WO5xipiHTZlSzBm7WLT627TqC/uVRd0HKmq8NXyebnNYxDoBi7wt8eTWrUrKXCOVaFq9x1kgag==", + "dev": true, + "license": "MIT" + }, + "node_modules/has-bigints": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/has-bigints/-/has-bigints-1.1.0.tgz", + "integrity": "sha512-R3pbpkcIqv2Pm3dUwgjclDRVmWpTJW2DcMzcIhEXEx1oh/CEMObMm3KLmRJOdvhM7o4uQBnwr8pzRK2sJWIqfg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/has-flag": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", + "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/has-property-descriptors": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/has-property-descriptors/-/has-property-descriptors-1.0.2.tgz", + "integrity": "sha512-55JNKuIW+vq4Ke1BjOTjM2YctQIvCT7GFzHwmfZPGo5wnrgkid0YQtnAleFSqumZm4az3n2BS+erby5ipJdgrg==", + "dev": true, + "license": "MIT", + "dependencies": { + "es-define-property": "^1.0.0" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/has-proto": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/has-proto/-/has-proto-1.2.0.tgz", + "integrity": "sha512-KIL7eQPfHQRC8+XluaIw7BHUwwqL19bQn4hzNgdr+1wXoU0KKj6rufu47lhY7KbJR2C6T6+PfyN0Ea7wkSS+qQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "dunder-proto": "^1.0.0" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/has-symbols": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/has-symbols/-/has-symbols-1.1.0.tgz", + "integrity": "sha512-1cDNdwJ2Jaohmb3sg4OmKaMBwuC48sYni5HUw2DvsC8LjGTLK9h+eb1X6RyuOHe4hT0ULCW68iomhjUoKUqlPQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/has-tostringtag": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/has-tostringtag/-/has-tostringtag-1.0.2.tgz", + "integrity": "sha512-NqADB8VjPFLM2V0VvHUewwwsw0ZWBaIdgo+ieHtK3hasLz4qeCRjYcqfB6AQrBggRKppKF8L52/VqdVsO47Dlw==", + "dev": true, + "license": "MIT", + "dependencies": { + "has-symbols": "^1.0.3" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/hasown": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/hasown/-/hasown-2.0.2.tgz", + "integrity": "sha512-0hJU9SCPvmMzIBdZFqNPXWa6dqh7WdH0cII9y+CyS8rG3nL48Bclra9HmKhVVUHyPWNH5Y7xDwAB7bfgSjkUMQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "function-bind": "^1.1.2" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/ignore": { + "version": "5.3.2", + "resolved": "https://registry.npmjs.org/ignore/-/ignore-5.3.2.tgz", + "integrity": "sha512-hsBTNUqQTDwkWtcdYI2i06Y/nUBEsNEDJKjWdigLvegy8kDuJAS8uRlpkkcQpyEXL0Z/pjDy5HBmMjRCJ2gq+g==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 4" + } + }, + "node_modules/import-fresh": { + "version": "3.3.0", + "resolved": "https://registry.npmjs.org/import-fresh/-/import-fresh-3.3.0.tgz", + "integrity": "sha512-veYYhQa+D1QBKznvhUHxb8faxlrwUnxseDAbAp457E0wLNio2bOSKnjYDhMj+YiAq61xrMGhQk9iXVk5FzgQMw==", + "dev": true, + "license": "MIT", + "dependencies": { + "parent-module": "^1.0.0", + "resolve-from": "^4.0.0" + }, + "engines": { + "node": ">=6" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/imurmurhash": { + "version": "0.1.4", + "resolved": "https://registry.npmjs.org/imurmurhash/-/imurmurhash-0.1.4.tgz", + "integrity": "sha512-JmXMZ6wuvDmLiHEml9ykzqO6lwFbof0GG4IkcGaENdCRDDmMVnny7s5HsIgHCbaq0w2MyPhDqkhTUgS2LU2PHA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.8.19" + } + }, + "node_modules/indent-string": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/indent-string/-/indent-string-5.0.0.tgz", + "integrity": "sha512-m6FAo/spmsW2Ab2fU35JTYwtOKa2yAwXSwgjSv1TJzh4Mh7mC3lzAOVLBprb72XsTrgkEIsl7YrFNAiDiRhIGg==", + "dev": true, + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/internal-slot": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/internal-slot/-/internal-slot-1.1.0.tgz", + "integrity": "sha512-4gd7VpWNQNB4UKKCFFVcp1AVv+FMOgs9NKzjHKusc8jTMhd5eL1NqQqOpE0KzMds804/yHlglp3uxgluOqAPLw==", + "dev": true, + "license": "MIT", + "dependencies": { + "es-errors": "^1.3.0", + "hasown": "^2.0.2", + "side-channel": "^1.1.0" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/is-array-buffer": { + "version": "3.0.5", + "resolved": "https://registry.npmjs.org/is-array-buffer/-/is-array-buffer-3.0.5.tgz", + "integrity": "sha512-DDfANUiiG2wC1qawP66qlTugJeL5HyzMpfr8lLK+jMQirGzNod0B12cFB/9q838Ru27sBwfw78/rdoU7RERz6A==", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bind": "^1.0.8", + "call-bound": "^1.0.3", + "get-intrinsic": "^1.2.6" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-async-function": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/is-async-function/-/is-async-function-2.0.0.tgz", + "integrity": "sha512-Y1JXKrfykRJGdlDwdKlLpLyMIiWqWvuSd17TvZk68PLAOGOoF4Xyav1z0Xhoi+gCYjZVeC5SI+hYFOfvXmGRCA==", + "dev": true, + "license": "MIT", + "dependencies": { + "has-tostringtag": "^1.0.0" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-bigint": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/is-bigint/-/is-bigint-1.1.0.tgz", + "integrity": "sha512-n4ZT37wG78iz03xPRKJrHTdZbe3IicyucEtdRsV5yglwc3GyUfbAfpSeD0FJ41NbUNSt5wbhqfp1fS+BgnvDFQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "has-bigints": "^1.0.2" + }, "engines": { - "node": ">=0.10.0" + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" } }, - "node_modules/event-stream": { - "version": "4.0.1", - "resolved": "https://registry.npmjs.org/event-stream/-/event-stream-4.0.1.tgz", - "integrity": "sha512-qACXdu/9VHPBzcyhdOWR5/IahhGMf0roTeZJfzz077GwylcDd90yOHLouhmv7GJ5XzPi6ekaQWd8AvPP2nOvpA==", + "node_modules/is-boolean-object": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/is-boolean-object/-/is-boolean-object-1.2.1.tgz", + "integrity": "sha512-l9qO6eFlUETHtuihLcYOaLKByJ1f+N4kthcU9YjHy3N+B3hWv0y/2Nd0mu/7lTFnRQHTrSdXF50HQ3bl5fEnng==", "dev": true, "license": "MIT", "dependencies": { - "duplexer": "^0.1.1", - "from": "^0.1.7", - "map-stream": "0.0.7", - "pause-stream": "^0.0.11", - "split": "^1.0.1", - "stream-combiner": "^0.2.2", - "through": "^2.3.8" + "call-bound": "^1.0.2", + "has-tostringtag": "^1.0.2" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" } }, - "node_modules/expect-type": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/expect-type/-/expect-type-1.1.0.tgz", - "integrity": "sha512-bFi65yM+xZgk+u/KRIpekdSYkTB5W1pEf0Lt8Q8Msh7b+eQ7LXVtIB1Bkm4fvclDEL1b2CZkMhv2mOeF8tMdkA==", + "node_modules/is-bun-module": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/is-bun-module/-/is-bun-module-1.3.0.tgz", + "integrity": "sha512-DgXeu5UWI0IsMQundYb5UAOzm6G2eVnarJ0byP6Tm55iZNKceD59LNPA2L4VvsScTtHcw0yEkVwSf7PC+QoLSA==", "dev": true, - "license": "Apache-2.0", + "license": "MIT", + "dependencies": { + "semver": "^7.6.3" + } + }, + "node_modules/is-bun-module/node_modules/semver": { + "version": "7.6.3", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.6.3.tgz", + "integrity": "sha512-oVekP1cKtI+CTDvHWYFUcMtsK/00wmAEfyqKfNdARm8u1wNVhSgaX7A8d4UuIlUI5e84iEwOhs7ZPYRmzU9U6A==", + "dev": true, + "license": "ISC", + "bin": { + "semver": "bin/semver.js" + }, "engines": { - "node": ">=12.0.0" + "node": ">=10" } }, - "node_modules/fast-deep-equal": { - "version": "3.1.3", - "resolved": "https://registry.npmjs.org/fast-deep-equal/-/fast-deep-equal-3.1.3.tgz", - "integrity": "sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q==", + "node_modules/is-callable": { + "version": "1.2.7", + "resolved": "https://registry.npmjs.org/is-callable/-/is-callable-1.2.7.tgz", + "integrity": "sha512-1BC0BVFhS/p0qtw6enp8e+8OD0UrK0oFLztSjNzhcKA3WDuJxxAPXzPuPtKkjEY9UUoEWlX/8fgKeu2S8i9JTA==", "dev": true, - "license": "MIT" + "license": "MIT", + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } }, - "node_modules/fast-json-stable-stringify": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/fast-json-stable-stringify/-/fast-json-stable-stringify-2.1.0.tgz", - "integrity": "sha512-lhd/wF+Lk98HZoTCtlVraHtfh5XYijIjalXck7saUtuanSDyLMxnHhSXEDJqHxD7msR8D0uCmqlkwjCV8xvwHw==", + "node_modules/is-core-module": { + "version": "2.16.1", + "resolved": "https://registry.npmjs.org/is-core-module/-/is-core-module-2.16.1.tgz", + "integrity": "sha512-UfoeMA6fIJ8wTYFEUjelnaGI67v6+N7qXJEvQuIGa99l4xsCruSYOVSQ0uPANn4dAzm8lkYPaKLrrijLq7x23w==", "dev": true, - "license": "MIT" + "license": "MIT", + "dependencies": { + "hasown": "^2.0.2" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } }, - "node_modules/fast-levenshtein": { - "version": "2.0.6", - "resolved": "https://registry.npmjs.org/fast-levenshtein/-/fast-levenshtein-2.0.6.tgz", - "integrity": "sha512-DCXu6Ifhqcks7TZKY3Hxp3y6qphY5SJZmrWMDrKcERSOXWQdMhU9Ig/PYrzyw/ul9jOIyh0N4M0tbC5hodg8dw==", + "node_modules/is-data-view": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/is-data-view/-/is-data-view-1.0.2.tgz", + "integrity": "sha512-RKtWF8pGmS87i2D6gqQu/l7EYRlVdfzemCJN/P3UOs//x1QE7mfhvzHIApBTRf7axvT6DMGwSwBXYCT0nfB9xw==", "dev": true, - "license": "MIT" + "license": "MIT", + "dependencies": { + "call-bound": "^1.0.2", + "get-intrinsic": "^1.2.6", + "is-typed-array": "^1.1.13" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } }, - "node_modules/file-entry-cache": { - "version": "8.0.0", - "resolved": "https://registry.npmjs.org/file-entry-cache/-/file-entry-cache-8.0.0.tgz", - "integrity": "sha512-XXTUwCvisa5oacNGRP9SfNtYBNAMi+RPwBFmblZEF7N7swHYQS6/Zfk7SRwx4D5j3CH211YNRco1DEMNVfZCnQ==", + "node_modules/is-date-object": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/is-date-object/-/is-date-object-1.1.0.tgz", + "integrity": "sha512-PwwhEakHVKTdRNVOw+/Gyh0+MzlCl4R6qKvkhuvLtPMggI1WAHt9sOwZxQLSGpUaDnrdyDsomoRgNnCfKNSXXg==", "dev": true, "license": "MIT", "dependencies": { - "flat-cache": "^4.0.0" + "call-bound": "^1.0.2", + "has-tostringtag": "^1.0.2" }, "engines": { - "node": ">=16.0.0" + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" } }, - "node_modules/find-up": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/find-up/-/find-up-5.0.0.tgz", - "integrity": "sha512-78/PXT1wlLLDgTzDs7sjq9hzz0vXD+zn+7wypEe4fXQxCmdmqfGsEPQxmiCSQI3ajFV91bVSsvNtrJRiW6nGng==", + "node_modules/is-extglob": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/is-extglob/-/is-extglob-2.1.1.tgz", + "integrity": "sha512-SbKbANkN603Vi4jEZv49LeVJMn4yGwsbzZworEoyEiutsN3nJYdbO36zfhGJ6QEDpOZIFkDtnq5JRxmvl3jsoQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/is-finalizationregistry": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/is-finalizationregistry/-/is-finalizationregistry-1.1.1.tgz", + "integrity": "sha512-1pC6N8qWJbWoPtEjgcL2xyhQOP491EQjeUo3qTKcmV8YSDDJrOepfG8pcC7h/QgnQHYSv0mJ3Z/ZWxmatVrysg==", "dev": true, "license": "MIT", "dependencies": { - "locate-path": "^6.0.0", - "path-exists": "^4.0.0" + "call-bound": "^1.0.3" }, "engines": { - "node": ">=10" + "node": ">= 0.4" }, "funding": { - "url": "https://github.com/sponsors/sindresorhus" + "url": "https://github.com/sponsors/ljharb" } }, - "node_modules/flat-cache": { - "version": "4.0.1", - "resolved": "https://registry.npmjs.org/flat-cache/-/flat-cache-4.0.1.tgz", - "integrity": "sha512-f7ccFPK3SXFHpx15UIGyRJ/FJQctuKZ0zVuN3frBo4HnK3cay9VEW0R6yPYFHC0AgqhukPzKjq22t5DmAyqGyw==", + "node_modules/is-generator-function": { + "version": "1.0.10", + "resolved": "https://registry.npmjs.org/is-generator-function/-/is-generator-function-1.0.10.tgz", + "integrity": "sha512-jsEjy9l3yiXEQ+PsXdmBwEPcOxaXWLspKdplFUVI9vq1iZgIekeC0L167qeu86czQaxed3q/Uzuw0swL0irL8A==", "dev": true, "license": "MIT", "dependencies": { - "flatted": "^3.2.9", - "keyv": "^4.5.4" + "has-tostringtag": "^1.0.0" }, "engines": { - "node": ">=16" + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" } }, - "node_modules/flatted": { - "version": "3.3.2", - "resolved": "https://registry.npmjs.org/flatted/-/flatted-3.3.2.tgz", - "integrity": "sha512-AiwGJM8YcNOaobumgtng+6NHuOqC3A7MixFeDafM3X9cIUM+xUXoS5Vfgf+OihAYe20fxqNM9yPBXJzRtZ/4eA==", + "node_modules/is-glob": { + "version": "4.0.3", + "resolved": "https://registry.npmjs.org/is-glob/-/is-glob-4.0.3.tgz", + "integrity": "sha512-xelSayHH36ZgE7ZWhli7pW34hNbNl8Ojv5KVmkJD4hBdD3th8Tfk9vYasLM+mXWOZhFkgZfxhLSnrwRr4elSSg==", "dev": true, - "license": "ISC" + "license": "MIT", + "dependencies": { + "is-extglob": "^2.1.1" + }, + "engines": { + "node": ">=0.10.0" + } }, - "node_modules/from": { - "version": "0.1.7", - "resolved": "https://registry.npmjs.org/from/-/from-0.1.7.tgz", - "integrity": "sha512-twe20eF1OxVxp/ML/kq2p1uc6KvFK/+vs8WjEbeKmV2He22MKm7YF2ANIt+EOqhJ5L3K/SuuPhk0hWQDjOM23g==", + "node_modules/is-map": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/is-map/-/is-map-2.0.3.tgz", + "integrity": "sha512-1Qed0/Hr2m+YqxnM09CjA2d/i6YZNfF6R2oRAOj36eUdS6qIV/huPJNSEpKbupewFs+ZsJlxsjjPbc0/afW6Lw==", "dev": true, - "license": "MIT" + "license": "MIT", + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } }, - "node_modules/fsevents": { - "version": "2.3.3", - "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.3.tgz", - "integrity": "sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw==", + "node_modules/is-number": { + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/is-number/-/is-number-7.0.0.tgz", + "integrity": "sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng==", "dev": true, - "hasInstallScript": true, "license": "MIT", - "optional": true, - "os": [ - "darwin" - ], "engines": { - "node": "^8.16.0 || ^10.6.0 || >=11.0.0" + "node": ">=0.12.0" } }, - "node_modules/gensync": { - "version": "1.0.0-beta.2", - "resolved": "https://registry.npmjs.org/gensync/-/gensync-1.0.0-beta.2.tgz", - "integrity": "sha512-3hN7NaskYvMDLQY55gnW3NQ+mesEAepTqlg+VEbj7zzqEMBVNhzcGYYeqFo/TlYz6eQiFcp1HcsCZO+nGgS8zg==", + "node_modules/is-number-object": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/is-number-object/-/is-number-object-1.1.1.tgz", + "integrity": "sha512-lZhclumE1G6VYD8VHe35wFaIif+CTy5SJIi5+3y4psDgWu4wPDoBhF8NxUOinEc7pHgiTsT6MaBb92rKhhD+Xw==", "dev": true, "license": "MIT", - "peer": true, + "dependencies": { + "call-bound": "^1.0.3", + "has-tostringtag": "^1.0.2" + }, "engines": { - "node": ">=6.9.0" + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" } }, - "node_modules/glob-parent": { - "version": "6.0.2", - "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-6.0.2.tgz", - "integrity": "sha512-XxwI8EOhVQgWp6iDL+3b0r86f4d6AX6zSU55HfB4ydCEuXLXc5FcYeOu+nnGftS4TEju/11rt4KJPTMgbfmv4A==", + "node_modules/is-regex": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/is-regex/-/is-regex-1.2.1.tgz", + "integrity": "sha512-MjYsKHO5O7mCsmRGxWcLWheFqN9DJ/2TmngvjKXihe6efViPqc274+Fx/4fYj/r03+ESvBdTXK0V6tA3rgez1g==", "dev": true, - "license": "ISC", + "license": "MIT", "dependencies": { - "is-glob": "^4.0.3" + "call-bound": "^1.0.2", + "gopd": "^1.2.0", + "has-tostringtag": "^1.0.2", + "hasown": "^2.0.2" }, "engines": { - "node": ">=10.13.0" + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" } }, - "node_modules/globals": { - "version": "15.13.0", - "resolved": "https://registry.npmjs.org/globals/-/globals-15.13.0.tgz", - "integrity": "sha512-49TewVEz0UxZjr1WYYsWpPrhyC/B/pA8Bq0fUmet2n+eR7yn0IvNzNaoBwnK6mdkzcN+se7Ez9zUgULTz2QH4g==", + "node_modules/is-set": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/is-set/-/is-set-2.0.3.tgz", + "integrity": "sha512-iPAjerrse27/ygGLxw+EBR9agv9Y6uLeYVJMu+QNCoouJ1/1ri0mGrcWpfCqFZuzzx3WjtwxG098X+n4OuRkPg==", "dev": true, "license": "MIT", "engines": { - "node": ">=18" + "node": ">= 0.4" }, "funding": { - "url": "https://github.com/sponsors/sindresorhus" + "url": "https://github.com/sponsors/ljharb" } }, - "node_modules/has-flag": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", - "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", + "node_modules/is-shared-array-buffer": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/is-shared-array-buffer/-/is-shared-array-buffer-1.0.4.tgz", + "integrity": "sha512-ISWac8drv4ZGfwKl5slpHG9OwPNty4jOWPRIhBpxOoD+hqITiwuipOQ2bNthAzwA3B4fIjO4Nln74N0S9byq8A==", "dev": true, "license": "MIT", + "dependencies": { + "call-bound": "^1.0.3" + }, "engines": { - "node": ">=8" + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" } }, - "node_modules/ignore": { - "version": "5.3.2", - "resolved": "https://registry.npmjs.org/ignore/-/ignore-5.3.2.tgz", - "integrity": "sha512-hsBTNUqQTDwkWtcdYI2i06Y/nUBEsNEDJKjWdigLvegy8kDuJAS8uRlpkkcQpyEXL0Z/pjDy5HBmMjRCJ2gq+g==", + "node_modules/is-string": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/is-string/-/is-string-1.1.1.tgz", + "integrity": "sha512-BtEeSsoaQjlSPBemMQIrY1MY0uM6vnS1g5fmufYOtnxLGUZM2178PKbhsk7Ffv58IX+ZtcvoGwccYsh0PglkAA==", "dev": true, "license": "MIT", + "dependencies": { + "call-bound": "^1.0.3", + "has-tostringtag": "^1.0.2" + }, "engines": { - "node": ">= 4" + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" } }, - "node_modules/import-fresh": { - "version": "3.3.0", - "resolved": "https://registry.npmjs.org/import-fresh/-/import-fresh-3.3.0.tgz", - "integrity": "sha512-veYYhQa+D1QBKznvhUHxb8faxlrwUnxseDAbAp457E0wLNio2bOSKnjYDhMj+YiAq61xrMGhQk9iXVk5FzgQMw==", + "node_modules/is-symbol": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/is-symbol/-/is-symbol-1.1.1.tgz", + "integrity": "sha512-9gGx6GTtCQM73BgmHQXfDmLtfjjTUDSyoxTCbp5WtoixAhfgsDirWIcVQ/IHpvI5Vgd5i/J5F7B9cN/WlVbC/w==", "dev": true, "license": "MIT", "dependencies": { - "parent-module": "^1.0.0", - "resolve-from": "^4.0.0" + "call-bound": "^1.0.2", + "has-symbols": "^1.1.0", + "safe-regex-test": "^1.1.0" }, "engines": { - "node": ">=6" + "node": ">= 0.4" }, "funding": { - "url": "https://github.com/sponsors/sindresorhus" + "url": "https://github.com/sponsors/ljharb" } }, - "node_modules/imurmurhash": { - "version": "0.1.4", - "resolved": "https://registry.npmjs.org/imurmurhash/-/imurmurhash-0.1.4.tgz", - "integrity": "sha512-JmXMZ6wuvDmLiHEml9ykzqO6lwFbof0GG4IkcGaENdCRDDmMVnny7s5HsIgHCbaq0w2MyPhDqkhTUgS2LU2PHA==", + "node_modules/is-typed-array": { + "version": "1.1.15", + "resolved": "https://registry.npmjs.org/is-typed-array/-/is-typed-array-1.1.15.tgz", + "integrity": "sha512-p3EcsicXjit7SaskXHs1hA91QxgTw46Fv6EFKKGS5DRFLD8yKnohjF3hxoju94b/OcMZoQukzpPpBE9uLVKzgQ==", "dev": true, "license": "MIT", + "dependencies": { + "which-typed-array": "^1.1.16" + }, "engines": { - "node": ">=0.8.19" + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" } }, - "node_modules/indent-string": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/indent-string/-/indent-string-5.0.0.tgz", - "integrity": "sha512-m6FAo/spmsW2Ab2fU35JTYwtOKa2yAwXSwgjSv1TJzh4Mh7mC3lzAOVLBprb72XsTrgkEIsl7YrFNAiDiRhIGg==", + "node_modules/is-weakmap": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/is-weakmap/-/is-weakmap-2.0.2.tgz", + "integrity": "sha512-K5pXYOm9wqY1RgjpL3YTkF39tni1XajUIkawTLUo9EZEVUFga5gSQJF8nNS7ZwJQ02y+1YCNYcMh+HIf1ZqE+w==", "dev": true, + "license": "MIT", "engines": { - "node": ">=12" + "node": ">= 0.4" }, "funding": { - "url": "https://github.com/sponsors/sindresorhus" + "url": "https://github.com/sponsors/ljharb" } }, - "node_modules/is-extglob": { - "version": "2.1.1", - "resolved": "https://registry.npmjs.org/is-extglob/-/is-extglob-2.1.1.tgz", - "integrity": "sha512-SbKbANkN603Vi4jEZv49LeVJMn4yGwsbzZworEoyEiutsN3nJYdbO36zfhGJ6QEDpOZIFkDtnq5JRxmvl3jsoQ==", + "node_modules/is-weakref": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/is-weakref/-/is-weakref-1.1.0.tgz", + "integrity": "sha512-SXM8Nwyys6nT5WP6pltOwKytLV7FqQ4UiibxVmW+EIosHcmCqkkjViTb5SNssDlkCiEYRP1/pdWUKVvZBmsR2Q==", "dev": true, "license": "MIT", + "dependencies": { + "call-bound": "^1.0.2" + }, "engines": { - "node": ">=0.10.0" + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" } }, - "node_modules/is-glob": { - "version": "4.0.3", - "resolved": "https://registry.npmjs.org/is-glob/-/is-glob-4.0.3.tgz", - "integrity": "sha512-xelSayHH36ZgE7ZWhli7pW34hNbNl8Ojv5KVmkJD4hBdD3th8Tfk9vYasLM+mXWOZhFkgZfxhLSnrwRr4elSSg==", + "node_modules/is-weakset": { + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/is-weakset/-/is-weakset-2.0.4.tgz", + "integrity": "sha512-mfcwb6IzQyOKTs84CQMrOwW4gQcaTOAWJ0zzJCl2WSPDrWk/OzDaImWFH3djXhb24g4eudZfLRozAvPGw4d9hQ==", "dev": true, "license": "MIT", "dependencies": { - "is-extglob": "^2.1.1" + "call-bound": "^1.0.3", + "get-intrinsic": "^1.2.6" }, "engines": { - "node": ">=0.10.0" + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" } }, + "node_modules/isarray": { + "version": "2.0.5", + "resolved": "https://registry.npmjs.org/isarray/-/isarray-2.0.5.tgz", + "integrity": "sha512-xHjhDr3cNBK0BzdUJSPXZntQUx/mwMS5Rw4A7lPJ90XGAO6ISP/ePDNuo0vhqOZU+UD5JoodwCAAoZQd3FeAKw==", + "dev": true, + "license": "MIT" + }, "node_modules/isexe": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/isexe/-/isexe-2.0.0.tgz", @@ -2658,13 +4451,30 @@ "dev": true, "license": "ISC" }, + "node_modules/iterator.prototype": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/iterator.prototype/-/iterator.prototype-1.1.4.tgz", + "integrity": "sha512-x4WH0BWmrMmg4oHHl+duwubhrvczGlyuGAZu3nvrf0UXOfPu8IhZObFEr7DE/iv01YgVZrsOiRcqw2srkKEDIA==", + "dev": true, + "license": "MIT", + "dependencies": { + "define-data-property": "^1.1.4", + "es-object-atoms": "^1.0.0", + "get-intrinsic": "^1.2.6", + "has-symbols": "^1.1.0", + "reflect.getprototypeof": "^1.0.8", + "set-function-name": "^2.0.2" + }, + "engines": { + "node": ">= 0.4" + } + }, "node_modules/js-tokens": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/js-tokens/-/js-tokens-4.0.0.tgz", "integrity": "sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ==", "dev": true, - "license": "MIT", - "peer": true + "license": "MIT" }, "node_modules/js-yaml": { "version": "4.1.0", @@ -2742,31 +4552,20 @@ "node": ">=6" } }, - "node_modules/jsonparse": { - "version": "1.3.1", - "resolved": "https://registry.npmjs.org/jsonparse/-/jsonparse-1.3.1.tgz", - "integrity": "sha512-POQXvpdL69+CluYsillJ7SUhKvytYjW9vG/GKpnf+xP8UWgYEM/RaMzHHofbALDiKbbP1W8UEYmgGl39WkPZsg==", - "dev": true, - "engines": [ - "node >= 0.2.0" - ], - "license": "MIT" - }, - "node_modules/JSONStream": { - "version": "1.3.5", - "resolved": "https://registry.npmjs.org/JSONStream/-/JSONStream-1.3.5.tgz", - "integrity": "sha512-E+iruNOY8VV9s4JEbe1aNEm6MiszPRr/UfcHMz0TQh1BXSxHK+ASV1R6W4HpjBhSeS+54PIsAMCBmwD06LLsqQ==", + "node_modules/jsx-ast-utils": { + "version": "3.3.5", + "resolved": "https://registry.npmjs.org/jsx-ast-utils/-/jsx-ast-utils-3.3.5.tgz", + "integrity": "sha512-ZZow9HBI5O6EPgSJLUb8n2NKgmVWTwCvHGwFuJlMjvLFqlGG6pjirPhtdsseaLZjSibD8eegzmYpUZwoIlj2cQ==", "dev": true, - "license": "(MIT OR Apache-2.0)", + "license": "MIT", "dependencies": { - "jsonparse": "^1.2.0", - "through": ">=2.2.7 <3" - }, - "bin": { - "JSONStream": "bin.js" + "array-includes": "^3.1.6", + "array.prototype.flat": "^1.3.1", + "object.assign": "^4.1.4", + "object.values": "^1.1.6" }, "engines": { - "node": "*" + "node": ">=4.0" } }, "node_modules/keyv": { @@ -2816,6 +4615,19 @@ "dev": true, "license": "MIT" }, + "node_modules/loose-envify": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/loose-envify/-/loose-envify-1.4.0.tgz", + "integrity": "sha512-lyuxPGr/Wfhrlem2CL/UcnUc1zcqKAImBDzukY7Y5F/yQiNdko6+fRLevlw1HgMySw7f611UIY408EtxRSoK3Q==", + "dev": true, + "license": "MIT", + "dependencies": { + "js-tokens": "^3.0.0 || ^4.0.0" + }, + "bin": { + "loose-envify": "cli.js" + } + }, "node_modules/loupe": { "version": "3.1.2", "resolved": "https://registry.npmjs.org/loupe/-/loupe-3.1.2.tgz", @@ -2842,12 +4654,52 @@ "@jridgewell/sourcemap-codec": "^1.5.0" } }, - "node_modules/map-stream": { - "version": "0.0.7", - "resolved": "https://registry.npmjs.org/map-stream/-/map-stream-0.0.7.tgz", - "integrity": "sha512-C0X0KQmGm3N2ftbTGBhSyuydQ+vV1LC3f3zPvT3RXHXNZrvfPZcoXp/N5DOa8vedX/rTMm2CjTtivFg2STJMRQ==", + "node_modules/math-intrinsics": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/math-intrinsics/-/math-intrinsics-1.1.0.tgz", + "integrity": "sha512-/IXtbwEk5HTPyEwyKX6hGkYXxM9nbj64B+ilVJnC/R6B0pH5G4V3b0pVbL7DBj4tkhBAppbQUlf6F6Xl9LHu1g==", "dev": true, - "license": "MIT" + "license": "MIT", + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/merge2": { + "version": "1.4.1", + "resolved": "https://registry.npmjs.org/merge2/-/merge2-1.4.1.tgz", + "integrity": "sha512-8q7VEgMJW4J8tcfVPy8g09NcQwZdbwFEqhe/WZkoIzjn/3TGDwtOCYtXGxA3O8tPzpczCCDgv+P2P5y00ZJOOg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 8" + } + }, + "node_modules/micromatch": { + "version": "4.0.8", + "resolved": "https://registry.npmjs.org/micromatch/-/micromatch-4.0.8.tgz", + "integrity": "sha512-PXwfBhYu0hBCPw8Dn0E+WDYb7af3dSLVWKi3HGv84IdF4TyFoC0ysxFd0Goxw7nSv4T/PzEJQxsYsEiFCKo2BA==", + "dev": true, + "license": "MIT", + "dependencies": { + "braces": "^3.0.3", + "picomatch": "^2.3.1" + }, + "engines": { + "node": ">=8.6" + } + }, + "node_modules/micromatch/node_modules/picomatch": { + "version": "2.3.1", + "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-2.3.1.tgz", + "integrity": "sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8.6" + }, + "funding": { + "url": "https://github.com/sponsors/jonschlinkert" + } }, "node_modules/minimatch": { "version": "3.1.2", @@ -2895,6 +4747,35 @@ "dev": true, "license": "MIT" }, + "node_modules/neostandard": { + "version": "0.12.0", + "resolved": "https://registry.npmjs.org/neostandard/-/neostandard-0.12.0.tgz", + "integrity": "sha512-MvtiRhevDzE+oqQUxFvDsEmipzy3erNmnz5q5TG9M8xZ30n86rt4PxGP9jgocGIZr1105OgPZNlK2FQEtb2Vng==", + "dev": true, + "license": "MIT", + "dependencies": { + "@humanwhocodes/gitignore-to-minimatch": "^1.0.2", + "@stylistic/eslint-plugin": "2.11.0", + "eslint-import-resolver-typescript": "^3.7.0", + "eslint-plugin-import-x": "^4.5.0", + "eslint-plugin-n": "^17.14.0", + "eslint-plugin-promise": "^7.2.1", + "eslint-plugin-react": "^7.37.2", + "find-up": "^5.0.0", + "globals": "^15.13.0", + "peowly": "^1.3.2", + "typescript-eslint": "^8.17.0" + }, + "bin": { + "neostandard": "cli.mjs" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "peerDependencies": { + "eslint": "^9.0.0" + } + }, "node_modules/node-releases": { "version": "2.0.19", "resolved": "https://registry.npmjs.org/node-releases/-/node-releases-2.0.19.tgz", @@ -2903,6 +4784,113 @@ "license": "MIT", "peer": true }, + "node_modules/object-assign": { + "version": "4.1.1", + "resolved": "https://registry.npmjs.org/object-assign/-/object-assign-4.1.1.tgz", + "integrity": "sha512-rJgTQnkUnH1sFw8yT6VSU3zD3sWmu6sZhIseY8VX+GRu3P6F7Fu+JNDoXfklElbLJSnc3FUQHVe4cU5hj+BcUg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/object-inspect": { + "version": "1.13.3", + "resolved": "https://registry.npmjs.org/object-inspect/-/object-inspect-1.13.3.tgz", + "integrity": "sha512-kDCGIbxkDSXE3euJZZXzc6to7fCrKHNI/hSRQnRuQ+BWjFNzZwiFF8fj/6o2t2G9/jTj8PSIYTfCLelLZEeRpA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/object-keys": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/object-keys/-/object-keys-1.1.1.tgz", + "integrity": "sha512-NuAESUOUMrlIXOfHKzD6bpPu3tYt3xvjNdRIQ+FeT0lNb4K8WR70CaDxhuNguS2XG+GjkyMwOzsN5ZktImfhLA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/object.assign": { + "version": "4.1.7", + "resolved": "https://registry.npmjs.org/object.assign/-/object.assign-4.1.7.tgz", + "integrity": "sha512-nK28WOo+QIjBkDduTINE4JkF/UJJKyf2EJxvJKfblDpyg0Q+pkOHNTL0Qwy6NP6FhE/EnzV73BxxqcJaXY9anw==", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bind": "^1.0.8", + "call-bound": "^1.0.3", + "define-properties": "^1.2.1", + "es-object-atoms": "^1.0.0", + "has-symbols": "^1.1.0", + "object-keys": "^1.1.1" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/object.entries": { + "version": "1.1.8", + "resolved": "https://registry.npmjs.org/object.entries/-/object.entries-1.1.8.tgz", + "integrity": "sha512-cmopxi8VwRIAw/fkijJohSfpef5PdN0pMQJN6VC/ZKvn0LIknWD8KtgY6KlQdEc4tIjcQ3HxSMmnvtzIscdaYQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bind": "^1.0.7", + "define-properties": "^1.2.1", + "es-object-atoms": "^1.0.0" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/object.fromentries": { + "version": "2.0.8", + "resolved": "https://registry.npmjs.org/object.fromentries/-/object.fromentries-2.0.8.tgz", + "integrity": "sha512-k6E21FzySsSK5a21KRADBd/NGneRegFO5pLHfdQLpRDETUNJueLXs3WCzyQ3tFRDYgbq3KHGXfTbi2bs8WQ6rQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bind": "^1.0.7", + "define-properties": "^1.2.1", + "es-abstract": "^1.23.2", + "es-object-atoms": "^1.0.0" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/object.values": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/object.values/-/object.values-1.2.1.tgz", + "integrity": "sha512-gXah6aZrcUxjWg2zR2MwouP2eHlCBzdV4pygudehaKXSGW4v2AsRQUK+lwwXhii6KFZcunEnmSUoYp5CXibxtA==", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bind": "^1.0.8", + "call-bound": "^1.0.3", + "define-properties": "^1.2.1", + "es-object-atoms": "^1.0.0" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, "node_modules/octokit": { "version": "4.0.2", "resolved": "https://registry.npmjs.org/octokit/-/octokit-4.0.2.tgz", @@ -2942,6 +4930,24 @@ "node": ">= 0.8.0" } }, + "node_modules/own-keys": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/own-keys/-/own-keys-1.0.1.tgz", + "integrity": "sha512-qFOyK5PjiWZd+QQIh+1jhdb9LpxTF0qs7Pm8o5QHYZ0M3vKqSqzsZaEB6oWlxZ+q2sJBMI/Ktgd2N5ZwQoRHfg==", + "dev": true, + "license": "MIT", + "dependencies": { + "get-intrinsic": "^1.2.6", + "object-keys": "^1.1.1", + "safe-push-apply": "^1.0.0" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, "node_modules/p-limit": { "version": "3.1.0", "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-3.1.0.tgz", @@ -3007,6 +5013,13 @@ "node": ">=8" } }, + "node_modules/path-parse": { + "version": "1.0.7", + "resolved": "https://registry.npmjs.org/path-parse/-/path-parse-1.0.7.tgz", + "integrity": "sha512-LDJzPVEEEPR+y48z93A0Ed0yXb8pAByGWo/k5YYdYgpY2/2EsOsksJrq7lOHxryrVOn1ejG6oAp8ahvOIQD8sw==", + "dev": true, + "license": "MIT" + }, "node_modules/pathe": { "version": "1.1.2", "resolved": "https://registry.npmjs.org/pathe/-/pathe-1.1.2.tgz", @@ -3024,17 +5037,14 @@ "node": ">= 14.16" } }, - "node_modules/pause-stream": { - "version": "0.0.11", - "resolved": "https://registry.npmjs.org/pause-stream/-/pause-stream-0.0.11.tgz", - "integrity": "sha512-e3FBlXLmN/D1S+zHzanP4E/4Z60oFAa3O051qt1pxa7DEJWKAyil6upYVXCWadEnuoqa4Pkc9oUx9zsxYeRv8A==", + "node_modules/peowly": { + "version": "1.3.2", + "resolved": "https://registry.npmjs.org/peowly/-/peowly-1.3.2.tgz", + "integrity": "sha512-BYIrwr8JCXY49jUZscgw311w9oGEKo7ux/s+BxrhKTQbiQ0iYNdZNJ5LgagaeercQdFHwnR7Z5IxxFWVQ+BasQ==", "dev": true, - "license": [ - "MIT", - "Apache2" - ], - "dependencies": { - "through": "~2.3" + "license": "MIT", + "engines": { + "node": ">=18.6.0" } }, "node_modules/picocolors": { @@ -3044,6 +5054,29 @@ "dev": true, "license": "ISC" }, + "node_modules/picomatch": { + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-4.0.2.tgz", + "integrity": "sha512-M7BAV6Rlcy5u+m6oPhAPFgJTzAioX/6B0DxyvDlo9l8+T3nLKbrczg2WLUyzd45L8RqfUMyGPzekbMvX2Ldkwg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/jonschlinkert" + } + }, + "node_modules/possible-typed-array-names": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/possible-typed-array-names/-/possible-typed-array-names-1.0.0.tgz", + "integrity": "sha512-d7Uw+eZoloe0EHDIYoe+bQ5WXnGMOpmiZFTuMWCwpjzzkL2nTjcKiAk4hh8TjnGye2TwWOk3UXucZ+3rbmBa8Q==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.4" + } + }, "node_modules/postcss": { "version": "8.4.49", "resolved": "https://registry.npmjs.org/postcss/-/postcss-8.4.49.tgz", @@ -3083,6 +5116,18 @@ "node": ">= 0.8.0" } }, + "node_modules/prop-types": { + "version": "15.8.1", + "resolved": "https://registry.npmjs.org/prop-types/-/prop-types-15.8.1.tgz", + "integrity": "sha512-oj87CgZICdulUohogVAR7AjlC0327U4el4L6eAvOqCeudMDVU0NThNaV+b9Df4dXgSP1gXMTnPdhfe/2qDH5cg==", + "dev": true, + "license": "MIT", + "dependencies": { + "loose-envify": "^1.4.0", + "object-assign": "^4.1.1", + "react-is": "^16.13.1" + } + }, "node_modules/punycode": { "version": "2.3.1", "resolved": "https://registry.npmjs.org/punycode/-/punycode-2.3.1.tgz", @@ -3093,6 +5138,97 @@ "node": ">=6" } }, + "node_modules/queue-microtask": { + "version": "1.2.3", + "resolved": "https://registry.npmjs.org/queue-microtask/-/queue-microtask-1.2.3.tgz", + "integrity": "sha512-NuaNSa6flKT5JaSYQzJok04JzTL1CA6aGhv5rfLW3PgqA+M2ChpZQnAC8h8i4ZFkBS8X5RqkDBHA7r4hej3K9A==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ], + "license": "MIT" + }, + "node_modules/react-is": { + "version": "16.13.1", + "resolved": "https://registry.npmjs.org/react-is/-/react-is-16.13.1.tgz", + "integrity": "sha512-24e6ynE2H+OKt4kqsOvNd8kBpV65zoxbA4BVsEOB3ARVWQki/DHzaUoC5KuON/BiccDaCCTZBuOcfZs70kR8bQ==", + "dev": true, + "license": "MIT" + }, + "node_modules/reflect.getprototypeof": { + "version": "1.0.9", + "resolved": "https://registry.npmjs.org/reflect.getprototypeof/-/reflect.getprototypeof-1.0.9.tgz", + "integrity": "sha512-r0Ay04Snci87djAsI4U+WNRcSw5S4pOH7qFjd/veA5gC7TbqESR3tcj28ia95L/fYUDw11JKP7uqUKUAfVvV5Q==", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bind": "^1.0.8", + "define-properties": "^1.2.1", + "dunder-proto": "^1.0.1", + "es-abstract": "^1.23.6", + "es-errors": "^1.3.0", + "get-intrinsic": "^1.2.6", + "gopd": "^1.2.0", + "which-builtin-type": "^1.2.1" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/regexp.prototype.flags": { + "version": "1.5.3", + "resolved": "https://registry.npmjs.org/regexp.prototype.flags/-/regexp.prototype.flags-1.5.3.tgz", + "integrity": "sha512-vqlC04+RQoFalODCbCumG2xIOvapzVMHwsyIGM/SIE8fRhFFsXeH8/QQ+s0T0kDAhKc4k30s73/0ydkHQz6HlQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bind": "^1.0.7", + "define-properties": "^1.2.1", + "es-errors": "^1.3.0", + "set-function-name": "^2.0.2" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/resolve": { + "version": "1.22.10", + "resolved": "https://registry.npmjs.org/resolve/-/resolve-1.22.10.tgz", + "integrity": "sha512-NPRy+/ncIMeDlTAsuqwKIiferiawhefFJtkNSW0qZJEqMEb+qBt/77B/jGeeek+F0uOeN05CDa6HXbbIgtVX4w==", + "dev": true, + "license": "MIT", + "dependencies": { + "is-core-module": "^2.16.0", + "path-parse": "^1.0.7", + "supports-preserve-symlinks-flag": "^1.0.0" + }, + "bin": { + "resolve": "bin/resolve" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, "node_modules/resolve-from": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/resolve-from/-/resolve-from-4.0.0.tgz", @@ -3103,6 +5239,27 @@ "node": ">=4" } }, + "node_modules/resolve-pkg-maps": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/resolve-pkg-maps/-/resolve-pkg-maps-1.0.0.tgz", + "integrity": "sha512-seS2Tj26TBVOC2NIc2rOe2y2ZO7efxITtLZcGSOnHHNOQ7CkiUBfw0Iw2ck6xkIhPwLhKNLS8BO+hEpngQlqzw==", + "dev": true, + "license": "MIT", + "funding": { + "url": "https://github.com/privatenumber/resolve-pkg-maps?sponsor=1" + } + }, + "node_modules/reusify": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/reusify/-/reusify-1.0.4.tgz", + "integrity": "sha512-U9nH88a3fc/ekCF1l0/UP1IosiuIjyTh7hBvXVMHYgVcfGvt897Xguj2UOLDeI5BG2m7/uwyaLVT6fbtCwTyzw==", + "dev": true, + "license": "MIT", + "engines": { + "iojs": ">=1.0.0", + "node": ">=0.10.0" + } + }, "node_modules/rollup": { "version": "4.29.1", "resolved": "https://registry.npmjs.org/rollup/-/rollup-4.29.1.tgz", @@ -3110,36 +5267,115 @@ "dev": true, "license": "MIT", "dependencies": { - "@types/estree": "1.0.6" - }, - "bin": { - "rollup": "dist/bin/rollup" + "@types/estree": "1.0.6" + }, + "bin": { + "rollup": "dist/bin/rollup" + }, + "engines": { + "node": ">=18.0.0", + "npm": ">=8.0.0" + }, + "optionalDependencies": { + "@rollup/rollup-android-arm-eabi": "4.29.1", + "@rollup/rollup-android-arm64": "4.29.1", + "@rollup/rollup-darwin-arm64": "4.29.1", + "@rollup/rollup-darwin-x64": "4.29.1", + "@rollup/rollup-freebsd-arm64": "4.29.1", + "@rollup/rollup-freebsd-x64": "4.29.1", + "@rollup/rollup-linux-arm-gnueabihf": "4.29.1", + "@rollup/rollup-linux-arm-musleabihf": "4.29.1", + "@rollup/rollup-linux-arm64-gnu": "4.29.1", + "@rollup/rollup-linux-arm64-musl": "4.29.1", + "@rollup/rollup-linux-loongarch64-gnu": "4.29.1", + "@rollup/rollup-linux-powerpc64le-gnu": "4.29.1", + "@rollup/rollup-linux-riscv64-gnu": "4.29.1", + "@rollup/rollup-linux-s390x-gnu": "4.29.1", + "@rollup/rollup-linux-x64-gnu": "4.29.1", + "@rollup/rollup-linux-x64-musl": "4.29.1", + "@rollup/rollup-win32-arm64-msvc": "4.29.1", + "@rollup/rollup-win32-ia32-msvc": "4.29.1", + "@rollup/rollup-win32-x64-msvc": "4.29.1", + "fsevents": "~2.3.2" + } + }, + "node_modules/run-parallel": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/run-parallel/-/run-parallel-1.2.0.tgz", + "integrity": "sha512-5l4VyZR86LZ/lDxZTR6jqL8AFE2S0IFLMP26AbjsLVADxHdhB/c0GUsH+y39UfCi3dzz8OlQuPmnaJOMoDHQBA==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ], + "license": "MIT", + "dependencies": { + "queue-microtask": "^1.2.2" + } + }, + "node_modules/safe-array-concat": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/safe-array-concat/-/safe-array-concat-1.1.3.tgz", + "integrity": "sha512-AURm5f0jYEOydBj7VQlVvDrjeFgthDdEF5H1dP+6mNpoXOMo1quQqJ4wvJDyRZ9+pO3kGWoOdmV08cSv2aJV6Q==", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bind": "^1.0.8", + "call-bound": "^1.0.2", + "get-intrinsic": "^1.2.6", + "has-symbols": "^1.1.0", + "isarray": "^2.0.5" + }, + "engines": { + "node": ">=0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/safe-push-apply": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/safe-push-apply/-/safe-push-apply-1.0.0.tgz", + "integrity": "sha512-iKE9w/Z7xCzUMIZqdBsp6pEQvwuEebH4vdpjcDWnyzaI6yl6O9FHvVpmGelvEHNsoY6wGblkxR6Zty/h00WiSA==", + "dev": true, + "license": "MIT", + "dependencies": { + "es-errors": "^1.3.0", + "isarray": "^2.0.5" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/safe-regex-test": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/safe-regex-test/-/safe-regex-test-1.1.0.tgz", + "integrity": "sha512-x/+Cz4YrimQxQccJf5mKEbIa1NzeCRNI5Ecl/ekmlYaampdNLPalVyIcCZNNH3MvmqBugV5TMYZXv0ljslUlaw==", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bound": "^1.0.2", + "es-errors": "^1.3.0", + "is-regex": "^1.2.1" }, "engines": { - "node": ">=18.0.0", - "npm": ">=8.0.0" + "node": ">= 0.4" }, - "optionalDependencies": { - "@rollup/rollup-android-arm-eabi": "4.29.1", - "@rollup/rollup-android-arm64": "4.29.1", - "@rollup/rollup-darwin-arm64": "4.29.1", - "@rollup/rollup-darwin-x64": "4.29.1", - "@rollup/rollup-freebsd-arm64": "4.29.1", - "@rollup/rollup-freebsd-x64": "4.29.1", - "@rollup/rollup-linux-arm-gnueabihf": "4.29.1", - "@rollup/rollup-linux-arm-musleabihf": "4.29.1", - "@rollup/rollup-linux-arm64-gnu": "4.29.1", - "@rollup/rollup-linux-arm64-musl": "4.29.1", - "@rollup/rollup-linux-loongarch64-gnu": "4.29.1", - "@rollup/rollup-linux-powerpc64le-gnu": "4.29.1", - "@rollup/rollup-linux-riscv64-gnu": "4.29.1", - "@rollup/rollup-linux-s390x-gnu": "4.29.1", - "@rollup/rollup-linux-x64-gnu": "4.29.1", - "@rollup/rollup-linux-x64-musl": "4.29.1", - "@rollup/rollup-win32-arm64-msvc": "4.29.1", - "@rollup/rollup-win32-ia32-msvc": "4.29.1", - "@rollup/rollup-win32-x64-msvc": "4.29.1", - "fsevents": "~2.3.2" + "funding": { + "url": "https://github.com/sponsors/ljharb" } }, "node_modules/semver": { @@ -3152,6 +5388,40 @@ "semver": "bin/semver.js" } }, + "node_modules/set-function-length": { + "version": "1.2.2", + "resolved": "https://registry.npmjs.org/set-function-length/-/set-function-length-1.2.2.tgz", + "integrity": "sha512-pgRc4hJ4/sNjWCSS9AmnS40x3bNMDTknHgL5UaMBTMyJnU90EgWh1Rz+MC9eFu4BuN/UwZjKQuY/1v3rM7HMfg==", + "dev": true, + "license": "MIT", + "dependencies": { + "define-data-property": "^1.1.4", + "es-errors": "^1.3.0", + "function-bind": "^1.1.2", + "get-intrinsic": "^1.2.4", + "gopd": "^1.0.1", + "has-property-descriptors": "^1.0.2" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/set-function-name": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/set-function-name/-/set-function-name-2.0.2.tgz", + "integrity": "sha512-7PGFlmtwsEADb0WYyvCMa1t+yke6daIG4Wirafur5kcf+MhUnPms1UeR0CKQdTZD81yESwMHbtn+TR+dMviakQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "define-data-property": "^1.1.4", + "es-errors": "^1.3.0", + "functions-have-names": "^1.2.3", + "has-property-descriptors": "^1.0.2" + }, + "engines": { + "node": ">= 0.4" + } + }, "node_modules/shebang-command": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/shebang-command/-/shebang-command-2.0.0.tgz", @@ -3175,6 +5445,82 @@ "node": ">=8" } }, + "node_modules/side-channel": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/side-channel/-/side-channel-1.1.0.tgz", + "integrity": "sha512-ZX99e6tRweoUXqR+VBrslhda51Nh5MTQwou5tnUDgbtyM0dBgmhEDtWGP/xbKn6hqfPRHujUNwz5fy/wbbhnpw==", + "dev": true, + "license": "MIT", + "dependencies": { + "es-errors": "^1.3.0", + "object-inspect": "^1.13.3", + "side-channel-list": "^1.0.0", + "side-channel-map": "^1.0.1", + "side-channel-weakmap": "^1.0.2" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/side-channel-list": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/side-channel-list/-/side-channel-list-1.0.0.tgz", + "integrity": "sha512-FCLHtRD/gnpCiCHEiJLOwdmFP+wzCmDEkc9y7NsYxeF4u7Btsn1ZuwgwJGxImImHicJArLP4R0yX4c2KCrMrTA==", + "dev": true, + "license": "MIT", + "dependencies": { + "es-errors": "^1.3.0", + "object-inspect": "^1.13.3" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/side-channel-map": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/side-channel-map/-/side-channel-map-1.0.1.tgz", + "integrity": "sha512-VCjCNfgMsby3tTdo02nbjtM/ewra6jPHmpThenkTYh8pG9ucZ/1P8So4u4FGBek/BjpOVsDCMoLA/iuBKIFXRA==", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bound": "^1.0.2", + "es-errors": "^1.3.0", + "get-intrinsic": "^1.2.5", + "object-inspect": "^1.13.3" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/side-channel-weakmap": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/side-channel-weakmap/-/side-channel-weakmap-1.0.2.tgz", + "integrity": "sha512-WPS/HvHQTYnHisLo9McqBHOJk2FkHO/tlpvldyrnem4aeQp4hai3gythswg6p01oSoTl58rcpiFAjF2br2Ak2A==", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bound": "^1.0.2", + "es-errors": "^1.3.0", + "get-intrinsic": "^1.2.5", + "object-inspect": "^1.13.3", + "side-channel-map": "^1.0.1" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, "node_modules/siginfo": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/siginfo/-/siginfo-2.0.0.tgz", @@ -3192,18 +5538,12 @@ "node": ">=0.10.0" } }, - "node_modules/split": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/split/-/split-1.0.1.tgz", - "integrity": "sha512-mTyOoPbrivtXnwnIxZRFYRrPNtEFKlpB2fvjSnCQUiAA6qAZzqwna5envK4uk6OIeP17CsdF3rSBGYVBsU0Tkg==", + "node_modules/stable-hash": { + "version": "0.0.4", + "resolved": "https://registry.npmjs.org/stable-hash/-/stable-hash-0.0.4.tgz", + "integrity": "sha512-LjdcbuBeLcdETCrPn9i8AYAZ1eCtu4ECAWtP7UleOiZ9LzVxRzzUZEoZ8zB24nhkQnDWyET0I+3sWokSDS3E7g==", "dev": true, - "license": "MIT", - "dependencies": { - "through": "2" - }, - "engines": { - "node": "*" - } + "license": "MIT" }, "node_modules/stackback": { "version": "0.0.2", @@ -3219,15 +5559,102 @@ "dev": true, "license": "MIT" }, - "node_modules/stream-combiner": { - "version": "0.2.2", - "resolved": "https://registry.npmjs.org/stream-combiner/-/stream-combiner-0.2.2.tgz", - "integrity": "sha512-6yHMqgLYDzQDcAkL+tjJDC5nSNuNIx0vZtRZeiPh7Saef7VHX9H5Ijn9l2VIol2zaNYlYEX6KyuT/237A58qEQ==", + "node_modules/string.prototype.matchall": { + "version": "4.0.12", + "resolved": "https://registry.npmjs.org/string.prototype.matchall/-/string.prototype.matchall-4.0.12.tgz", + "integrity": "sha512-6CC9uyBL+/48dYizRf7H7VAYCMCNTBeM78x/VTUe9bFEaxBepPJDa1Ow99LqI/1yF7kuy7Q3cQsYMrcjGUcskA==", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bind": "^1.0.8", + "call-bound": "^1.0.3", + "define-properties": "^1.2.1", + "es-abstract": "^1.23.6", + "es-errors": "^1.3.0", + "es-object-atoms": "^1.0.0", + "get-intrinsic": "^1.2.6", + "gopd": "^1.2.0", + "has-symbols": "^1.1.0", + "internal-slot": "^1.1.0", + "regexp.prototype.flags": "^1.5.3", + "set-function-name": "^2.0.2", + "side-channel": "^1.1.0" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/string.prototype.repeat": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/string.prototype.repeat/-/string.prototype.repeat-1.0.0.tgz", + "integrity": "sha512-0u/TldDbKD8bFCQ/4f5+mNRrXwZ8hg2w7ZR8wa16e8z9XpePWl3eGEcUD0OXpEH/VJH/2G3gjUtR3ZOiBe2S/w==", + "dev": true, + "license": "MIT", + "dependencies": { + "define-properties": "^1.1.3", + "es-abstract": "^1.17.5" + } + }, + "node_modules/string.prototype.trim": { + "version": "1.2.10", + "resolved": "https://registry.npmjs.org/string.prototype.trim/-/string.prototype.trim-1.2.10.tgz", + "integrity": "sha512-Rs66F0P/1kedk5lyYyH9uBzuiI/kNRmwJAR9quK6VOtIpZ2G+hMZd+HQbbv25MgCA6gEffoMZYxlTod4WcdrKA==", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bind": "^1.0.8", + "call-bound": "^1.0.2", + "define-data-property": "^1.1.4", + "define-properties": "^1.2.1", + "es-abstract": "^1.23.5", + "es-object-atoms": "^1.0.0", + "has-property-descriptors": "^1.0.2" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/string.prototype.trimend": { + "version": "1.0.9", + "resolved": "https://registry.npmjs.org/string.prototype.trimend/-/string.prototype.trimend-1.0.9.tgz", + "integrity": "sha512-G7Ok5C6E/j4SGfyLCloXTrngQIQU3PWtXGst3yM7Bea9FRURf1S42ZHlZZtsNque2FN2PoUhfZXYLNWwEr4dLQ==", "dev": true, "license": "MIT", "dependencies": { - "duplexer": "~0.1.1", - "through": "~2.3.4" + "call-bind": "^1.0.8", + "call-bound": "^1.0.2", + "define-properties": "^1.2.1", + "es-object-atoms": "^1.0.0" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/string.prototype.trimstart": { + "version": "1.0.8", + "resolved": "https://registry.npmjs.org/string.prototype.trimstart/-/string.prototype.trimstart-1.0.8.tgz", + "integrity": "sha512-UXSH262CSZY1tfu3G3Secr6uGLCFVPMhIqHjlgCUtCCcgihYc/xKs9djMTMUOb2j1mVSeU8EU6NWc/iQKU6Gfg==", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bind": "^1.0.7", + "define-properties": "^1.2.1", + "es-object-atoms": "^1.0.0" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" } }, "node_modules/strip-json-comments": { @@ -3256,12 +5683,28 @@ "node": ">=8" } }, - "node_modules/through": { - "version": "2.3.8", - "resolved": "https://registry.npmjs.org/through/-/through-2.3.8.tgz", - "integrity": "sha512-w89qg7PI8wAdvX60bMDP+bFoD5Dvhm9oLheFp5O4a2QF0cSBGsBX4qZmadPMvVqlLJBBci+WqGGOAPvcDeNSVg==", + "node_modules/supports-preserve-symlinks-flag": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/supports-preserve-symlinks-flag/-/supports-preserve-symlinks-flag-1.0.0.tgz", + "integrity": "sha512-ot0WnXS9fgdkgIcePe6RHNk1WA8+muPa6cSjeR3V8K27q9BB1rTE3R1p7Hv0z1ZyAc8s6Vvv8DIyWf681MAt0w==", "dev": true, - "license": "MIT" + "license": "MIT", + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/tapable": { + "version": "2.2.1", + "resolved": "https://registry.npmjs.org/tapable/-/tapable-2.2.1.tgz", + "integrity": "sha512-GNzQvQTOIP6RyTfE2Qxb8ZVlNmw0n88vp1szwWRimP02mnTsx3Wtn5qRdqY9w2XduFNUgvOwhNnQsjwCp+kqaQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6" + } }, "node_modules/tinybench": { "version": "2.9.0", @@ -3307,6 +5750,39 @@ "node": ">=14.0.0" } }, + "node_modules/to-regex-range": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/to-regex-range/-/to-regex-range-5.0.1.tgz", + "integrity": "sha512-65P7iz6X5yEr1cwcgvQxbbIw7Uk3gOy5dIdtZ4rDveLqhrdJP+Li/Hx6tyK0NEb+2GCyneCMJiGqrADCSNk8sQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "is-number": "^7.0.0" + }, + "engines": { + "node": ">=8.0" + } + }, + "node_modules/ts-api-utils": { + "version": "1.4.3", + "resolved": "https://registry.npmjs.org/ts-api-utils/-/ts-api-utils-1.4.3.tgz", + "integrity": "sha512-i3eMG77UTMD0hZhgRS562pv83RC6ukSAC2GMNWc+9dieh/+jDM5u5YG+NHX6VNDRHQcHwmsTHctP9LhbC3WxVw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=16" + }, + "peerDependencies": { + "typescript": ">=4.2.0" + } + }, + "node_modules/tslib": { + "version": "2.8.1", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.8.1.tgz", + "integrity": "sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w==", + "dev": true, + "license": "0BSD" + }, "node_modules/type-check": { "version": "0.4.0", "resolved": "https://registry.npmjs.org/type-check/-/type-check-0.4.0.tgz", @@ -3320,6 +5796,141 @@ "node": ">= 0.8.0" } }, + "node_modules/typed-array-buffer": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/typed-array-buffer/-/typed-array-buffer-1.0.3.tgz", + "integrity": "sha512-nAYYwfY3qnzX30IkA6AQZjVbtK6duGontcQm1WSG1MD94YLqK0515GNApXkoxKOWMusVssAHWLh9SeaoefYFGw==", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bound": "^1.0.3", + "es-errors": "^1.3.0", + "is-typed-array": "^1.1.14" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/typed-array-byte-length": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/typed-array-byte-length/-/typed-array-byte-length-1.0.3.tgz", + "integrity": "sha512-BaXgOuIxz8n8pIq3e7Atg/7s+DpiYrxn4vdot3w9KbnBhcRQq6o3xemQdIfynqSeXeDrF32x+WvfzmOjPiY9lg==", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bind": "^1.0.8", + "for-each": "^0.3.3", + "gopd": "^1.2.0", + "has-proto": "^1.2.0", + "is-typed-array": "^1.1.14" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/typed-array-byte-offset": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/typed-array-byte-offset/-/typed-array-byte-offset-1.0.4.tgz", + "integrity": "sha512-bTlAFB/FBYMcuX81gbL4OcpH5PmlFHqlCCpAl8AlEzMz5k53oNDvN8p1PNOWLEmI2x4orp3raOFB51tv9X+MFQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "available-typed-arrays": "^1.0.7", + "call-bind": "^1.0.8", + "for-each": "^0.3.3", + "gopd": "^1.2.0", + "has-proto": "^1.2.0", + "is-typed-array": "^1.1.15", + "reflect.getprototypeof": "^1.0.9" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/typed-array-length": { + "version": "1.0.7", + "resolved": "https://registry.npmjs.org/typed-array-length/-/typed-array-length-1.0.7.tgz", + "integrity": "sha512-3KS2b+kL7fsuk/eJZ7EQdnEmQoaho/r6KUef7hxvltNA5DR8NAUM+8wJMbJyZ4G9/7i3v5zPBIMN5aybAh2/Jg==", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bind": "^1.0.7", + "for-each": "^0.3.3", + "gopd": "^1.0.1", + "is-typed-array": "^1.1.13", + "possible-typed-array-names": "^1.0.0", + "reflect.getprototypeof": "^1.0.6" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/typescript": { + "version": "5.7.2", + "resolved": "https://registry.npmjs.org/typescript/-/typescript-5.7.2.tgz", + "integrity": "sha512-i5t66RHxDvVN40HfDd1PsEThGNnlMCMT3jMUuoh9/0TaqWevNontacunWyN02LA9/fIbEWlcHZcgTKb9QoaLfg==", + "dev": true, + "license": "Apache-2.0", + "peer": true, + "bin": { + "tsc": "bin/tsc", + "tsserver": "bin/tsserver" + }, + "engines": { + "node": ">=14.17" + } + }, + "node_modules/typescript-eslint": { + "version": "8.19.0", + "resolved": "https://registry.npmjs.org/typescript-eslint/-/typescript-eslint-8.19.0.tgz", + "integrity": "sha512-Ni8sUkVWYK4KAcTtPjQ/UTiRk6jcsuDhPpxULapUDi8A/l8TSBk+t1GtJA1RsCzIJg0q6+J7bf35AwQigENWRQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@typescript-eslint/eslint-plugin": "8.19.0", + "@typescript-eslint/parser": "8.19.0", + "@typescript-eslint/utils": "8.19.0" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + }, + "peerDependencies": { + "eslint": "^8.57.0 || ^9.0.0", + "typescript": ">=4.8.4 <5.8.0" + } + }, + "node_modules/unbox-primitive": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/unbox-primitive/-/unbox-primitive-1.1.0.tgz", + "integrity": "sha512-nWJ91DjeOkej/TA8pXQ3myruKpKEYgqvpw9lz4OPHj/NWFNluYrjbz9j01CJ8yKQd2g4jFoOkINCTW2I5LEEyw==", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bound": "^1.0.3", + "has-bigints": "^1.0.2", + "has-symbols": "^1.1.0", + "which-boxed-primitive": "^1.1.1" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, "node_modules/universal-github-app-jwt": { "version": "2.2.0", "resolved": "https://registry.npmjs.org/universal-github-app-jwt/-/universal-github-app-jwt-2.2.0.tgz", @@ -3539,6 +6150,94 @@ "node": ">= 8" } }, + "node_modules/which-boxed-primitive": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/which-boxed-primitive/-/which-boxed-primitive-1.1.1.tgz", + "integrity": "sha512-TbX3mj8n0odCBFVlY8AxkqcHASw3L60jIuF8jFP78az3C2YhmGvqbHBpAjTRH2/xqYunrJ9g1jSyjCjpoWzIAA==", + "dev": true, + "license": "MIT", + "dependencies": { + "is-bigint": "^1.1.0", + "is-boolean-object": "^1.2.1", + "is-number-object": "^1.1.1", + "is-string": "^1.1.1", + "is-symbol": "^1.1.1" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/which-builtin-type": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/which-builtin-type/-/which-builtin-type-1.2.1.tgz", + "integrity": "sha512-6iBczoX+kDQ7a3+YJBnh3T+KZRxM/iYNPXicqk66/Qfm1b93iu+yOImkg0zHbj5LNOcNv1TEADiZ0xa34B4q6Q==", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bound": "^1.0.2", + "function.prototype.name": "^1.1.6", + "has-tostringtag": "^1.0.2", + "is-async-function": "^2.0.0", + "is-date-object": "^1.1.0", + "is-finalizationregistry": "^1.1.0", + "is-generator-function": "^1.0.10", + "is-regex": "^1.2.1", + "is-weakref": "^1.0.2", + "isarray": "^2.0.5", + "which-boxed-primitive": "^1.1.0", + "which-collection": "^1.0.2", + "which-typed-array": "^1.1.16" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/which-collection": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/which-collection/-/which-collection-1.0.2.tgz", + "integrity": "sha512-K4jVyjnBdgvc86Y6BkaLZEN933SwYOuBFkdmBu9ZfkcAbdVbpITnDmjvZ/aQjRXQrv5EPkTnD1s39GiiqbngCw==", + "dev": true, + "license": "MIT", + "dependencies": { + "is-map": "^2.0.3", + "is-set": "^2.0.3", + "is-weakmap": "^2.0.2", + "is-weakset": "^2.0.3" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/which-typed-array": { + "version": "1.1.18", + "resolved": "https://registry.npmjs.org/which-typed-array/-/which-typed-array-1.1.18.tgz", + "integrity": "sha512-qEcY+KJYlWyLH9vNbsr6/5j59AXk5ni5aakf8ldzBvGde6Iz4sxZGkJyWSAueTG7QhOvNRYb1lDdFmL5Td0QKA==", + "dev": true, + "license": "MIT", + "dependencies": { + "available-typed-arrays": "^1.0.7", + "call-bind": "^1.0.8", + "call-bound": "^1.0.3", + "for-each": "^0.3.3", + "gopd": "^1.2.0", + "has-tostringtag": "^1.0.2" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, "node_modules/why-is-node-running": { "version": "2.3.0", "resolved": "https://registry.npmjs.org/why-is-node-running/-/why-is-node-running-2.3.0.tgz", diff --git a/package.json b/package.json index c792260c..a03af6f0 100644 --- a/package.json +++ b/package.json @@ -23,10 +23,8 @@ "@octokit/plugin-throttling": "^9.3.0", "@yarnpkg/lockfile": "^1.1.0", "eslint": "^9.17.0", - "event-stream": "^4.0.1", - "globals": "^15.13.0", "json-2-csv": "^5.5.7", - "JSONStream": "^1.3.5", + "neostandard": "^0.12.0", "octokit": "^4.0.2", "vitest": "^2.1.8" } diff --git a/todo.md b/todo.md index 49d698fc..5fb5c10f 100644 --- a/todo.md +++ b/todo.md @@ -1,33 +1,42 @@ # TODOs -## Optimization +- [*] Handle multiple packagefiles +- [ ] Handle nested (multiple?) lock files -- Investigate using dependency graph/SBOM instead of searching files - - Won't need to fetch repo tree - - Won't need to get file contents - - ISSUE: checking for prototype - - ISSUE: doesn't define whether dependency is indirect or not +## Manual ports (STRETCH) -### If that's not a goer - -- Handle multiple packagefiles -- Handle other language package managers +- [ ] Run get dependents for known manual ports and include them somehow on larger script +- [ ] Handle other language package managers - here's what GitHub supports: https://docs.github.com/en/code-security/supply-chain-security/understanding-your-software-supply-chain/dependency-graph-supported-package-ecosystems#supported-package-ecosystems -## Manual ports - -- Run get dependents for known manual ports and include them somehow on larger script - ## Testing -- More and better +- [ ] More and better ## Linting -- Neostandard - - for import assertions, get rid of the problem by making the current JSON files into js files +- [*] Neostandard ## Presentation -- Rejig refactoring into clear commits -- Update README +- [ ] Rejig refactoring into clear commits + - Add octokit class + - Add octokit tests + - Add repodata class + - Add repodata tests + - convert data lists to JS + - Add linting + - Use repodata in build script + - Add build script tests +- [ ] Update README +- [ ] Summarise key data and have tests to detect wide variance + - Number of prototypes + - Number of government services + - Number updated in last year + - Number of errored checks + - Number of direct dependencies + - Number of indirect dependencies + +# Notes +- Investigated using dependency graph, but there's no easy way to get dependents and trees +- Investigated using search API, but rate limit for code search is 10 per minute (and normal search is 30 per minute) \ No newline at end of file From ed5324359cf18006ae2a9088e9d9ceaf98cca94e Mon Sep 17 00:00:00 2001 From: Brett Kyle Date: Tue, 31 Dec 2024 22:33:01 +0000 Subject: [PATCH 13/20] TODO --- todo.md | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/todo.md b/todo.md index 5fb5c10f..716ca697 100644 --- a/todo.md +++ b/todo.md @@ -11,7 +11,8 @@ ## Testing -- [ ] More and better +- [ ] Test the build script way better somehow +- [ ] Rationalise repodata tests ## Linting From be7225d90396c03ef572ea6461630cde54327d44 Mon Sep 17 00:00:00 2001 From: Brett Kyle Date: Wed, 1 Jan 2025 20:15:12 +0000 Subject: [PATCH 14/20] Get some repo info using the GraphQL API This has a separate rate limit, and getting this information basically doesn't impact it at all. Should save a few thousand REST API calls. --- build-filtered-data.mjs | 3 +- helpers/octokit.mjs | 62 +++++++++++++++++-------------- helpers/repo-data.mjs | 45 +++++++++------------- helpers/repo-data.test.mjs | 76 +++++++++++++++++++------------------- package-lock.json | 36 ++++++++++++------ package.json | 1 + todo.md | 4 ++ 7 files changed, 120 insertions(+), 107 deletions(-) diff --git a/build-filtered-data.mjs b/build-filtered-data.mjs index f5884499..99c3d35a 100644 --- a/build-filtered-data.mjs +++ b/build-filtered-data.mjs @@ -71,7 +71,8 @@ export async function analyseRepo (repo) { } repoData.log('analyzing...') - await repoData.fetchAndValidateMetaData() + await repoData.fetchAndValidateRepoInfo() + repoData.log('repo metadata and latest commit details fetched and validated.') await repoData.fetchAndValidateRepoTree() repoData.log('tree fetched and validated.') diff --git a/helpers/octokit.mjs b/helpers/octokit.mjs index b27605dc..7e07b54e 100644 --- a/helpers/octokit.mjs +++ b/helpers/octokit.mjs @@ -1,5 +1,6 @@ import { Octokit } from 'octokit' import { throttling } from '@octokit/plugin-throttling' +import { graphql } from '@octokit/graphql' const MyOctokit = Octokit.plugin(throttling) const octokit = new MyOctokit({ @@ -31,35 +32,40 @@ const octokit = new MyOctokit({ }, }) -/** - * Gets repo metadata - * - * @param {string} repoOwner - The owner of the repo - * @param {string} repoName - The name of the repo - * @returns {Promise>} - * @throws {RequestError} - If the request fails - */ -export async function getRepoMetaData (repoOwner, repoName) { - return await octokit.rest.repos.get({ - owner: repoOwner, - repo: repoName, - }) -} +const graphQLAuth = graphql.defaults({ + headers: { + authorization: `token ${process.env.GITHUB_AUTH_TOKEN}`, + } +}) -/** - * Gets the latest commit for a repo - * @param {string} repoOwner - The owner of the repo - * @param {string} repoName - The name of the repo - * @returns {Promise>} - * @throws {RequestError} - If the request fails - */ -export async function getLatestCommit (repoOwner, repoName) { - const commits = await octokit.rest.repos.listCommits({ - owner: repoOwner, - repo: repoName, - per_page: 1, - }) - return commits.data[0] +export async function getRepoInfo (owner, name) { + const query = ` + query($owner: String!, $name: String!) { + repository(owner: $owner, name: $name) { + createdAt + pushedAt + defaultBranchRef { + target { + ... on Commit { + oid + } + } + } + } + rateLimit { + cost + remaining + resetAt + } + } + ` + + const variables = { + owner, + name, + } + + return await graphQLAuth(query, variables) } /** diff --git a/helpers/repo-data.mjs b/helpers/repo-data.mjs index a3a70f5c..7e91d1b4 100644 --- a/helpers/repo-data.mjs +++ b/helpers/repo-data.mjs @@ -1,7 +1,6 @@ import { + getRepoInfo, getFileContent, - getLatestCommit, - getRepoMetaData, getRepoTree, } from './octokit.mjs' import * as yarnLock from '@yarnpkg/lockfile' @@ -43,6 +42,8 @@ export class RepoData { this.errorThrown = null this.repoTree = null this.frontendVersions = [] + this.latestCommitSHA = null + this.graphQLRateLimit = null } /** @@ -58,24 +59,28 @@ export class RepoData { } /** - * Fetches and validates repo metadata + * Fetches metadata and repo tree using GraphQL * * @throws {NoMetaDataError} - If metadata could not be fetched + * @throws {NoRepoTreeError} - If the tree could not be fetched * @throws {RequestError} - If the request fails - * */ - async fetchAndValidateMetaData () { - const repoMetaData = await getRepoMetaData(this.repoOwner, this.repoName) - if (repoMetaData) { - this.lastUpdated = repoMetaData.data.pushed_at - this.repoCreated = repoMetaData.data.created_at - } + async fetchAndValidateRepoInfo () { + const response = await getRepoInfo(this.repoOwner, this.repoName) + + this.repoCreated = response.repository?.createdAt + this.lastUpdated = response.repository?.pushedAt + this.latestCommitSHA = response.repository?.defaultBranchRef?.target?.oid + this.graphQLRateLimit = response.rateLimit // Some repos won't have a pushed_at if (!this.repoCreated) { throw new NoMetaDataError() } - this.log('metadata fetched and validated.') + + if (!this.latestCommitSHA) { + throw new NoCommitsError() + } } /** @@ -85,32 +90,16 @@ export class RepoData { * @throws {RequestError} - If the request fails */ async fetchAndValidateRepoTree () { - const latestCommitSha = await this.getLatestCommitSha() this.repoTree = await getRepoTree( this.repoOwner, this.repoName, - latestCommitSha + this.latestCommitSHA ) if (!this.repoTree || !this.repoTree.data || !this.repoTree.data.tree) { throw new NoRepoTreeError() } } - /** - * Gets the SHA of the latest commit - * - * @returns {string} - The SHA of the latest commit - * @throws {NoCommitsError} - If the repo has no commits - * @throws {RequestError} - If the request fails - */ - async getLatestCommitSha () { - const latestCommit = await getLatestCommit(this.repoOwner, this.repoName) - if (latestCommit === undefined) { - throw new NoCommitsError() - } - return latestCommit.sha - } - /** * Checks if repo is a prototype * diff --git a/helpers/repo-data.test.mjs b/helpers/repo-data.test.mjs index a4382f55..726fffd2 100644 --- a/helpers/repo-data.test.mjs +++ b/helpers/repo-data.test.mjs @@ -2,16 +2,14 @@ import { describe, it, expect, vi } from 'vitest' import { RepoData, NoMetaDataError, NoRepoTreeError, NoCommitsError, UnsupportedLockFileError } from './repo-data.mjs' import { getFileContent, - getLatestCommit, - getRepoMetaData, getRepoTree, + getRepoInfo } from './octokit.mjs' // Mock the octokit functions vi.mock('./octokit.mjs', () => ({ getFileContent: vi.fn(), - getLatestCommit: vi.fn(), - getRepoMetaData: vi.fn(), + getRepoInfo: vi.fn(), getRepoTree: vi.fn(), })) @@ -57,40 +55,62 @@ describe('RepoData', () => { ) }) - describe('fetchAndValidateMetaData', () => { + describe('fetchAndValidateRepoInfo', () => { it('should throw a NoMetaDataError if metadata is missing', async () => { const repoData = new RepoData(repoOwner, repoName, serviceOwners) - getRepoMetaData.mockResolvedValue({ + getRepoInfo.mockResolvedValue({ data: { - pushed_at: null, - created_at: null, - }, + repository: { + createdAt: '2022-01-01T00:00:00Z', + pushedAt: '2023-01-01T00:00:00Z' + } + } }) - await expect(repoData.fetchAndValidateMetaData()).rejects.toThrow( + await expect(repoData.fetchAndValidateRepoInfo()).rejects.toThrow( NoMetaDataError ) }) - it('should fetch and validate metadata', async () => { + it('should throw a NoCommitsError if no latest commit', async () => { const repoData = new RepoData(repoOwner, repoName, serviceOwners) - getRepoMetaData.mockResolvedValue({ - data: { - pushed_at: '2023-01-01T00:00:00Z', - created_at: '2022-01-01T00:00:00Z', - }, + getRepoInfo.mockResolvedValue({ + repository: { + createdAt: '2022-01-01T00:00:00Z', + pushedAt: '2023-01-01T00:00:00Z' + } + }) + + await expect(repoData.fetchAndValidateRepoInfo()).rejects.toThrow( + NoCommitsError + ) + }) + + it('should fetch and validate repo info', async () => { + const repoData = new RepoData(repoOwner, repoName, serviceOwners) + getRepoInfo.mockResolvedValue({ + repository: { + createdAt: '2022-01-01T00:00:00Z', + pushedAt: '2023-01-01T00:00:00Z', + defaultBranchRef: { + target: { + oid: 'test-sha' + } + } + } }) - await repoData.fetchAndValidateMetaData() + await repoData.fetchAndValidateRepoInfo() expect(repoData.lastUpdated).toBe('2023-01-01T00:00:00Z') expect(repoData.repoCreated).toBe('2022-01-01T00:00:00Z') + expect(repoData.latestCommitSHA).toBe('test-sha') }) }) describe('fetchAndValidateRepoTree', () => { it('should throw a NoRepoTreeError if repo tree is missing', async () => { const repoData = new RepoData(repoOwner, repoName, serviceOwners) - vi.spyOn(repoData, 'getLatestCommitSha').mockResolvedValue('test-sha') + repoData.latestCommitSHA = 'test-sha' getRepoTree.mockResolvedValue({ data: { tree: null, @@ -104,7 +124,6 @@ describe('RepoData', () => { it('should fetch and validate repo tree', async () => { const repoData = new RepoData(repoOwner, repoName, serviceOwners) - getLatestCommit.mockResolvedValue({ sha: 'test-sha' }) getRepoTree.mockResolvedValue({ data: { tree: [] } }) await repoData.fetchAndValidateRepoTree() @@ -112,25 +131,6 @@ describe('RepoData', () => { }) }) - describe('getLatestCommitSha', () => { - it('should throw a NoCommitsError if the repo has no commits', async () => { - const repoData = new RepoData(repoOwner, repoName, serviceOwners) - getLatestCommit.mockResolvedValue(undefined) - - await expect(repoData.getLatestCommitSha()).rejects.toThrow( - NoCommitsError - ) - }) - - it('should get the SHA of the latest commit', async () => { - const repoData = new RepoData(repoOwner, repoName, serviceOwners) - getLatestCommit.mockResolvedValue({ sha: 'test-sha' }) - - const sha = await repoData.getLatestCommitSha() - expect(sha).toBe('test-sha') - }) - }) - describe('checkPrototype', () => { it('should assume prototype if usage_data.js present', async () => { const repoData = new RepoData(repoOwner, repoName, serviceOwners) diff --git a/package-lock.json b/package-lock.json index ff007f84..5b11cc4a 100644 --- a/package-lock.json +++ b/package-lock.json @@ -10,6 +10,7 @@ "@babel/eslint-parser": "^7.25.9", "@babel/plugin-syntax-import-assertions": "^7.26.0", "@eslint/js": "^9.17.0", + "@octokit/graphql": "^8.1.2", "@octokit/plugin-throttling": "^9.3.0", "@yarnpkg/lockfile": "^1.1.0", "eslint": "^9.17.0", @@ -1241,13 +1242,14 @@ } }, "node_modules/@octokit/graphql": { - "version": "8.1.1", - "resolved": "https://registry.npmjs.org/@octokit/graphql/-/graphql-8.1.1.tgz", - "integrity": "sha512-ukiRmuHTi6ebQx/HFRCXKbDlOh/7xEV6QUXaE7MJEKGNAncGI/STSbOkl12qVXZrfZdpXctx5O9X1AIaebiDBg==", + "version": "8.1.2", + "resolved": "https://registry.npmjs.org/@octokit/graphql/-/graphql-8.1.2.tgz", + "integrity": "sha512-bdlj/CJVjpaz06NBpfHhp4kGJaRZfz7AzC+6EwUImRtrwIw8dIgJ63Xg0OzV9pRn3rIzrt5c2sa++BL0JJ8GLw==", "dev": true, + "license": "MIT", "dependencies": { - "@octokit/request": "^9.0.0", - "@octokit/types": "^13.0.0", + "@octokit/request": "^9.1.4", + "@octokit/types": "^13.6.2", "universal-user-agent": "^7.0.0" }, "engines": { @@ -1385,14 +1387,16 @@ } }, "node_modules/@octokit/request": { - "version": "9.1.1", - "resolved": "https://registry.npmjs.org/@octokit/request/-/request-9.1.1.tgz", - "integrity": "sha512-pyAguc0p+f+GbQho0uNetNQMmLG1e80WjkIaqqgUkihqUp0boRU6nKItXO4VWnr+nbZiLGEyy4TeKRwqaLvYgw==", + "version": "9.1.4", + "resolved": "https://registry.npmjs.org/@octokit/request/-/request-9.1.4.tgz", + "integrity": "sha512-tMbOwGm6wDII6vygP3wUVqFTw3Aoo0FnVQyhihh8vVq12uO3P+vQZeo2CKMpWtPSogpACD0yyZAlVlQnjW71DA==", "dev": true, + "license": "MIT", "dependencies": { "@octokit/endpoint": "^10.0.0", "@octokit/request-error": "^6.0.1", - "@octokit/types": "^13.1.0", + "@octokit/types": "^13.6.2", + "fast-content-type-parse": "^2.0.0", "universal-user-agent": "^7.0.2" }, "engines": { @@ -1412,10 +1416,11 @@ } }, "node_modules/@octokit/types": { - "version": "13.5.0", - "resolved": "https://registry.npmjs.org/@octokit/types/-/types-13.5.0.tgz", - "integrity": "sha512-HdqWTf5Z3qwDVlzCrP8UJquMwunpDiMPt5er+QjGzL4hqr/vBVY/MauQgS1xWxCDT1oMx1EULyqxncdCY/NVSQ==", + "version": "13.6.2", + "resolved": "https://registry.npmjs.org/@octokit/types/-/types-13.6.2.tgz", + "integrity": "sha512-WpbZfZUcZU77DrSW4wbsSgTPfKcp286q3ItaIgvSbBpZJlu6mnYXAkjZz6LVZPXkEvLIM8McanyZejKTYUHipA==", "dev": true, + "license": "MIT", "dependencies": { "@octokit/openapi-types": "^22.2.0" } @@ -3557,6 +3562,13 @@ "node": ">=12.0.0" } }, + "node_modules/fast-content-type-parse": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/fast-content-type-parse/-/fast-content-type-parse-2.0.0.tgz", + "integrity": "sha512-fCqg/6Sps8tqk8p+kqyKqYfOF0VjPNYrqpLiqNl0RBKmD80B080AJWVV6EkSkscjToNExcXg1+Mfzftrx6+iSA==", + "dev": true, + "license": "MIT" + }, "node_modules/fast-deep-equal": { "version": "3.1.3", "resolved": "https://registry.npmjs.org/fast-deep-equal/-/fast-deep-equal-3.1.3.tgz", diff --git a/package.json b/package.json index a03af6f0..3476084d 100644 --- a/package.json +++ b/package.json @@ -20,6 +20,7 @@ "@babel/eslint-parser": "^7.25.9", "@babel/plugin-syntax-import-assertions": "^7.26.0", "@eslint/js": "^9.17.0", + "@octokit/graphql": "^8.1.2", "@octokit/plugin-throttling": "^9.3.0", "@yarnpkg/lockfile": "^1.1.0", "eslint": "^9.17.0", diff --git a/todo.md b/todo.md index 716ca697..0cf7d0d2 100644 --- a/todo.md +++ b/todo.md @@ -1,7 +1,11 @@ # TODOs +## Optimisation and behaviour + - [*] Handle multiple packagefiles - [ ] Handle nested (multiple?) lock files +- [ ] Fetch metadata, repotree and latest commit SHA in one API call (3*4600 API calls is a big saving!) + - Make sure to check cost of GraphQL query - the rate limit is 10,000 - but mixing that and REST might give us more? ## Manual ports (STRETCH) From 11d98251d47bc494f41e0646aa848900049ea2ca Mon Sep 17 00:00:00 2001 From: Brett Kyle Date: Wed, 1 Jan 2025 21:03:06 +0000 Subject: [PATCH 15/20] --fixup log graphql ratelimit --- build-filtered-data.mjs | 1 + 1 file changed, 1 insertion(+) diff --git a/build-filtered-data.mjs b/build-filtered-data.mjs index 99c3d35a..6aa57d61 100644 --- a/build-filtered-data.mjs +++ b/build-filtered-data.mjs @@ -73,6 +73,7 @@ export async function analyseRepo (repo) { await repoData.fetchAndValidateRepoInfo() repoData.log('repo metadata and latest commit details fetched and validated.') + repoData.log(`GraphQL rate limit remaining: ${repoData.graphQLRateLimit.remaining}`) await repoData.fetchAndValidateRepoTree() repoData.log('tree fetched and validated.') From 3f584cebe0ed4b190f3b43b602cba4746558043b Mon Sep 17 00:00:00 2001 From: domoscargin Date: Wed, 1 Jan 2025 23:37:05 +0000 Subject: [PATCH 16/20] Update todo.md --- todo.md | 10 ++++++---- 1 file changed, 6 insertions(+), 4 deletions(-) diff --git a/todo.md b/todo.md index 0cf7d0d2..b9031aa5 100644 --- a/todo.md +++ b/todo.md @@ -4,8 +4,9 @@ - [*] Handle multiple packagefiles - [ ] Handle nested (multiple?) lock files -- [ ] Fetch metadata, repotree and latest commit SHA in one API call (3*4600 API calls is a big saving!) - - Make sure to check cost of GraphQL query - the rate limit is 10,000 - but mixing that and REST might give us more? +- [*] Fetch metadata, repotree and latest commit SHA in one API call (3*4600 API calls is a big saving!) +- [ ] Caching + - we already have a huge amount of local info. I'm thinking simple caching could be storing a file which contains an object of repo name keys to latest commit/last updated values. When we initially get this info (for free!) via the initial graphql query, we can simply check if these values match. if they do, we don't do any processing, This could result in some memory issues, but the main complication would come from having to include the unprocessed repo in the end data file with all its old information, ie: how to retrieve that quickly and efficiently, because the files are big (the CSV file probably isn't THAT big, to be honest) ## Manual ports (STRETCH) @@ -15,7 +16,7 @@ ## Testing -- [ ] Test the build script way better somehow +- [ ] Test the build script way better - maybe even run integration tests with actual API calls on single repos? - [ ] Rationalise repodata tests ## Linting @@ -44,4 +45,5 @@ # Notes - Investigated using dependency graph, but there's no easy way to get dependents and trees -- Investigated using search API, but rate limit for code search is 10 per minute (and normal search is 30 per minute) \ No newline at end of file +- Investigated using search API, but rate limit for code search is 10 per minute (and normal search is 30 per minute) +- Getting created at, pushed at and latest commit sha via graphql is a free call - no impact on graphql API limit \ No newline at end of file From a4032cc2f3cbd7be7c4f211f83ce1cb49b818c7c Mon Sep 17 00:00:00 2001 From: domoscargin Date: Wed, 1 Jan 2025 23:38:09 +0000 Subject: [PATCH 17/20] Update todo.md --- todo.md | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/todo.md b/todo.md index b9031aa5..406dd812 100644 --- a/todo.md +++ b/todo.md @@ -2,9 +2,9 @@ ## Optimisation and behaviour -- [*] Handle multiple packagefiles +- [x] Handle multiple packagefiles - [ ] Handle nested (multiple?) lock files -- [*] Fetch metadata, repotree and latest commit SHA in one API call (3*4600 API calls is a big saving!) +- [x] Fetch metadata, repotree and latest commit SHA in one API call (3*4600 API calls is a big saving!) - [ ] Caching - we already have a huge amount of local info. I'm thinking simple caching could be storing a file which contains an object of repo name keys to latest commit/last updated values. When we initially get this info (for free!) via the initial graphql query, we can simply check if these values match. if they do, we don't do any processing, This could result in some memory issues, but the main complication would come from having to include the unprocessed repo in the end data file with all its old information, ie: how to retrieve that quickly and efficiently, because the files are big (the CSV file probably isn't THAT big, to be honest) @@ -21,7 +21,7 @@ ## Linting -- [*] Neostandard +- [x] Neostandard ## Presentation From 54e89c4ac2a0a7ee3827dc976c6b4d8e13280b2c Mon Sep 17 00:00:00 2001 From: domoscargin Date: Wed, 1 Jan 2025 23:55:46 +0000 Subject: [PATCH 18/20] Update todo.md --- todo.md | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/todo.md b/todo.md index 406dd812..84395524 100644 --- a/todo.md +++ b/todo.md @@ -6,7 +6,8 @@ - [ ] Handle nested (multiple?) lock files - [x] Fetch metadata, repotree and latest commit SHA in one API call (3*4600 API calls is a big saving!) - [ ] Caching - - we already have a huge amount of local info. I'm thinking simple caching could be storing a file which contains an object of repo name keys to latest commit/last updated values. When we initially get this info (for free!) via the initial graphql query, we can simply check if these values match. if they do, we don't do any processing, This could result in some memory issues, but the main complication would come from having to include the unprocessed repo in the end data file with all its old information, ie: how to retrieve that quickly and efficiently, because the files are big (the CSV file probably isn't THAT big, to be honest) + - we already have a huge amount of local info. I'm thinking simple caching could be storing a file which contains an object of repo name keys to latest commit/last updated values. When we initially get this info (for free!) via the initial graphql query, we can simply check if these values match. if they do, we don't do any processing, This could result in some memory issues, but the main complication would come from having to include the unprocessed repo in the end data file with all its old information, ie: how to retrieve that quickly and efficiently, because the files are big (the CSV file probably isn't THAT big, to be honest). + - potentially could investigate storing the data in an SQLite DB instead of JSON files, then should be able to make really quick queries. ## Manual ports (STRETCH) From c85ebf17fe23ab1406ebb9b36cb9b2b84965d56c Mon Sep 17 00:00:00 2001 From: domoscargin Date: Thu, 2 Jan 2025 00:00:01 +0000 Subject: [PATCH 19/20] Update todo.md --- todo.md | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/todo.md b/todo.md index 84395524..d8d5ded8 100644 --- a/todo.md +++ b/todo.md @@ -4,10 +4,11 @@ - [x] Handle multiple packagefiles - [ ] Handle nested (multiple?) lock files -- [x] Fetch metadata, repotree and latest commit SHA in one API call (3*4600 API calls is a big saving!) +- [x] Fetch metadata and latest commit SHA in one API call (2*4600 API calls is a big saving!) - [ ] Caching - we already have a huge amount of local info. I'm thinking simple caching could be storing a file which contains an object of repo name keys to latest commit/last updated values. When we initially get this info (for free!) via the initial graphql query, we can simply check if these values match. if they do, we don't do any processing, This could result in some memory issues, but the main complication would come from having to include the unprocessed repo in the end data file with all its old information, ie: how to retrieve that quickly and efficiently, because the files are big (the CSV file probably isn't THAT big, to be honest). - potentially could investigate storing the data in an SQLite DB instead of JSON files, then should be able to make really quick queries. + - should consider whether we need a history of the repo data or just the current repo data, and a history of the big data points. ## Manual ports (STRETCH) @@ -43,6 +44,7 @@ - Number of errored checks - Number of direct dependencies - Number of indirect dependencies +- [ ] Store history for this key data # Notes - Investigated using dependency graph, but there's no easy way to get dependents and trees From d37b8e7b602bfa33239f15a1dcb04bca67e0c878 Mon Sep 17 00:00:00 2001 From: domoscargin Date: Thu, 2 Jan 2025 00:04:57 +0000 Subject: [PATCH 20/20] Update todo.md --- todo.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/todo.md b/todo.md index d8d5ded8..764c557e 100644 --- a/todo.md +++ b/todo.md @@ -3,7 +3,7 @@ ## Optimisation and behaviour - [x] Handle multiple packagefiles -- [ ] Handle nested (multiple?) lock files +- [ ] [STRETCH] Handle nested (multiple?) lock files - [x] Fetch metadata and latest commit SHA in one API call (2*4600 API calls is a big saving!) - [ ] Caching - we already have a huge amount of local info. I'm thinking simple caching could be storing a file which contains an object of repo name keys to latest commit/last updated values. When we initially get this info (for free!) via the initial graphql query, we can simply check if these values match. if they do, we don't do any processing, This could result in some memory issues, but the main complication would come from having to include the unprocessed repo in the end data file with all its old information, ie: how to retrieve that quickly and efficiently, because the files are big (the CSV file probably isn't THAT big, to be honest). @@ -49,4 +49,4 @@ # Notes - Investigated using dependency graph, but there's no easy way to get dependents and trees - Investigated using search API, but rate limit for code search is 10 per minute (and normal search is 30 per minute) -- Getting created at, pushed at and latest commit sha via graphql is a free call - no impact on graphql API limit \ No newline at end of file +- Getting created at, pushed at and latest commit sha via graphql is a free call - no impact on graphql API limit. it also shaves about 20 minutes from the running time of the build script. \ No newline at end of file