diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 1ec3f0f..aacd71a 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -49,4 +49,4 @@ jobs: run: npm run db:seed - name: Run tests - run: npm test \ No newline at end of file + run: npm run test:ci \ No newline at end of file diff --git a/README.md b/README.md index 4b98666..0bca85f 100644 --- a/README.md +++ b/README.md @@ -100,6 +100,21 @@ For example, to add a project named "express" with GitHub URLs: node index.js project add --name express --github-urls https://github.com/expressjs https://github.com/pillarjs https://github.com/jshttp --category impact ``` +### Workflows + +To run a workflow, use the following command: + +```bash +node index.js workflow run [--name ] +``` + +To list all available workflows, use the following command: + +```bash +node index.js workflow list +``` + + ## Debug mode This project uses the [debug library](https://www.npmjs.com/package/debug), so you can always use the environmental variable `DEBUG=*` to print more detailed information of the execution. diff --git a/__tests__/cli/__snapshots__/workflows.test.js.snap b/__tests__/cli/__snapshots__/workflows.test.js.snap new file mode 100644 index 0000000..2eb364e --- /dev/null +++ b/__tests__/cli/__snapshots__/workflows.test.js.snap @@ -0,0 +1,10 @@ +// Jest Snapshot v1, https://goo.gl/fbAQLP + +exports[`list - Non-Interactive Mode Should provide a list of available workflows 1`] = ` +[ + { + "description": "Check the organizations stored and update the information with the GitHub API.", + "name": "update-github-orgs", + }, +] +`; diff --git a/__tests__/cli.test.js b/__tests__/cli/project.test.js similarity index 96% rename from __tests__/cli.test.js rename to __tests__/cli/project.test.js index 9519e32..d36bea2 100644 --- a/__tests__/cli.test.js +++ b/__tests__/cli/project.test.js @@ -1,8 +1,8 @@ const inquirer = require('inquirer').default const knexInit = require('knex') -const { getConfig } = require('../src/config/') -const { runAddProjectCommand } = require('../src/cli') -const { resetDatabase, getAllProjects, getAllGithubOrgs } = require('./utils') +const { getConfig } = require('../../src/config') +const { runAddProjectCommand } = require('../../src/cli') +const { resetDatabase, getAllProjects, getAllGithubOrgs } = require('../utils') const { dbSettings } = getConfig('test') @@ -24,7 +24,9 @@ let knex beforeAll(() => { knex = knexInit(dbSettings) }) -beforeEach(() => resetDatabase(knex)) +beforeEach(async () => { + await resetDatabase(knex) +}) afterEach(jest.clearAllMocks) afterAll(async () => { await resetDatabase(knex) diff --git a/__tests__/cli/workflows.test.js b/__tests__/cli/workflows.test.js new file mode 100644 index 0000000..4beea04 --- /dev/null +++ b/__tests__/cli/workflows.test.js @@ -0,0 +1,114 @@ +const inquirer = require('inquirer').default +const knexInit = require('knex') +const { getConfig } = require('../../src/config') +const { runWorkflowCommand, listWorkflowCommand } = require('../../src/cli') +const { resetDatabase, getAllProjects, getAllGithubOrgs, addGithubOrg, addProject } = require('../utils') +const { github } = require('../../src/providers') +const { sampleGithubOrg } = require('../fixtures') + +const { dbSettings } = getConfig('test') + +let knex + +beforeAll(() => { + knex = knexInit(dbSettings) +}) +beforeEach(async () => { + await resetDatabase(knex) + jest.clearAllMocks() +}) +afterEach(jest.clearAllMocks) +afterAll(async () => { + await resetDatabase(knex) + await knex.destroy() +}) + +describe('list - Non-Interactive Mode', () => { + jest.spyOn(inquirer, 'prompt').mockImplementation(async () => ({})) + + test('Should provide a list of available workflows', async () => { + // @TODO: This test can be improved, currently is used to ensure that all the commands are listed + await expect(listWorkflowCommand()).toMatchSnapshot() + }) +}) + +describe('run update-github-orgs - Interactive Mode', () => { + // Mock inquirer for testing + jest.spyOn(inquirer, 'prompt').mockImplementation(async (questions) => { + const questionMap = { + 'What is the name of the workflow?': 'update-github-orgs' + } + return questions.reduce((acc, question) => { + acc[question.name] = questionMap[question.message] + return acc + }, {}) + }) + + test('Should throw an error when no Github orgs are stored in the database', async () => { + const projects = await getAllProjects(knex) + expect(projects.length).toBe(0) + const githubOrgs = await getAllGithubOrgs(knex) + expect(githubOrgs.length).toBe(0) + await expect(runWorkflowCommand(knex, {})) + .rejects + .toThrow('No organizations found. Please add organizations/projects before running this workflow.') + }) + + test('Should update the project with new information available', async () => { + // Prepare the database + await addProject(knex, { name: sampleGithubOrg.login, category: 'impact' }) + await addGithubOrg(knex, { login: sampleGithubOrg.login, html_url: sampleGithubOrg.html_url }) + const projects = await getAllProjects(knex) + expect(projects.length).toBe(1) + let githubOrgs = await getAllGithubOrgs(knex) + expect(githubOrgs.length).toBe(1) + expect(githubOrgs[0].description).toBe(null) + // Mock the fetchOrgByLogin method + jest.spyOn(github, 'fetchOrgByLogin').mockResolvedValue(sampleGithubOrg) + await runWorkflowCommand(knex, {}) + // Check the database changes + githubOrgs = await getAllGithubOrgs(knex) + expect(githubOrgs.length).toBe(1) + expect(githubOrgs[0].description).toBe(sampleGithubOrg.description) + }) + + test.todo('Should throw an error when the Github API is not available') +}) + +describe('run update-github-orgs - Non-Interactive Mode', () => { + // Mock inquirer for testing + jest.spyOn(inquirer, 'prompt').mockImplementation(async (questions) => { + const questionMap = { + 'What is the name of the workflow?': 'update-github-orgs' + } + return questions.reduce((acc, question) => { + acc[question.name] = questionMap[question.message] + return acc + }, {}) + }) + + test('Should throw an error when invalid name is provided', async () => { + await expect(runWorkflowCommand(knex, { name: 'invented' })) + .rejects + .toThrow('Invalid workflow name. Please enter a valid workflow name.') + }) + + test('Should throw an error when no name is provided', async () => { + await expect(runWorkflowCommand(knex, { name: undefined })) + .rejects + .toThrow('Invalid workflow name. Please enter a valid workflow name.') + }) + + test('Should throw an error when no Github orgs are stored in the database', async () => { + const projects = await getAllProjects(knex) + expect(projects.length).toBe(0) + const githubOrgs = await getAllGithubOrgs(knex) + expect(githubOrgs.length).toBe(0) + await expect(runWorkflowCommand(knex, { name: 'update-github-orgs' })) + .rejects + .toThrow('No organizations found. Please add organizations/projects before running this workflow.') + }) + + test.todo('Should update the project with new information available') + test.todo('Should throw an error when the Github API is not available') +}) diff --git a/__tests__/fixtures/index.js b/__tests__/fixtures/index.js new file mode 100644 index 0000000..75cba9c --- /dev/null +++ b/__tests__/fixtures/index.js @@ -0,0 +1,70 @@ +// https://docs.github.com/en/rest/orgs/orgs?apiVersion=2022-11-28#get-an-organization +const sampleGithubOrg = { + login: 'github', + id: 1, + node_id: 'MDEyOk9yZ2FuaXphdGlvbjE=', + url: 'https://api.github.com/orgs/github', + repos_url: 'https://api.github.com/orgs/github/repos', + events_url: 'https://api.github.com/orgs/github/events', + hooks_url: 'https://api.github.com/orgs/github/hooks', + issues_url: 'https://api.github.com/orgs/github/issues', + members_url: 'https://api.github.com/orgs/github/members{/member}', + public_members_url: 'https://api.github.com/orgs/github/public_members{/member}', + avatar_url: 'https://github.com/images/error/octocat_happy.gif', + description: 'A great organization', + name: 'github', + company: 'GitHub', + blog: 'https://github.com/blog', + location: 'San Francisco', + email: 'octocat@github.com', + twitter_username: 'github', + is_verified: true, + has_organization_projects: true, + has_repository_projects: true, + public_repos: 2, + public_gists: 1, + followers: 20, + following: 0, + html_url: 'https://github.com/octocat', + created_at: '2008-01-14T04:33:35Z', + type: 'Organization', + total_private_repos: 100, + owned_private_repos: 100, + private_gists: 81, + disk_usage: 10000, + collaborators: 8, + billing_email: 'mona@github.com', + plan: { + name: 'Medium', + space: 400, + private_repos: 20, + filled_seats: 4, + seats: 5 + }, + default_repository_permission: 'read', + members_can_create_repositories: true, + two_factor_requirement_enabled: true, + members_allowed_repository_creation_type: 'all', + members_can_create_public_repositories: false, + members_can_create_private_repositories: false, + members_can_create_internal_repositories: false, + members_can_create_pages: true, + members_can_create_public_pages: true, + members_can_create_private_pages: true, + members_can_fork_private_repositories: false, + web_commit_signoff_required: false, + updated_at: '2014-03-03T18:58:10Z', + deploy_keys_enabled_for_repositories: false, + dependency_graph_enabled_for_new_repositories: false, + dependabot_alerts_enabled_for_new_repositories: false, + dependabot_security_updates_enabled_for_new_repositories: false, + advanced_security_enabled_for_new_repositories: false, + secret_scanning_enabled_for_new_repositories: false, + secret_scanning_push_protection_enabled_for_new_repositories: false, + secret_scanning_push_protection_custom_link: 'https://github.com/octo-org/octo-repo/blob/main/im-blocked.md', + secret_scanning_push_protection_custom_link_enabled: false +} + +module.exports = { + sampleGithubOrg +} diff --git a/__tests__/schemas.test.js b/__tests__/schemas.test.js index a1bc4e4..1a1b9e4 100644 --- a/__tests__/schemas.test.js +++ b/__tests__/schemas.test.js @@ -1,72 +1,6 @@ +const { sampleGithubOrg } = require('./fixtures') const { validateGithubOrg } = require('../src/schemas') -// https://docs.github.com/en/rest/orgs/orgs?apiVersion=2022-11-28#get-an-organization -const sampleGithubOrg = { - login: 'github', - id: 1, - node_id: 'MDEyOk9yZ2FuaXphdGlvbjE=', - url: 'https://api.github.com/orgs/github', - repos_url: 'https://api.github.com/orgs/github/repos', - events_url: 'https://api.github.com/orgs/github/events', - hooks_url: 'https://api.github.com/orgs/github/hooks', - issues_url: 'https://api.github.com/orgs/github/issues', - members_url: 'https://api.github.com/orgs/github/members{/member}', - public_members_url: 'https://api.github.com/orgs/github/public_members{/member}', - avatar_url: 'https://github.com/images/error/octocat_happy.gif', - description: 'A great organization', - name: 'github', - company: 'GitHub', - blog: 'https://github.com/blog', - location: 'San Francisco', - email: 'octocat@github.com', - twitter_username: 'github', - is_verified: true, - has_organization_projects: true, - has_repository_projects: true, - public_repos: 2, - public_gists: 1, - followers: 20, - following: 0, - html_url: 'https://github.com/octocat', - created_at: '2008-01-14T04:33:35Z', - type: 'Organization', - total_private_repos: 100, - owned_private_repos: 100, - private_gists: 81, - disk_usage: 10000, - collaborators: 8, - billing_email: 'mona@github.com', - plan: { - name: 'Medium', - space: 400, - private_repos: 20, - filled_seats: 4, - seats: 5 - }, - default_repository_permission: 'read', - members_can_create_repositories: true, - two_factor_requirement_enabled: true, - members_allowed_repository_creation_type: 'all', - members_can_create_public_repositories: false, - members_can_create_private_repositories: false, - members_can_create_internal_repositories: false, - members_can_create_pages: true, - members_can_create_public_pages: true, - members_can_create_private_pages: true, - members_can_fork_private_repositories: false, - web_commit_signoff_required: false, - updated_at: '2014-03-03T18:58:10Z', - deploy_keys_enabled_for_repositories: false, - dependency_graph_enabled_for_new_repositories: false, - dependabot_alerts_enabled_for_new_repositories: false, - dependabot_security_updates_enabled_for_new_repositories: false, - advanced_security_enabled_for_new_repositories: false, - secret_scanning_enabled_for_new_repositories: false, - secret_scanning_push_protection_enabled_for_new_repositories: false, - secret_scanning_push_protection_custom_link: 'https://github.com/octo-org/octo-repo/blob/main/im-blocked.md', - secret_scanning_push_protection_custom_link_enabled: false -} - describe('schemas', () => { describe('validateGithubOrg', () => { test('Should not throw an error with valid data', () => { diff --git a/__tests__/utils/index.js b/__tests__/utils/index.js index a7459a5..bcddb7a 100644 --- a/__tests__/utils/index.js +++ b/__tests__/utils/index.js @@ -1,13 +1,25 @@ const resetDatabase = async (knex) => { - await knex('github_organizations').del() - await knex('projects').del() + await knex.raw('TRUNCATE TABLE github_organizations RESTART IDENTITY CASCADE') + await knex.raw('TRUNCATE TABLE projects RESTART IDENTITY CASCADE') } const getAllProjects = (knex) => knex('projects').select('*') const getAllGithubOrgs = (knex) => knex('github_organizations').select('*') +const addProject = async (knex, { name, category }) => { + const [project] = await knex('projects').insert({ name, category }).returning('*') + return project +} + +const addGithubOrg = async (knex, data) => { + const [githubOrg] = await knex('github_organizations').insert(data).returning('*') + return githubOrg +} + module.exports = { resetDatabase, getAllProjects, - getAllGithubOrgs + getAllGithubOrgs, + addProject, + addGithubOrg } diff --git a/index.js b/index.js index 8e6631c..08ceff1 100644 --- a/index.js +++ b/index.js @@ -1,7 +1,7 @@ const { Command } = require('commander') const { getConfig } = require('./src/config') const { projectCategories, dbSettings } = getConfig() -const { runAddProjectCommand } = require('./src/cli') +const { runAddProjectCommand, runWorkflowCommand, listWorkflowCommand } = require('./src/cli') const knex = require('knex')(dbSettings) const program = new Command() @@ -26,4 +26,28 @@ project } }) +// Workflow commands +const workflow = program.command('workflow').description('Manage workflows') + +workflow + .command('run') + .description('Run a workflow') + .option('--name ', 'Name of the workflow') + .action(async (options) => { + try { + await runWorkflowCommand(knex, options) + } catch (error) { + console.error('Error running workflow:', error.message) + process.exit(1) + } finally { + await knex.destroy() + } + }) + +workflow + .command('list') + .description('List all available workflows') + .action((options) => { + listWorkflowCommand(options) + }) program.parse(process.argv) diff --git a/jest.config.js b/jest.config.js index 4bb1db0..1d618fc 100644 --- a/jest.config.js +++ b/jest.config.js @@ -11,6 +11,8 @@ export default { '/node_modules/(?!octokit).+\\.js$' ], modulePathIgnorePatterns: [ - '/__tests__/utils/' + '/__tests__/utils/', + '/__tests__/fixtures/' + ] } diff --git a/package-lock.json b/package-lock.json index 6938b60..70faa49 100644 --- a/package-lock.json +++ b/package-lock.json @@ -10,6 +10,7 @@ "license": "MIT", "dependencies": { "@ulisesgascon/normalize-boolean": "2.0.0", + "@ulisesgascon/simplify-object": "2.0.0", "@ulisesgascon/string-to-array": "2.0.0", "ajv": "8.17.1", "ajv-formats": "3.0.1", @@ -17,6 +18,7 @@ "debug": "4.3.7", "inquirer": "12.1.0", "knex": "3.1.0", + "octokit": "3.2.1", "pg": "8.13.1", "validator": "13.12.0" }, @@ -1426,6 +1428,424 @@ "node": ">= 8" } }, + "node_modules/@octokit/app": { + "version": "14.1.0", + "resolved": "https://registry.npmjs.org/@octokit/app/-/app-14.1.0.tgz", + "integrity": "sha512-g3uEsGOQCBl1+W1rgfwoRFUIR6PtvB2T1E4RpygeUU5LrLvlOqcxrt5lfykIeRpUPpupreGJUYl70fqMDXdTpw==", + "license": "MIT", + "dependencies": { + "@octokit/auth-app": "^6.0.0", + "@octokit/auth-unauthenticated": "^5.0.0", + "@octokit/core": "^5.0.0", + "@octokit/oauth-app": "^6.0.0", + "@octokit/plugin-paginate-rest": "^9.0.0", + "@octokit/types": "^12.0.0", + "@octokit/webhooks": "^12.0.4" + }, + "engines": { + "node": ">= 18" + } + }, + "node_modules/@octokit/app/node_modules/@octokit/openapi-types": { + "version": "20.0.0", + "resolved": "https://registry.npmjs.org/@octokit/openapi-types/-/openapi-types-20.0.0.tgz", + "integrity": "sha512-EtqRBEjp1dL/15V7WiX5LJMIxxkdiGJnabzYx5Apx4FkQIFgAfKumXeYAqqJCj1s+BMX4cPFIFC4OLCR6stlnA==", + "license": "MIT" + }, + "node_modules/@octokit/app/node_modules/@octokit/plugin-paginate-rest": { + "version": "9.2.1", + "resolved": "https://registry.npmjs.org/@octokit/plugin-paginate-rest/-/plugin-paginate-rest-9.2.1.tgz", + "integrity": "sha512-wfGhE/TAkXZRLjksFXuDZdmGnJQHvtU/joFQdweXUgzo1XwvBCD4o4+75NtFfjfLK5IwLf9vHTfSiU3sLRYpRw==", + "license": "MIT", + "dependencies": { + "@octokit/types": "^12.6.0" + }, + "engines": { + "node": ">= 18" + }, + "peerDependencies": { + "@octokit/core": "5" + } + }, + "node_modules/@octokit/app/node_modules/@octokit/types": { + "version": "12.6.0", + "resolved": "https://registry.npmjs.org/@octokit/types/-/types-12.6.0.tgz", + "integrity": "sha512-1rhSOfRa6H9w4YwK0yrf5faDaDTb+yLyBUKOCV4xtCDB5VmIPqd/v9yr9o6SAzOAlRxMiRiCic6JVM1/kunVkw==", + "license": "MIT", + "dependencies": { + "@octokit/openapi-types": "^20.0.0" + } + }, + "node_modules/@octokit/auth-app": { + "version": "6.1.3", + "resolved": "https://registry.npmjs.org/@octokit/auth-app/-/auth-app-6.1.3.tgz", + "integrity": "sha512-dcaiteA6Y/beAlDLZOPNReN3FGHu+pARD6OHfh3T9f3EO09++ec+5wt3KtGGSSs2Mp5tI8fQwdMOEnrzBLfgUA==", + "license": "MIT", + "dependencies": { + "@octokit/auth-oauth-app": "^7.1.0", + "@octokit/auth-oauth-user": "^4.1.0", + "@octokit/request": "^8.3.1", + "@octokit/request-error": "^5.1.0", + "@octokit/types": "^13.1.0", + "deprecation": "^2.3.1", + "lru-cache": "npm:@wolfy1339/lru-cache@^11.0.2-patch.1", + "universal-github-app-jwt": "^1.1.2", + "universal-user-agent": "^6.0.0" + }, + "engines": { + "node": ">= 18" + } + }, + "node_modules/@octokit/auth-app/node_modules/lru-cache": { + "name": "@wolfy1339/lru-cache", + "version": "11.0.2-patch.1", + "resolved": "https://registry.npmjs.org/@wolfy1339/lru-cache/-/lru-cache-11.0.2-patch.1.tgz", + "integrity": "sha512-BgYZfL2ADCXKOw2wJtkM3slhHotawWkgIRRxq4wEybnZQPjvAp71SPX35xepMykTw8gXlzWcWPTY31hlbnRsDA==", + "license": "ISC", + "engines": { + "node": "18 >=18.20 || 20 || >=22" + } + }, + "node_modules/@octokit/auth-oauth-app": { + "version": "7.1.0", + "resolved": "https://registry.npmjs.org/@octokit/auth-oauth-app/-/auth-oauth-app-7.1.0.tgz", + "integrity": "sha512-w+SyJN/b0l/HEb4EOPRudo7uUOSW51jcK1jwLa+4r7PA8FPFpoxEnHBHMITqCsc/3Vo2qqFjgQfz/xUUvsSQnA==", + "license": "MIT", + "dependencies": { + "@octokit/auth-oauth-device": "^6.1.0", + "@octokit/auth-oauth-user": "^4.1.0", + "@octokit/request": "^8.3.1", + "@octokit/types": "^13.0.0", + "@types/btoa-lite": "^1.0.0", + "btoa-lite": "^1.0.0", + "universal-user-agent": "^6.0.0" + }, + "engines": { + "node": ">= 18" + } + }, + "node_modules/@octokit/auth-oauth-device": { + "version": "6.1.0", + "resolved": "https://registry.npmjs.org/@octokit/auth-oauth-device/-/auth-oauth-device-6.1.0.tgz", + "integrity": "sha512-FNQ7cb8kASufd6Ej4gnJ3f1QB5vJitkoV1O0/g6e6lUsQ7+VsSNRHRmFScN2tV4IgKA12frrr/cegUs0t+0/Lw==", + "license": "MIT", + "dependencies": { + "@octokit/oauth-methods": "^4.1.0", + "@octokit/request": "^8.3.1", + "@octokit/types": "^13.0.0", + "universal-user-agent": "^6.0.0" + }, + "engines": { + "node": ">= 18" + } + }, + "node_modules/@octokit/auth-oauth-user": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/@octokit/auth-oauth-user/-/auth-oauth-user-4.1.0.tgz", + "integrity": "sha512-FrEp8mtFuS/BrJyjpur+4GARteUCrPeR/tZJzD8YourzoVhRics7u7we/aDcKv+yywRNwNi/P4fRi631rG/OyQ==", + "license": "MIT", + "dependencies": { + "@octokit/auth-oauth-device": "^6.1.0", + "@octokit/oauth-methods": "^4.1.0", + "@octokit/request": "^8.3.1", + "@octokit/types": "^13.0.0", + "btoa-lite": "^1.0.0", + "universal-user-agent": "^6.0.0" + }, + "engines": { + "node": ">= 18" + } + }, + "node_modules/@octokit/auth-token": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/@octokit/auth-token/-/auth-token-4.0.0.tgz", + "integrity": "sha512-tY/msAuJo6ARbK6SPIxZrPBms3xPbfwBrulZe0Wtr/DIY9lje2HeV1uoebShn6mx7SjCHif6EjMvoREj+gZ+SA==", + "license": "MIT", + "engines": { + "node": ">= 18" + } + }, + "node_modules/@octokit/auth-unauthenticated": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/@octokit/auth-unauthenticated/-/auth-unauthenticated-5.0.1.tgz", + "integrity": "sha512-oxeWzmBFxWd+XolxKTc4zr+h3mt+yofn4r7OfoIkR/Cj/o70eEGmPsFbueyJE2iBAGpjgTnEOKM3pnuEGVmiqg==", + "license": "MIT", + "dependencies": { + "@octokit/request-error": "^5.0.0", + "@octokit/types": "^12.0.0" + }, + "engines": { + "node": ">= 18" + } + }, + "node_modules/@octokit/auth-unauthenticated/node_modules/@octokit/openapi-types": { + "version": "20.0.0", + "resolved": "https://registry.npmjs.org/@octokit/openapi-types/-/openapi-types-20.0.0.tgz", + "integrity": "sha512-EtqRBEjp1dL/15V7WiX5LJMIxxkdiGJnabzYx5Apx4FkQIFgAfKumXeYAqqJCj1s+BMX4cPFIFC4OLCR6stlnA==", + "license": "MIT" + }, + "node_modules/@octokit/auth-unauthenticated/node_modules/@octokit/types": { + "version": "12.6.0", + "resolved": "https://registry.npmjs.org/@octokit/types/-/types-12.6.0.tgz", + "integrity": "sha512-1rhSOfRa6H9w4YwK0yrf5faDaDTb+yLyBUKOCV4xtCDB5VmIPqd/v9yr9o6SAzOAlRxMiRiCic6JVM1/kunVkw==", + "license": "MIT", + "dependencies": { + "@octokit/openapi-types": "^20.0.0" + } + }, + "node_modules/@octokit/core": { + "version": "5.2.0", + "resolved": "https://registry.npmjs.org/@octokit/core/-/core-5.2.0.tgz", + "integrity": "sha512-1LFfa/qnMQvEOAdzlQymH0ulepxbxnCYAKJZfMci/5XJyIHWgEYnDmgnKakbTh7CH2tFQ5O60oYDvns4i9RAIg==", + "license": "MIT", + "dependencies": { + "@octokit/auth-token": "^4.0.0", + "@octokit/graphql": "^7.1.0", + "@octokit/request": "^8.3.1", + "@octokit/request-error": "^5.1.0", + "@octokit/types": "^13.0.0", + "before-after-hook": "^2.2.0", + "universal-user-agent": "^6.0.0" + }, + "engines": { + "node": ">= 18" + } + }, + "node_modules/@octokit/endpoint": { + "version": "9.0.5", + "resolved": "https://registry.npmjs.org/@octokit/endpoint/-/endpoint-9.0.5.tgz", + "integrity": "sha512-ekqR4/+PCLkEBF6qgj8WqJfvDq65RH85OAgrtnVp1mSxaXF03u2xW/hUdweGS5654IlC0wkNYC18Z50tSYTAFw==", + "license": "MIT", + "dependencies": { + "@octokit/types": "^13.1.0", + "universal-user-agent": "^6.0.0" + }, + "engines": { + "node": ">= 18" + } + }, + "node_modules/@octokit/graphql": { + "version": "7.1.0", + "resolved": "https://registry.npmjs.org/@octokit/graphql/-/graphql-7.1.0.tgz", + "integrity": "sha512-r+oZUH7aMFui1ypZnAvZmn0KSqAUgE1/tUXIWaqUCa1758ts/Jio84GZuzsvUkme98kv0WFY8//n0J1Z+vsIsQ==", + "license": "MIT", + "dependencies": { + "@octokit/request": "^8.3.0", + "@octokit/types": "^13.0.0", + "universal-user-agent": "^6.0.0" + }, + "engines": { + "node": ">= 18" + } + }, + "node_modules/@octokit/oauth-app": { + "version": "6.1.0", + "resolved": "https://registry.npmjs.org/@octokit/oauth-app/-/oauth-app-6.1.0.tgz", + "integrity": "sha512-nIn/8eUJ/BKUVzxUXd5vpzl1rwaVxMyYbQkNZjHrF7Vk/yu98/YDF/N2KeWO7uZ0g3b5EyiFXFkZI8rJ+DH1/g==", + "license": "MIT", + "dependencies": { + "@octokit/auth-oauth-app": "^7.0.0", + "@octokit/auth-oauth-user": "^4.0.0", + "@octokit/auth-unauthenticated": "^5.0.0", + "@octokit/core": "^5.0.0", + "@octokit/oauth-authorization-url": "^6.0.2", + "@octokit/oauth-methods": "^4.0.0", + "@types/aws-lambda": "^8.10.83", + "universal-user-agent": "^6.0.0" + }, + "engines": { + "node": ">= 18" + } + }, + "node_modules/@octokit/oauth-authorization-url": { + "version": "6.0.2", + "resolved": "https://registry.npmjs.org/@octokit/oauth-authorization-url/-/oauth-authorization-url-6.0.2.tgz", + "integrity": "sha512-CdoJukjXXxqLNK4y/VOiVzQVjibqoj/xHgInekviUJV73y/BSIcwvJ/4aNHPBPKcPWFnd4/lO9uqRV65jXhcLA==", + "license": "MIT", + "engines": { + "node": ">= 18" + } + }, + "node_modules/@octokit/oauth-methods": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/@octokit/oauth-methods/-/oauth-methods-4.1.0.tgz", + "integrity": "sha512-4tuKnCRecJ6CG6gr0XcEXdZtkTDbfbnD5oaHBmLERTjTMZNi2CbfEHZxPU41xXLDG4DfKf+sonu00zvKI9NSbw==", + "license": "MIT", + "dependencies": { + "@octokit/oauth-authorization-url": "^6.0.2", + "@octokit/request": "^8.3.1", + "@octokit/request-error": "^5.1.0", + "@octokit/types": "^13.0.0", + "btoa-lite": "^1.0.0" + }, + "engines": { + "node": ">= 18" + } + }, + "node_modules/@octokit/openapi-types": { + "version": "22.2.0", + "resolved": "https://registry.npmjs.org/@octokit/openapi-types/-/openapi-types-22.2.0.tgz", + "integrity": "sha512-QBhVjcUa9W7Wwhm6DBFu6ZZ+1/t/oYxqc2tp81Pi41YNuJinbFRx8B133qVOrAaBbF7D/m0Et6f9/pZt9Rc+tg==", + "license": "MIT" + }, + "node_modules/@octokit/plugin-paginate-graphql": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/@octokit/plugin-paginate-graphql/-/plugin-paginate-graphql-4.0.1.tgz", + "integrity": "sha512-R8ZQNmrIKKpHWC6V2gum4x9LG2qF1RxRjo27gjQcG3j+vf2tLsEfE7I/wRWEPzYMaenr1M+qDAtNcwZve1ce1A==", + "license": "MIT", + "engines": { + "node": ">= 18" + }, + "peerDependencies": { + "@octokit/core": ">=5" + } + }, + "node_modules/@octokit/plugin-paginate-rest": { + "version": "11.3.1", + "resolved": "https://registry.npmjs.org/@octokit/plugin-paginate-rest/-/plugin-paginate-rest-11.3.1.tgz", + "integrity": "sha512-ryqobs26cLtM1kQxqeZui4v8FeznirUsksiA+RYemMPJ7Micju0WSkv50dBksTuZks9O5cg4wp+t8fZ/cLY56g==", + "license": "MIT", + "dependencies": { + "@octokit/types": "^13.5.0" + }, + "engines": { + "node": ">= 18" + }, + "peerDependencies": { + "@octokit/core": "5" + } + }, + "node_modules/@octokit/plugin-rest-endpoint-methods": { + "version": "13.2.2", + "resolved": "https://registry.npmjs.org/@octokit/plugin-rest-endpoint-methods/-/plugin-rest-endpoint-methods-13.2.2.tgz", + "integrity": "sha512-EI7kXWidkt3Xlok5uN43suK99VWqc8OaIMktY9d9+RNKl69juoTyxmLoWPIZgJYzi41qj/9zU7G/ljnNOJ5AFA==", + "license": "MIT", + "dependencies": { + "@octokit/types": "^13.5.0" + }, + "engines": { + "node": ">= 18" + }, + "peerDependencies": { + "@octokit/core": "^5" + } + }, + "node_modules/@octokit/plugin-retry": { + "version": "6.1.0", + "resolved": "https://registry.npmjs.org/@octokit/plugin-retry/-/plugin-retry-6.1.0.tgz", + "integrity": "sha512-WrO3bvq4E1Xh1r2mT9w6SDFg01gFmP81nIG77+p/MqW1JeXXgL++6umim3t6x0Zj5pZm3rXAN+0HEjmmdhIRig==", + "license": "MIT", + "dependencies": { + "@octokit/request-error": "^5.0.0", + "@octokit/types": "^13.0.0", + "bottleneck": "^2.15.3" + }, + "engines": { + "node": ">= 18" + }, + "peerDependencies": { + "@octokit/core": "5" + } + }, + "node_modules/@octokit/plugin-throttling": { + "version": "8.2.0", + "resolved": "https://registry.npmjs.org/@octokit/plugin-throttling/-/plugin-throttling-8.2.0.tgz", + "integrity": "sha512-nOpWtLayKFpgqmgD0y3GqXafMFuKcA4tRPZIfu7BArd2lEZeb1988nhWhwx4aZWmjDmUfdgVf7W+Tt4AmvRmMQ==", + "license": "MIT", + "dependencies": { + "@octokit/types": "^12.2.0", + "bottleneck": "^2.15.3" + }, + "engines": { + "node": ">= 18" + }, + "peerDependencies": { + "@octokit/core": "^5.0.0" + } + }, + "node_modules/@octokit/plugin-throttling/node_modules/@octokit/openapi-types": { + "version": "20.0.0", + "resolved": "https://registry.npmjs.org/@octokit/openapi-types/-/openapi-types-20.0.0.tgz", + "integrity": "sha512-EtqRBEjp1dL/15V7WiX5LJMIxxkdiGJnabzYx5Apx4FkQIFgAfKumXeYAqqJCj1s+BMX4cPFIFC4OLCR6stlnA==", + "license": "MIT" + }, + "node_modules/@octokit/plugin-throttling/node_modules/@octokit/types": { + "version": "12.6.0", + "resolved": "https://registry.npmjs.org/@octokit/types/-/types-12.6.0.tgz", + "integrity": "sha512-1rhSOfRa6H9w4YwK0yrf5faDaDTb+yLyBUKOCV4xtCDB5VmIPqd/v9yr9o6SAzOAlRxMiRiCic6JVM1/kunVkw==", + "license": "MIT", + "dependencies": { + "@octokit/openapi-types": "^20.0.0" + } + }, + "node_modules/@octokit/request": { + "version": "8.4.0", + "resolved": "https://registry.npmjs.org/@octokit/request/-/request-8.4.0.tgz", + "integrity": "sha512-9Bb014e+m2TgBeEJGEbdplMVWwPmL1FPtggHQRkV+WVsMggPtEkLKPlcVYm/o8xKLkpJ7B+6N8WfQMtDLX2Dpw==", + "license": "MIT", + "dependencies": { + "@octokit/endpoint": "^9.0.1", + "@octokit/request-error": "^5.1.0", + "@octokit/types": "^13.1.0", + "universal-user-agent": "^6.0.0" + }, + "engines": { + "node": ">= 18" + } + }, + "node_modules/@octokit/request-error": { + "version": "5.1.0", + "resolved": "https://registry.npmjs.org/@octokit/request-error/-/request-error-5.1.0.tgz", + "integrity": "sha512-GETXfE05J0+7H2STzekpKObFe765O5dlAKUTLNGeH+x47z7JjXHfsHKo5z21D/o/IOZTUEI6nyWyR+bZVP/n5Q==", + "license": "MIT", + "dependencies": { + "@octokit/types": "^13.1.0", + "deprecation": "^2.0.0", + "once": "^1.4.0" + }, + "engines": { + "node": ">= 18" + } + }, + "node_modules/@octokit/types": { + "version": "13.6.2", + "resolved": "https://registry.npmjs.org/@octokit/types/-/types-13.6.2.tgz", + "integrity": "sha512-WpbZfZUcZU77DrSW4wbsSgTPfKcp286q3ItaIgvSbBpZJlu6mnYXAkjZz6LVZPXkEvLIM8McanyZejKTYUHipA==", + "license": "MIT", + "dependencies": { + "@octokit/openapi-types": "^22.2.0" + } + }, + "node_modules/@octokit/webhooks": { + "version": "12.3.1", + "resolved": "https://registry.npmjs.org/@octokit/webhooks/-/webhooks-12.3.1.tgz", + "integrity": "sha512-BVwtWE3rRXB9IugmQTfKspqjNa8q+ab73ddkV9k1Zok3XbuOxJUi4lTYk5zBZDhfWb/Y2H+RO9Iggm25gsqeow==", + "license": "MIT", + "dependencies": { + "@octokit/request-error": "^5.0.0", + "@octokit/webhooks-methods": "^4.1.0", + "@octokit/webhooks-types": "7.6.1", + "aggregate-error": "^3.1.0" + }, + "engines": { + "node": ">= 18" + } + }, + "node_modules/@octokit/webhooks-methods": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/@octokit/webhooks-methods/-/webhooks-methods-4.1.0.tgz", + "integrity": "sha512-zoQyKw8h9STNPqtm28UGOYFE7O6D4Il8VJwhAtMHFt2C4L0VQT1qGKLeefUOqHNs1mNRYSadVv7x0z8U2yyeWQ==", + "license": "MIT", + "engines": { + "node": ">= 18" + } + }, + "node_modules/@octokit/webhooks-types": { + "version": "7.6.1", + "resolved": "https://registry.npmjs.org/@octokit/webhooks-types/-/webhooks-types-7.6.1.tgz", + "integrity": "sha512-S8u2cJzklBC0FgTwWVLaM8tMrDuDMVE4xiTK4EYXM9GntyvrdbSoxqDQa+Fh57CCNApyIpyeqPhhFEmHPfrXgw==", + "license": "MIT" + }, "node_modules/@rtsao/scc": { "version": "1.1.0", "resolved": "https://registry.npmjs.org/@rtsao/scc/-/scc-1.1.0.tgz", @@ -1460,6 +1880,12 @@ "@sinonjs/commons": "^3.0.0" } }, + "node_modules/@types/aws-lambda": { + "version": "8.10.146", + "resolved": "https://registry.npmjs.org/@types/aws-lambda/-/aws-lambda-8.10.146.tgz", + "integrity": "sha512-3BaDXYTh0e6UCJYL/jwV/3+GRslSc08toAiZSmleYtkAUyV5rtvdPYxrG/88uqvTuT6sb27WE9OS90ZNTIuQ0g==", + "license": "MIT" + }, "node_modules/@types/babel__core": { "version": "7.20.5", "resolved": "https://registry.npmjs.org/@types/babel__core/-/babel__core-7.20.5.tgz", @@ -1505,6 +1931,12 @@ "@babel/types": "^7.20.7" } }, + "node_modules/@types/btoa-lite": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/@types/btoa-lite/-/btoa-lite-1.0.2.tgz", + "integrity": "sha512-ZYbcE2x7yrvNFJiU7xJGrpF/ihpkM7zKgw8bha3LNJSesvTtUNxbpzaT7WXBIryf6jovisrxTBvymxMeLLj1Mg==", + "license": "MIT" + }, "node_modules/@types/graceful-fs": { "version": "4.1.9", "resolved": "https://registry.npmjs.org/@types/graceful-fs/-/graceful-fs-4.1.9.tgz", @@ -1549,6 +1981,15 @@ "dev": true, "license": "MIT" }, + "node_modules/@types/jsonwebtoken": { + "version": "9.0.7", + "resolved": "https://registry.npmjs.org/@types/jsonwebtoken/-/jsonwebtoken-9.0.7.tgz", + "integrity": "sha512-ugo316mmTYBl2g81zDFnZ7cfxlut3o+/EQdaP7J8QN2kY6lJ22hmQYCK5EHcJHbrW+dkCGSCPgbG8JtYj6qSrg==", + "license": "MIT", + "dependencies": { + "@types/node": "*" + } + }, "node_modules/@types/node": { "version": "22.10.1", "resolved": "https://registry.npmjs.org/@types/node/-/node-22.10.1.tgz", @@ -1591,6 +2032,15 @@ "node": ">=18.0.0" } }, + "node_modules/@ulisesgascon/simplify-object": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/@ulisesgascon/simplify-object/-/simplify-object-2.0.0.tgz", + "integrity": "sha512-2V14soGuGTluGExYab7dV04kg9H+QuKcaO8dasBgwRKDbxLaKN0MKP7jclAcnem2oaKcl+ECHdGBhTvH4uljnA==", + "license": "MIT", + "engines": { + "node": ">=18.0.0" + } + }, "node_modules/@ulisesgascon/string-to-array": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/@ulisesgascon/string-to-array/-/string-to-array-2.0.0.tgz", @@ -1630,6 +2080,19 @@ "acorn": "^6.0.0 || ^7.0.0 || ^8.0.0" } }, + "node_modules/aggregate-error": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/aggregate-error/-/aggregate-error-3.1.0.tgz", + "integrity": "sha512-4I7Td01quW/RpocfNayFdFVk1qSuoh0E7JrbRJ16nH01HhKFQ88INq9Sd+nd72zqRySlr9BmDA8xlEJ6vJMrYA==", + "license": "MIT", + "dependencies": { + "clean-stack": "^2.0.0", + "indent-string": "^4.0.0" + }, + "engines": { + "node": ">=8" + } + }, "node_modules/ajv": { "version": "8.17.1", "resolved": "https://registry.npmjs.org/ajv/-/ajv-8.17.1.tgz", @@ -2032,6 +2495,18 @@ "dev": true, "license": "MIT" }, + "node_modules/before-after-hook": { + "version": "2.2.3", + "resolved": "https://registry.npmjs.org/before-after-hook/-/before-after-hook-2.2.3.tgz", + "integrity": "sha512-NzUnlZexiaH/46WDhANlyR2bXRopNg4F/zuSA3OpZnllCUgRaOF2znDioDWrmbNVsuZk6l9pMquQB38cfBZwkQ==", + "license": "Apache-2.0" + }, + "node_modules/bottleneck": { + "version": "2.19.5", + "resolved": "https://registry.npmjs.org/bottleneck/-/bottleneck-2.19.5.tgz", + "integrity": "sha512-VHiNCbI1lKdl44tGrhNfU3lup0Tj/ZBMJB5/2ZbNXRCPuRCO7ed2mgcK4r17y+KB2EfuYuRaVlwNbAeaWGSpbw==", + "license": "MIT" + }, "node_modules/brace-expansion": { "version": "1.1.11", "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.11.tgz", @@ -2099,6 +2574,18 @@ "node-int64": "^0.4.0" } }, + "node_modules/btoa-lite": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/btoa-lite/-/btoa-lite-1.0.0.tgz", + "integrity": "sha512-gvW7InbIyF8AicrqWoptdW08pUxuhq8BEgowNajy9RhiE86fmGAGl+bLKo6oB8QP0CkqHLowfN0oJdKC/J6LbA==", + "license": "MIT" + }, + "node_modules/buffer-equal-constant-time": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/buffer-equal-constant-time/-/buffer-equal-constant-time-1.0.1.tgz", + "integrity": "sha512-zRpUiDwd/xk6ADqPMATG8vc9VPrkck7T07OIx0gnjmJAnHnTVXNQG3vfvWNuiZIkwu9KrKdA1iJKfsfTVxE6NA==", + "license": "BSD-3-Clause" + }, "node_modules/buffer-from": { "version": "1.1.2", "resolved": "https://registry.npmjs.org/buffer-from/-/buffer-from-1.1.2.tgz", @@ -2246,6 +2733,15 @@ "dev": true, "license": "MIT" }, + "node_modules/clean-stack": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/clean-stack/-/clean-stack-2.2.0.tgz", + "integrity": "sha512-4diC9HaTE+KRAMWhDhrGOECgWZxoevMc5TlkObMqNSsVU62PYzXZ/SMTjzyGAFF1YusgxGcSWTEXBhp0CPwQ1A==", + "license": "MIT", + "engines": { + "node": ">=6" + } + }, "node_modules/cli-width": { "version": "4.1.0", "resolved": "https://registry.npmjs.org/cli-width/-/cli-width-4.1.0.tgz", @@ -2511,6 +3007,12 @@ "url": "https://github.com/sponsors/ljharb" } }, + "node_modules/deprecation": { + "version": "2.3.1", + "resolved": "https://registry.npmjs.org/deprecation/-/deprecation-2.3.1.tgz", + "integrity": "sha512-xmHIy4F3scKVwMsQ4WnVaS8bHOx0DmVwRywosKhaILI0ywMDWPtBSku2HNxRvF7jtwDRsoEwYQSfbxj8b7RlJQ==", + "license": "ISC" + }, "node_modules/detect-newline": { "version": "3.1.0", "resolved": "https://registry.npmjs.org/detect-newline/-/detect-newline-3.1.0.tgz", @@ -2544,6 +3046,15 @@ "node": ">=6.0.0" } }, + "node_modules/ecdsa-sig-formatter": { + "version": "1.0.11", + "resolved": "https://registry.npmjs.org/ecdsa-sig-formatter/-/ecdsa-sig-formatter-1.0.11.tgz", + "integrity": "sha512-nagl3RYrbNv6kQkeJIpt6NJZy8twLB/2vtz6yN9Z4vRKHN4/QZJIEbqohALSgwKdnksuY3k5Addp5lg8sVoVcQ==", + "license": "Apache-2.0", + "dependencies": { + "safe-buffer": "^5.0.1" + } + }, "node_modules/electron-to-chromium": { "version": "1.5.67", "resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.5.67.tgz", @@ -3951,6 +4462,15 @@ "node": ">=0.8.19" } }, + "node_modules/indent-string": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/indent-string/-/indent-string-4.0.0.tgz", + "integrity": "sha512-EdDDZu4A2OyIK7Lr/2zG+w5jmbuk1DVBnEwREQvBzspBJkCEbRa8GxU1lghYcaGJCnRWibjDXlq779X1/y5xwg==", + "license": "MIT", + "engines": { + "node": ">=8" + } + }, "node_modules/inflight": { "version": "1.0.6", "resolved": "https://registry.npmjs.org/inflight/-/inflight-1.0.6.tgz", @@ -5253,6 +5773,40 @@ "json5": "lib/cli.js" } }, + "node_modules/jsonwebtoken": { + "version": "9.0.2", + "resolved": "https://registry.npmjs.org/jsonwebtoken/-/jsonwebtoken-9.0.2.tgz", + "integrity": "sha512-PRp66vJ865SSqOlgqS8hujT5U4AOgMfhrwYIuIhfKaoSCZcirrmASQr8CX7cUg+RMih+hgznrjp99o+W4pJLHQ==", + "license": "MIT", + "dependencies": { + "jws": "^3.2.2", + "lodash.includes": "^4.3.0", + "lodash.isboolean": "^3.0.3", + "lodash.isinteger": "^4.0.4", + "lodash.isnumber": "^3.0.3", + "lodash.isplainobject": "^4.0.6", + "lodash.isstring": "^4.0.1", + "lodash.once": "^4.0.0", + "ms": "^2.1.1", + "semver": "^7.5.4" + }, + "engines": { + "node": ">=12", + "npm": ">=6" + } + }, + "node_modules/jsonwebtoken/node_modules/semver": { + "version": "7.6.3", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.6.3.tgz", + "integrity": "sha512-oVekP1cKtI+CTDvHWYFUcMtsK/00wmAEfyqKfNdARm8u1wNVhSgaX7A8d4UuIlUI5e84iEwOhs7ZPYRmzU9U6A==", + "license": "ISC", + "bin": { + "semver": "bin/semver.js" + }, + "engines": { + "node": ">=10" + } + }, "node_modules/jsx-ast-utils": { "version": "3.3.5", "resolved": "https://registry.npmjs.org/jsx-ast-utils/-/jsx-ast-utils-3.3.5.tgz", @@ -5269,6 +5823,27 @@ "node": ">=4.0" } }, + "node_modules/jwa": { + "version": "1.4.1", + "resolved": "https://registry.npmjs.org/jwa/-/jwa-1.4.1.tgz", + "integrity": "sha512-qiLX/xhEEFKUAJ6FiBMbes3w9ATzyk5W7Hvzpa/SLYdxNtng+gcurvrI7TbACjIXlsJyr05/S1oUhZrc63evQA==", + "license": "MIT", + "dependencies": { + "buffer-equal-constant-time": "1.0.1", + "ecdsa-sig-formatter": "1.0.11", + "safe-buffer": "^5.0.1" + } + }, + "node_modules/jws": { + "version": "3.2.2", + "resolved": "https://registry.npmjs.org/jws/-/jws-3.2.2.tgz", + "integrity": "sha512-YHlZCB6lMTllWDtSPHz/ZXTsi8S00usEV6v1tjq8tOUZzw7DpSDWVXjXDre6ed1w/pd495ODpHZYSdkRTsa0HA==", + "license": "MIT", + "dependencies": { + "jwa": "^1.4.1", + "safe-buffer": "^5.0.1" + } + }, "node_modules/keyv": { "version": "4.5.4", "resolved": "https://registry.npmjs.org/keyv/-/keyv-4.5.4.tgz", @@ -5452,6 +6027,42 @@ "integrity": "sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg==", "license": "MIT" }, + "node_modules/lodash.includes": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/lodash.includes/-/lodash.includes-4.3.0.tgz", + "integrity": "sha512-W3Bx6mdkRTGtlJISOvVD/lbqjTlPPUDTMnlXZFnVwi9NKJ6tiAk6LVdlhZMm17VZisqhKcgzpO5Wz91PCt5b0w==", + "license": "MIT" + }, + "node_modules/lodash.isboolean": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/lodash.isboolean/-/lodash.isboolean-3.0.3.tgz", + "integrity": "sha512-Bz5mupy2SVbPHURB98VAcw+aHh4vRV5IPNhILUCsOzRmsTmSQ17jIuqopAentWoehktxGd9e/hbIXq980/1QJg==", + "license": "MIT" + }, + "node_modules/lodash.isinteger": { + "version": "4.0.4", + "resolved": "https://registry.npmjs.org/lodash.isinteger/-/lodash.isinteger-4.0.4.tgz", + "integrity": "sha512-DBwtEWN2caHQ9/imiNeEA5ys1JoRtRfY3d7V9wkqtbycnAmTvRRmbHKDV4a0EYc678/dia0jrte4tjYwVBaZUA==", + "license": "MIT" + }, + "node_modules/lodash.isnumber": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/lodash.isnumber/-/lodash.isnumber-3.0.3.tgz", + "integrity": "sha512-QYqzpfwO3/CWf3XP+Z+tkQsfaLL/EnUlXWVkIk5FUPc4sBdTehEqZONuyRt2P67PXAk+NXmTBcc97zw9t1FQrw==", + "license": "MIT" + }, + "node_modules/lodash.isplainobject": { + "version": "4.0.6", + "resolved": "https://registry.npmjs.org/lodash.isplainobject/-/lodash.isplainobject-4.0.6.tgz", + "integrity": "sha512-oSXzaWypCMHkPC3NvBEaPHf0KsA5mvPrOPgQWDsbg8n7orZ290M0BmC/jgRZ4vcJ6DTAhjrsSYgdsW/F+MFOBA==", + "license": "MIT" + }, + "node_modules/lodash.isstring": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/lodash.isstring/-/lodash.isstring-4.0.1.tgz", + "integrity": "sha512-0wJxfxH1wgO3GrbuP+dTTk7op+6L41QCXbGINEmD+ny/G/eCqGzxyCsh7159S+mgDDcoarnBw6PC1PS5+wUGgw==", + "license": "MIT" + }, "node_modules/lodash.merge": { "version": "4.6.2", "resolved": "https://registry.npmjs.org/lodash.merge/-/lodash.merge-4.6.2.tgz", @@ -5459,6 +6070,12 @@ "dev": true, "license": "MIT" }, + "node_modules/lodash.once": { + "version": "4.1.1", + "resolved": "https://registry.npmjs.org/lodash.once/-/lodash.once-4.1.1.tgz", + "integrity": "sha512-Sb487aTOCr9drQVL8pIxOzVhafOjZN9UU54hiN8PU3uAiSV7lx1yYNpbNmex2PK6dSJoNTSJUUswT651yww3Mg==", + "license": "MIT" + }, "node_modules/loose-envify": { "version": "1.4.0", "resolved": "https://registry.npmjs.org/loose-envify/-/loose-envify-1.4.0.tgz", @@ -5753,11 +6370,31 @@ "url": "https://github.com/sponsors/ljharb" } }, + "node_modules/octokit": { + "version": "3.2.1", + "resolved": "https://registry.npmjs.org/octokit/-/octokit-3.2.1.tgz", + "integrity": "sha512-u+XuSejhe3NdIvty3Jod00JvTdAE/0/+XbhIDhefHbu+2OcTRHd80aCiH6TX19ZybJmwPQBKFQmHGxp0i9mJrg==", + "license": "MIT", + "dependencies": { + "@octokit/app": "^14.0.2", + "@octokit/core": "^5.0.0", + "@octokit/oauth-app": "^6.0.0", + "@octokit/plugin-paginate-graphql": "^4.0.0", + "@octokit/plugin-paginate-rest": "11.3.1", + "@octokit/plugin-rest-endpoint-methods": "13.2.2", + "@octokit/plugin-retry": "^6.0.0", + "@octokit/plugin-throttling": "^8.0.0", + "@octokit/request-error": "^5.0.0", + "@octokit/types": "^13.0.0" + }, + "engines": { + "node": ">= 18" + } + }, "node_modules/once": { "version": "1.4.0", "resolved": "https://registry.npmjs.org/once/-/once-1.4.0.tgz", "integrity": "sha512-lNaJgI+2Q5URQBkccEKHTQOPaXdUxnZZElQTZY0MFUAuaEqe1E+Nyvgdz/aIyNi6Z9MzO5dv1H8n58/GELp3+w==", - "dev": true, "license": "ISC", "dependencies": { "wrappy": "1" @@ -6593,6 +7230,26 @@ "url": "https://github.com/sponsors/ljharb" } }, + "node_modules/safe-buffer": { + "version": "5.2.1", + "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.2.1.tgz", + "integrity": "sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ==", + "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/safe-regex-test": { "version": "1.0.3", "resolved": "https://registry.npmjs.org/safe-regex-test/-/safe-regex-test-1.0.3.tgz", @@ -7270,6 +7927,22 @@ "integrity": "sha512-Ny6QZ2Nju20vw1SRHe3d9jVu6gJ+4e3+MMpqu7pqE5HT6WsTSlce++GQmK5UXS8mzV8DSYHrQH+Xrf2jVcuKNg==", "license": "MIT" }, + "node_modules/universal-github-app-jwt": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/universal-github-app-jwt/-/universal-github-app-jwt-1.2.0.tgz", + "integrity": "sha512-dncpMpnsKBk0eetwfN8D8OUHGfiDhhJ+mtsbMl+7PfW7mYjiH8LIcqRmYMtzYLgSh47HjfdBtrBwIQ/gizKR3g==", + "license": "MIT", + "dependencies": { + "@types/jsonwebtoken": "^9.0.0", + "jsonwebtoken": "^9.0.2" + } + }, + "node_modules/universal-user-agent": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/universal-user-agent/-/universal-user-agent-6.0.1.tgz", + "integrity": "sha512-yCzhz6FN2wU1NiiQRogkTQszlQSlpWaw8SvVegAc+bDxbzHgh1vX8uIe8OYyMH6DwH+sdTJsgMl36+mSMdRJIQ==", + "license": "ISC" + }, "node_modules/update-browserslist-db": { "version": "1.1.1", "resolved": "https://registry.npmjs.org/update-browserslist-db/-/update-browserslist-db-1.1.1.tgz", @@ -7487,7 +8160,6 @@ "version": "1.0.2", "resolved": "https://registry.npmjs.org/wrappy/-/wrappy-1.0.2.tgz", "integrity": "sha512-l4Sp/DRseor9wL6EvV2+TuQn63dMkPjZ/sp9XkghTEbV9KlPS1xUsZ3u7/IQO4wxtcFB4bgpQPRcR3QCvezPcQ==", - "dev": true, "license": "ISC" }, "node_modules/write-file-atomic": { diff --git a/package.json b/package.json index c14b2cf..ed140d2 100644 --- a/package.json +++ b/package.json @@ -8,6 +8,7 @@ "lint:fix": "standard --fix", "test": "jest", "test:coverage": "jest --coverage", + "test:ci": "jest --runInBand --verbose --coverage", "infra:start": "docker-compose up -d", "infra:stop": "docker-compose down", "db:migrate": "knex migrate:latest", @@ -25,6 +26,7 @@ }, "dependencies": { "@ulisesgascon/normalize-boolean": "2.0.0", + "@ulisesgascon/simplify-object": "2.0.0", "@ulisesgascon/string-to-array": "2.0.0", "ajv": "8.17.1", "ajv-formats": "3.0.1", @@ -32,6 +34,7 @@ "debug": "4.3.7", "inquirer": "12.1.0", "knex": "3.1.0", + "octokit": "3.2.1", "pg": "8.13.1", "validator": "13.12.0" }, diff --git a/src/cli/index.js b/src/cli/index.js index 85aa298..b722bb2 100644 --- a/src/cli/index.js +++ b/src/cli/index.js @@ -1,3 +1,7 @@ const project = require('./project') +const workflows = require('./workflows') -module.exports = project +module.exports = { + ...project, + ...workflows +} diff --git a/src/cli/workflows.js b/src/cli/workflows.js new file mode 100644 index 0000000..aa131ad --- /dev/null +++ b/src/cli/workflows.js @@ -0,0 +1,44 @@ +const { updateGithubOrgs } = require('../workflows') +const inquirer = require('inquirer').default + +const commandList = [{ + name: 'update-github-orgs', + description: 'Check the organizations stored and update the information with the GitHub API.' +}] + +const validCommandNames = commandList.map(({ name }) => name) + +function listWorkflowCommand (options = {}) { + console.log('Available workflows:') + console.log(commandList.map(({ name, description }) => `- ${name}: ${description}`).join('\n')) + return commandList +} + +async function runWorkflowCommand (knex, options = {}) { + if (Object.keys(options).length && !validCommandNames.includes(options.name)) { + throw new Error('Invalid workflow name. Please enter a valid workflow name.') + } + + const answers = options.name + ? options + : await inquirer.prompt([ + { + type: 'list', + name: 'name', + message: 'What is the name of the workflow?', + choices: validCommandNames, + when: () => !options.name + } + ]) + + if (answers.name === 'update-github-orgs') { + await updateGithubOrgs(knex) + } + + return answers +} + +module.exports = { + listWorkflowCommand, + runWorkflowCommand +} diff --git a/src/providers/index.js b/src/providers/index.js new file mode 100644 index 0000000..ccc583d --- /dev/null +++ b/src/providers/index.js @@ -0,0 +1,21 @@ +const { Octokit } = require('octokit') +const { ensureGithubToken } = require('../utils') +const debug = require('debug')('providers:github') + +const fetchOrgByLogin = async (login) => { + debug(`Fetching organization (${login})...`) + ensureGithubToken() + const octokit = new Octokit({ auth: process.env.GITHUB_TOKEN }) + const { data } = await octokit.request('GET /orgs/{org}', { + org: login + }) + return data +} + +const github = { + fetchOrgByLogin +} + +module.exports = { + github +} diff --git a/src/store/index.js b/src/store/index.js index 4380c6e..16c99ed 100644 --- a/src/store/index.js +++ b/src/store/index.js @@ -1,5 +1,16 @@ const debug = require('debug')('store') +const getAllGithubOrganizations = knex => async () => { + debug('Getting all GitHub organizations...') + return knex('github_organizations').select() +} + +const updateGithubOrganization = knex => async (organization) => { + const { login } = organization + debug(`Updating organization (${login})...`) + return knex('github_organizations').where({ login }).update(organization) +} + const addGithubOrganization = knex => async (organization) => { const organizationExists = await knex('github_organizations').where({ html_url: organization.html_url }).first() debug(`Checking if organization (${organization.login}) exists...`) @@ -31,7 +42,9 @@ const initializeStore = (knex) => { debug('Initializing store...') return { addProject: addProject(knex), - addGithubOrganization: addGithubOrganization(knex) + addGithubOrganization: addGithubOrganization(knex), + getAllGithubOrganizations: getAllGithubOrganizations(knex), + updateGithubOrganization: updateGithubOrganization(knex) } } diff --git a/src/workflows/index.js b/src/workflows/index.js new file mode 100644 index 0000000..fe6dc2b --- /dev/null +++ b/src/workflows/index.js @@ -0,0 +1,59 @@ +const debug = require('debug')('workflows') +const { simplifyObject } = require('@ulisesgascon/simplify-object') +const { github } = require('../providers') +const { initializeStore } = require('../store') + +const mapOrgData = (data) => { + const mappedData = simplifyObject(data, { + // Relevant fields for organizations + include: [ + 'login', 'node_id', 'url', 'avatar_url', + 'description', 'name', 'company', 'blog', + 'location', 'twitter_username', 'is_verified', + 'has_organization_projects', 'has_repository_projects', + 'public_repos', 'public_gists', 'followers', + 'following', 'html_url', 'total_private_repos', + 'owned_private_repos', 'private_gists', 'disk_usage', + 'collaborators', 'default_repository_permission', + 'default_repository_permission', 'members_can_create_repositories', + 'two_factor_requirement_enabled', 'members_allowed_repository_creation_type', + 'members_can_create_public_repositories', 'members_can_create_private_repositories', + 'members_can_create_internal_repositories', 'members_can_create_pages', + 'members_can_create_public_pages', 'members_can_create_private_pages', + 'members_can_fork_private_repositories', 'web_commit_signoff_required', + 'deploy_keys_enabled_for_repositories', 'dependency_graph_enabled_for_new_repositories', + 'dependabot_alerts_enabled_for_new_repositories', 'dependabot_security_updates_enabled_for_new_repositories', + 'advanced_security_enabled_for_new_repositories', 'secret_scanning_enabled_for_new_repositories', + 'secret_scanning_push_protection_enabled_for_new_repositories', 'secret_scanning_push_protection_custom_link', + 'secret_scanning_push_protection_custom_link_enabled'] + }) + // Additional fields that have different names in the database + mappedData.github_org_id = data.id + mappedData.github_created_at = data.created_at + mappedData.github_updated_at = data.updated_at + mappedData.github_archived_at = data.archived_at + return mappedData +} + +const updateGithubOrgs = async (knex) => { + const { getAllGithubOrganizations, updateGithubOrganization } = initializeStore(knex) + const organizations = await getAllGithubOrganizations() + debug('Checking organizations details') + if (organizations.length === 0) { + throw new Error('No organizations found. Please add organizations/projects before running this workflow.') + } + debug('Fetching details for organizations from GitHub API') + await Promise.all(organizations.map(async (org) => { + debug(`Fetching details for org (${org.login})`) + const data = await github.fetchOrgByLogin(org.login) + debug('Transforming data') + const mappedData = mapOrgData(data) + debug('Updating organization in database') + await updateGithubOrganization(mappedData) + })) + console.log('GitHub organizations updated successfully') +} + +module.exports = { + updateGithubOrgs +}