From 40877194c5c514d586ff795d325dd68a10921d5c Mon Sep 17 00:00:00 2001 From: Sanjula Ganepola Date: Wed, 5 Aug 2026 10:45:12 -0400 Subject: [PATCH 1/4] Add support for proxyCmd Signed-off-by: Sanjula Ganepola --- api/runner.ts | 31 +++++++++++++++++++++---------- api/types.ts | 13 +++++++++---- schemas/testing.json | 20 ++++++++++++++++++++ 3 files changed, 50 insertions(+), 14 deletions(-) diff --git a/api/runner.ts b/api/runner.ts index f2a9560..cf4292d 100644 --- a/api/runner.ts +++ b/api/runner.ts @@ -1,5 +1,5 @@ -import { MergedCoverageData, BasicUri, CODECOV, CompilationStatus, DeploymentStatus, Env, LogLevel, MappedCoverageData, RUCALLTST, RUCRTCBL, RUCRTRPG, TestBucket, TestCase, TestCaseResult, TestMetrics, TestRequest, TestSuite, WrapperCmd, TestOutcome, AssertionResult } from "./types"; +import { MergedCoverageData, BasicUri, CODECOV, CommandOverrides, CompilationStatus, DeploymentStatus, Env, LogLevel, MappedCoverageData, RUCALLTST, RUCRTCBL, RUCRTRPG, TestBucket, TestCase, TestCaseResult, TestMetrics, TestRequest, TestSuite, WrapperCmd, TestOutcome, AssertionResult } from "./types"; import { TestLogger } from "./testLogger"; import { ILELibrarySettings } from "vscode-ibmi/src/api/CompileTools"; import IBMi from "vscode-ibmi/src/api/IBMi"; @@ -298,14 +298,17 @@ export class Runner { return 'errored'; } + let proxyCmd: string | undefined; let wrapperCmd: WrapperCmd | undefined; const isRPGLE = ApiUtils.isRPGLE(testSuitePath); if (isRPGLE) { const rucrtrpg = testSuite.testingConfig?.rpgunit?.rucrtrpg; + proxyCmd = testSuite.testingConfig?.rpgunit?.rucrtrpg?.proxyCmd; wrapperCmd = testSuite.testingConfig?.rpgunit?.rucrtrpg?.wrapperCmd; - if (wrapperCmd) { - delete rucrtrpg?.wrapperCmd; + if (rucrtrpg) { + delete (rucrtrpg as CommandOverrides).proxyCmd; + delete (rucrtrpg as CommandOverrides).wrapperCmd; } compileParams = { @@ -318,9 +321,11 @@ export class Runner { } } else { const rucrtcbl = testSuite.testingConfig?.rpgunit?.rucrtcbl; + proxyCmd = testSuite.testingConfig?.rpgunit?.rucrtcbl?.proxyCmd; wrapperCmd = testSuite.testingConfig?.rpgunit?.rucrtcbl?.wrapperCmd; - if (wrapperCmd) { - delete rucrtcbl?.wrapperCmd; + if (rucrtcbl) { + delete (rucrtcbl as CommandOverrides).proxyCmd; + delete (rucrtcbl as CommandOverrides).wrapperCmd; } compileParams = { @@ -392,7 +397,8 @@ export class Runner { // Build compile command const productLibrary = this.testCallbacks.getProductLibrary(); const languageSpecificCommand = isRPGLE ? 'RUCRTRPG' : 'RUCRTCBL'; - let compileCommand = content.toCl(`${productLibrary}/${languageSpecificCommand}`, flattenedCompileParams as any); + const compileCommandName = proxyCmd || `${productLibrary}/${languageSpecificCommand}`; + let compileCommand = content.toCl(compileCommandName, flattenedCompileParams as any); // Wrap compile command if a wrapper command is specified if (wrapperCmd && wrapperCmd.cmd) { @@ -522,9 +528,11 @@ export class Runner { // Merge base execution params (ie. from VS Code settings) and execution params from config file const baseExecutionParams = this.testCallbacks.getBaseExecutionParams(qualifiedTstPgm, xmlStmf, testCase?.name); const rucalltst = testSuite.testingConfig?.rpgunit?.rucalltst; + const rucalltstProxyCmd = testSuite.testingConfig?.rpgunit?.rucalltst?.proxyCmd; const rucalltstWrapperCmd = testSuite.testingConfig?.rpgunit?.rucalltst?.wrapperCmd; - if (rucalltstWrapperCmd) { - delete rucalltst?.wrapperCmd; + if (rucalltst) { + delete (rucalltst as CommandOverrides).proxyCmd; + delete (rucalltst as CommandOverrides).wrapperCmd; } const testParams: RUCALLTST = { ...baseExecutionParams, @@ -547,7 +555,8 @@ export class Runner { // Build call tests command const productLibrary = this.testCallbacks.getProductLibrary(); - let testCommand = content.toCl(`${productLibrary}/RUCALLTST`, testParams as any); + const testCommandName = rucalltstProxyCmd || `${productLibrary}/RUCALLTST`; + let testCommand = content.toCl(testCommandName, testParams as any); // Wrap call tests command if a wrapper command is specified if (rucalltstWrapperCmd && rucalltstWrapperCmd.cmd) { @@ -559,6 +568,7 @@ export class Runner { // Build CODECOV command if code coverage is enabled let coverageParams: CODECOV | undefined; if (testSuite.ccLvl) { + const codecovProxyCmd = testSuite.testingConfig?.codecov?.proxyCmd; const codecovWrapperCmd = testSuite.testingConfig?.codecov?.wrapperCmd; coverageParams = { cmd: testCommand, @@ -577,7 +587,8 @@ export class Runner { coverageParams.module = coverageParams.module.map((m: string) => `(${m})`); const flattenedCoverageParams = ApiUtils.flattenCommandParams(coverageParams); - const codecovCommand = `QDEVTOOLS/CODECOV CMD(${flattenedCoverageParams.cmd}) MODULE(${flattenedCoverageParams.module}) CCLVL(${flattenedCoverageParams.ccLvl}) OUTSTMF('${flattenedCoverageParams.outStmf}')`; + const codecovCommandName = codecovProxyCmd || `QDEVTOOLS/CODECOV`; + const codecovCommand = `${codecovCommandName} CMD(${flattenedCoverageParams.cmd}) MODULE(${flattenedCoverageParams.module}) CCLVL(${flattenedCoverageParams.ccLvl}) OUTSTMF('${flattenedCoverageParams.outStmf}')`; // Wrap code coverage command if a wrapper command is specified if (codecovWrapperCmd && codecovWrapperCmd.cmd) { diff --git a/api/types.ts b/api/types.ts index 7bfda55..04ddfa7 100644 --- a/api/types.ts +++ b/api/types.ts @@ -151,11 +151,16 @@ export interface TestMetrics { export interface TestingConfig { rpgunit?: { - rucrtrpg?: RUCRTRPG & { wrapperCmd?: WrapperCmd }, - rucrtcbl?: RUCRTCBL & { wrapperCmd?: WrapperCmd }, - rucalltst?: RUCALLTST & { wrapperCmd?: WrapperCmd } + rucrtrpg?: RUCRTRPG & CommandOverrides, + rucrtcbl?: RUCRTCBL & CommandOverrides, + rucalltst?: RUCALLTST & CommandOverrides }, - codecov?: CODECOV & { wrapperCmd?: WrapperCmd } + codecov?: CODECOV & CommandOverrides +} + +export interface CommandOverrides { + proxyCmd?: string, + wrapperCmd?: WrapperCmd } export interface RUCRTRPG { diff --git a/schemas/testing.json b/schemas/testing.json index ea58f32..0de7f4a 100644 --- a/schemas/testing.json +++ b/schemas/testing.json @@ -375,6 +375,11 @@ "default": "*JOB", "markdownDescription": "Target CCSID\n\nSpecifies the CCSID that the compiler uses to read the source files." }, + "proxyCmd": { + "type": "string", + "default": "", + "markdownDescription": "Proxy Command\n\nSpecifies a command to substitute the `RUCRTRPG` command." + }, "wrapperCmd": { "type": "object", "markdownDescription": "Wrapper Command\n\nSpecifies a custom command to wrap the `RUCRTRPG` command.", @@ -777,6 +782,11 @@ "default": "*JOB", "markdownDescription": "Target CCSID\n\nSpecifies the CCSID that the compiler uses to read the source files." }, + "proxyCmd": { + "type": "string", + "default": "", + "markdownDescription": "Proxy Command\n\nSpecifies a command to substitute the `RUCRTCBL` command." + }, "wrapperCmd": { "type": "object", "markdownDescription": "Wrapper Command\n\nSpecifies a custom command to wrap the `RUCRTCBL` command.", @@ -924,6 +934,11 @@ ], "markdownDescription": "On Failure\n\nSpecifies test execution behavior after a failing assertion." }, + "proxyCmd": { + "type": "string", + "default": "", + "markdownDescription": "Proxy Command\n\nSpecifies a command to substitute the `RUCALLTST` command." + }, "wrapperCmd": { "type": "object", "markdownDescription": "Wrapper Command\n\nSpecifies a custom command to wrap the `RUCALLTST` command.", @@ -984,6 +999,11 @@ }, "markdownDescription": "Exclude\n\nObjects to exclude" }, + "proxyCmd": { + "type": "string", + "default": "", + "markdownDescription": "Proxy Command\n\nSpecifies a command to substitute the `CODECOV` command." + }, "wrapperCmd": { "type": "object", "markdownDescription": "Wrapper Command\n\nSpecifies a custom command to wrap the `CODECOV` command.", From a5361c136ffba677db71ae9359b3a5f619860717 Mon Sep 17 00:00:00 2001 From: Sanjula Ganepola Date: Wed, 5 Aug 2026 11:32:05 -0400 Subject: [PATCH 2/4] Support custom parameters on CODECOV command Signed-off-by: Sanjula Ganepola --- api/runner.ts | 12 +++++++++--- 1 file changed, 9 insertions(+), 3 deletions(-) diff --git a/api/runner.ts b/api/runner.ts index cf4292d..ce6eafe 100644 --- a/api/runner.ts +++ b/api/runner.ts @@ -574,9 +574,7 @@ export class Runner { cmd: testCommand, module: [], ccLvl: testSuite.ccLvl, - ccView: testSuite.testingConfig?.codecov?.ccView, outStmf: testStorage.CODECOV, - testId: testSuite.testingConfig?.codecov?.testId, }; // Add the service program under test and modules from the testing config @@ -588,7 +586,15 @@ export class Runner { const flattenedCoverageParams = ApiUtils.flattenCommandParams(coverageParams); const codecovCommandName = codecovProxyCmd || `QDEVTOOLS/CODECOV`; - const codecovCommand = `${codecovCommandName} CMD(${flattenedCoverageParams.cmd}) MODULE(${flattenedCoverageParams.module}) CCLVL(${flattenedCoverageParams.ccLvl}) OUTSTMF('${flattenedCoverageParams.outStmf}')`; + let codecovCommand = `${codecovCommandName} CMD(${flattenedCoverageParams.cmd}) MODULE(${flattenedCoverageParams.module}) CCLVL(${flattenedCoverageParams.ccLvl}) OUTSTMF('${flattenedCoverageParams.outStmf}')`; + + // Append extra params from the codecov config (anything beyond the fields already in the hand-rolled command) + const knownCodecovKeys = new Set([...Object.keys(flattenedCoverageParams), `proxyCmd`, `wrapperCmd`]); + const extraCodecovParams = Object.fromEntries( + Object.entries(testSuite.testingConfig?.codecov || {}).filter(([key]) => !knownCodecovKeys.has(key)) + ); + const flattenedExtraCodecovParams = ApiUtils.flattenCommandParams(extraCodecovParams); + codecovCommand = content.toCl(codecovCommand, flattenedExtraCodecovParams); // Wrap code coverage command if a wrapper command is specified if (codecovWrapperCmd && codecovWrapperCmd.cmd) { From 452221adde0525a54c878d61229aa4fd4dd94154 Mon Sep 17 00:00:00 2001 From: Sanjula Ganepola Date: Thu, 27 Aug 2026 14:19:41 -0400 Subject: [PATCH 3/4] Add support for optional github install Signed-off-by: Sanjula Ganepola --- package-lock.json | 571 +++++++++++++++++++++++++++--- package.json | 1 + src/components/rpgUnit/github.ts | 64 ++++ src/components/rpgUnit/rpgUnit.ts | 223 +++++++++--- 4 files changed, 759 insertions(+), 100 deletions(-) create mode 100644 src/components/rpgUnit/github.ts diff --git a/package-lock.json b/package-lock.json index d712d7d..6982f71 100644 --- a/package-lock.json +++ b/package-lock.json @@ -13,6 +13,7 @@ "ansi-colors": "^4.1.3", "compare-versions": "^6.1.1", "node-fetch": "^3.3.2", + "octokit": "^5.0.5", "tmp": "^0.2.5" }, "devDependencies": { @@ -287,28 +288,228 @@ "@jridgewell/sourcemap-codec": "^1.4.14" } }, + "node_modules/@octokit/app": { + "version": "16.1.4", + "resolved": "https://registry.npmjs.org/@octokit/app/-/app-16.1.4.tgz", + "integrity": "sha512-g70WONQyGoBgqIJtv4O0MUEfOF1iJpTfFt3qAW7HaiwiJ6k607xg/gBezCa4tuq6BpO6qXn7qvq8v9tjWtgGQg==", + "license": "MIT", + "dependencies": { + "@octokit/auth-app": "^8.3.0", + "@octokit/auth-unauthenticated": "^7.0.4", + "@octokit/core": "^7.0.7", + "@octokit/oauth-app": "^8.0.4", + "@octokit/plugin-paginate-rest": "^15.0.0", + "@octokit/types": "^17.0.0", + "@octokit/webhooks": "^14.0.0" + }, + "engines": { + "node": ">= 20" + } + }, + "node_modules/@octokit/app/node_modules/@octokit/openapi-types": { + "version": "28.0.0", + "resolved": "https://registry.npmjs.org/@octokit/openapi-types/-/openapi-types-28.0.0.tgz", + "integrity": "sha512-0rFyLuyHvIj6uuZWuDslxkowFYdPXoNIkeAv4b27dzm2Tf4vGWXnPsMcxs7d65kLdMERgP3wc1AEPlqMz8e1cQ==", + "license": "MIT" + }, + "node_modules/@octokit/app/node_modules/@octokit/plugin-paginate-rest": { + "version": "15.0.0", + "resolved": "https://registry.npmjs.org/@octokit/plugin-paginate-rest/-/plugin-paginate-rest-15.0.0.tgz", + "integrity": "sha512-lw9A9YL5s4VPj+VEx8uMxaxQm2YTgrPDkrLEuZuu18R8TK3oPcXF4K6t52nx6xn1RcogZC9i188sAr/4XYJaQQ==", + "license": "MIT", + "dependencies": { + "@octokit/types": "^17.0.0" + }, + "engines": { + "node": ">= 20" + }, + "peerDependencies": { + "@octokit/core": ">=6" + } + }, + "node_modules/@octokit/app/node_modules/@octokit/types": { + "version": "17.0.0", + "resolved": "https://registry.npmjs.org/@octokit/types/-/types-17.0.0.tgz", + "integrity": "sha512-ByP1v7YL5SMveFPP7+sj0/ZuWCOOg/Chs4NafOMpq6WNIM/hdGY0S7C0TCGDBWu1aGmOxmUIhMx3cO+IdwYZ1Q==", + "license": "MIT", + "dependencies": { + "@octokit/openapi-types": "^28.0.0" + } + }, + "node_modules/@octokit/auth-app": { + "version": "8.3.0", + "resolved": "https://registry.npmjs.org/@octokit/auth-app/-/auth-app-8.3.0.tgz", + "integrity": "sha512-/UaKmJCsOc5XBZwhnFiGNdLH/FkDF8lYtBn1QlKxtX7IpgaRB/XOXjixFtERAknyUZHxp3oDuoiG0En4VptJSg==", + "license": "MIT", + "dependencies": { + "@octokit/auth-oauth-app": "^9.0.4", + "@octokit/auth-oauth-user": "^6.0.3", + "@octokit/request": "^10.0.13", + "@octokit/request-error": "^7.1.1", + "@octokit/types": "^17.0.0", + "toad-cache": "^3.7.0", + "universal-github-app-jwt": "^2.2.0", + "universal-user-agent": "^7.0.0" + }, + "engines": { + "node": ">= 20" + } + }, + "node_modules/@octokit/auth-app/node_modules/@octokit/openapi-types": { + "version": "28.0.0", + "resolved": "https://registry.npmjs.org/@octokit/openapi-types/-/openapi-types-28.0.0.tgz", + "integrity": "sha512-0rFyLuyHvIj6uuZWuDslxkowFYdPXoNIkeAv4b27dzm2Tf4vGWXnPsMcxs7d65kLdMERgP3wc1AEPlqMz8e1cQ==", + "license": "MIT" + }, + "node_modules/@octokit/auth-app/node_modules/@octokit/types": { + "version": "17.0.0", + "resolved": "https://registry.npmjs.org/@octokit/types/-/types-17.0.0.tgz", + "integrity": "sha512-ByP1v7YL5SMveFPP7+sj0/ZuWCOOg/Chs4NafOMpq6WNIM/hdGY0S7C0TCGDBWu1aGmOxmUIhMx3cO+IdwYZ1Q==", + "license": "MIT", + "dependencies": { + "@octokit/openapi-types": "^28.0.0" + } + }, + "node_modules/@octokit/auth-oauth-app": { + "version": "9.0.4", + "resolved": "https://registry.npmjs.org/@octokit/auth-oauth-app/-/auth-oauth-app-9.0.4.tgz", + "integrity": "sha512-Pe3du5LrC6dlv10b0RbarBlJtIT1Urq8BFoQklK8WmBw8YKnGgrANn7cQmHiA1v8AVihgi7YutV8MncP57I4og==", + "license": "MIT", + "dependencies": { + "@octokit/auth-oauth-device": "^8.0.4", + "@octokit/auth-oauth-user": "^6.0.3", + "@octokit/request": "^10.0.13", + "@octokit/types": "^17.0.0", + "universal-user-agent": "^7.0.0" + }, + "engines": { + "node": ">= 20" + } + }, + "node_modules/@octokit/auth-oauth-app/node_modules/@octokit/openapi-types": { + "version": "28.0.0", + "resolved": "https://registry.npmjs.org/@octokit/openapi-types/-/openapi-types-28.0.0.tgz", + "integrity": "sha512-0rFyLuyHvIj6uuZWuDslxkowFYdPXoNIkeAv4b27dzm2Tf4vGWXnPsMcxs7d65kLdMERgP3wc1AEPlqMz8e1cQ==", + "license": "MIT" + }, + "node_modules/@octokit/auth-oauth-app/node_modules/@octokit/types": { + "version": "17.0.0", + "resolved": "https://registry.npmjs.org/@octokit/types/-/types-17.0.0.tgz", + "integrity": "sha512-ByP1v7YL5SMveFPP7+sj0/ZuWCOOg/Chs4NafOMpq6WNIM/hdGY0S7C0TCGDBWu1aGmOxmUIhMx3cO+IdwYZ1Q==", + "license": "MIT", + "dependencies": { + "@octokit/openapi-types": "^28.0.0" + } + }, + "node_modules/@octokit/auth-oauth-device": { + "version": "8.0.4", + "resolved": "https://registry.npmjs.org/@octokit/auth-oauth-device/-/auth-oauth-device-8.0.4.tgz", + "integrity": "sha512-M/+34rmkxMvUnnTo/uqNeVRCXJoJ+5N34eNTbL1KMtIyJpedCTsu/iFL1cWdL+w5hQMlYt6xzXoyux2gn0OVnw==", + "license": "MIT", + "dependencies": { + "@octokit/oauth-methods": "^6.0.3", + "@octokit/request": "^10.0.13", + "@octokit/types": "^17.0.0", + "universal-user-agent": "^7.0.0" + }, + "engines": { + "node": ">= 20" + } + }, + "node_modules/@octokit/auth-oauth-device/node_modules/@octokit/openapi-types": { + "version": "28.0.0", + "resolved": "https://registry.npmjs.org/@octokit/openapi-types/-/openapi-types-28.0.0.tgz", + "integrity": "sha512-0rFyLuyHvIj6uuZWuDslxkowFYdPXoNIkeAv4b27dzm2Tf4vGWXnPsMcxs7d65kLdMERgP3wc1AEPlqMz8e1cQ==", + "license": "MIT" + }, + "node_modules/@octokit/auth-oauth-device/node_modules/@octokit/types": { + "version": "17.0.0", + "resolved": "https://registry.npmjs.org/@octokit/types/-/types-17.0.0.tgz", + "integrity": "sha512-ByP1v7YL5SMveFPP7+sj0/ZuWCOOg/Chs4NafOMpq6WNIM/hdGY0S7C0TCGDBWu1aGmOxmUIhMx3cO+IdwYZ1Q==", + "license": "MIT", + "dependencies": { + "@octokit/openapi-types": "^28.0.0" + } + }, + "node_modules/@octokit/auth-oauth-user": { + "version": "6.0.3", + "resolved": "https://registry.npmjs.org/@octokit/auth-oauth-user/-/auth-oauth-user-6.0.3.tgz", + "integrity": "sha512-t4OKUhrI5britmpRiSzPAz1TJPyUN/Hw1HEE3Hz/uElxcmnf/YCRSKtFRNc5gy4yJFTlXgk34H1lvxiotQJQvQ==", + "license": "MIT", + "dependencies": { + "@octokit/auth-oauth-device": "^8.0.4", + "@octokit/oauth-methods": "^6.0.3", + "@octokit/request": "^10.0.13", + "@octokit/types": "^17.0.0", + "universal-user-agent": "^7.0.0" + }, + "engines": { + "node": ">= 20" + } + }, + "node_modules/@octokit/auth-oauth-user/node_modules/@octokit/openapi-types": { + "version": "28.0.0", + "resolved": "https://registry.npmjs.org/@octokit/openapi-types/-/openapi-types-28.0.0.tgz", + "integrity": "sha512-0rFyLuyHvIj6uuZWuDslxkowFYdPXoNIkeAv4b27dzm2Tf4vGWXnPsMcxs7d65kLdMERgP3wc1AEPlqMz8e1cQ==", + "license": "MIT" + }, + "node_modules/@octokit/auth-oauth-user/node_modules/@octokit/types": { + "version": "17.0.0", + "resolved": "https://registry.npmjs.org/@octokit/types/-/types-17.0.0.tgz", + "integrity": "sha512-ByP1v7YL5SMveFPP7+sj0/ZuWCOOg/Chs4NafOMpq6WNIM/hdGY0S7C0TCGDBWu1aGmOxmUIhMx3cO+IdwYZ1Q==", + "license": "MIT", + "dependencies": { + "@octokit/openapi-types": "^28.0.0" + } + }, "node_modules/@octokit/auth-token": { "version": "6.0.0", "resolved": "https://registry.npmjs.org/@octokit/auth-token/-/auth-token-6.0.0.tgz", "integrity": "sha512-P4YJBPdPSpWTQ1NU4XYdvHvXJJDxM6YwpS0FZHRgP7YFkdVxsWcpWGy/NVqlAA7PcPCnMacXlRm1y2PFZRWL/w==", - "dev": true, "license": "MIT", "engines": { "node": ">= 20" } }, + "node_modules/@octokit/auth-unauthenticated": { + "version": "7.0.4", + "resolved": "https://registry.npmjs.org/@octokit/auth-unauthenticated/-/auth-unauthenticated-7.0.4.tgz", + "integrity": "sha512-j4zgVdP8C8C53PNsj4LLI+WFtoykaQARwwrIBILTdxwZ2Ljaa9YUxb1yQ7/seGVnmWPdC3OBefX3LLbh28gKtw==", + "license": "MIT", + "dependencies": { + "@octokit/request-error": "^7.1.1", + "@octokit/types": "^17.0.0" + }, + "engines": { + "node": ">= 20" + } + }, + "node_modules/@octokit/auth-unauthenticated/node_modules/@octokit/openapi-types": { + "version": "28.0.0", + "resolved": "https://registry.npmjs.org/@octokit/openapi-types/-/openapi-types-28.0.0.tgz", + "integrity": "sha512-0rFyLuyHvIj6uuZWuDslxkowFYdPXoNIkeAv4b27dzm2Tf4vGWXnPsMcxs7d65kLdMERgP3wc1AEPlqMz8e1cQ==", + "license": "MIT" + }, + "node_modules/@octokit/auth-unauthenticated/node_modules/@octokit/types": { + "version": "17.0.0", + "resolved": "https://registry.npmjs.org/@octokit/types/-/types-17.0.0.tgz", + "integrity": "sha512-ByP1v7YL5SMveFPP7+sj0/ZuWCOOg/Chs4NafOMpq6WNIM/hdGY0S7C0TCGDBWu1aGmOxmUIhMx3cO+IdwYZ1Q==", + "license": "MIT", + "dependencies": { + "@octokit/openapi-types": "^28.0.0" + } + }, "node_modules/@octokit/core": { - "version": "7.0.6", - "resolved": "https://registry.npmjs.org/@octokit/core/-/core-7.0.6.tgz", - "integrity": "sha512-DhGl4xMVFGVIyMwswXeyzdL4uXD5OGILGX5N8Y+f6W7LhC1Ze2poSNrkF/fedpVDHEEZ+PHFW0vL14I+mm8K3Q==", - "dev": true, + "version": "7.0.7", + "resolved": "https://registry.npmjs.org/@octokit/core/-/core-7.0.7.tgz", + "integrity": "sha512-DcB0M3KFgr9ECI328lhBMVsyFT2DnmNucSBTqEN3exyNKUzkkpUSCHmTRcunF41Eou2TIQKW4seewri8ON9bSA==", "license": "MIT", "dependencies": { "@octokit/auth-token": "^6.0.0", - "@octokit/graphql": "^9.0.3", - "@octokit/request": "^10.0.6", - "@octokit/request-error": "^7.0.2", - "@octokit/types": "^16.0.0", + "@octokit/graphql": "^9.0.4", + "@octokit/request": "^10.0.13", + "@octokit/request-error": "^7.1.1", + "@octokit/types": "^17.0.0", "before-after-hook": "^4.0.0", "universal-user-agent": "^7.0.0" }, @@ -316,47 +517,164 @@ "node": ">= 20" } }, + "node_modules/@octokit/core/node_modules/@octokit/openapi-types": { + "version": "28.0.0", + "resolved": "https://registry.npmjs.org/@octokit/openapi-types/-/openapi-types-28.0.0.tgz", + "integrity": "sha512-0rFyLuyHvIj6uuZWuDslxkowFYdPXoNIkeAv4b27dzm2Tf4vGWXnPsMcxs7d65kLdMERgP3wc1AEPlqMz8e1cQ==", + "license": "MIT" + }, + "node_modules/@octokit/core/node_modules/@octokit/types": { + "version": "17.0.0", + "resolved": "https://registry.npmjs.org/@octokit/types/-/types-17.0.0.tgz", + "integrity": "sha512-ByP1v7YL5SMveFPP7+sj0/ZuWCOOg/Chs4NafOMpq6WNIM/hdGY0S7C0TCGDBWu1aGmOxmUIhMx3cO+IdwYZ1Q==", + "license": "MIT", + "dependencies": { + "@octokit/openapi-types": "^28.0.0" + } + }, "node_modules/@octokit/endpoint": { - "version": "11.0.3", - "resolved": "https://registry.npmjs.org/@octokit/endpoint/-/endpoint-11.0.3.tgz", - "integrity": "sha512-FWFlNxghg4HrXkD3ifYbS/IdL/mDHjh9QcsNyhQjN8dplUoZbejsdpmuqdA76nxj2xoWPs7p8uX2SNr9rYu0Ag==", - "dev": true, + "version": "11.0.4", + "resolved": "https://registry.npmjs.org/@octokit/endpoint/-/endpoint-11.0.4.tgz", + "integrity": "sha512-f1cOWoHPmxryJFknxbtDdjODWfV8A9tc8Aae6ermXPNgHFZ/x91AtHIz4gicEjL8hkJiip+u21QHJORfBv/qiA==", "license": "MIT", "dependencies": { - "@octokit/types": "^16.0.0", + "@octokit/types": "^17.0.0", "universal-user-agent": "^7.0.2" }, "engines": { "node": ">= 20" } }, + "node_modules/@octokit/endpoint/node_modules/@octokit/openapi-types": { + "version": "28.0.0", + "resolved": "https://registry.npmjs.org/@octokit/openapi-types/-/openapi-types-28.0.0.tgz", + "integrity": "sha512-0rFyLuyHvIj6uuZWuDslxkowFYdPXoNIkeAv4b27dzm2Tf4vGWXnPsMcxs7d65kLdMERgP3wc1AEPlqMz8e1cQ==", + "license": "MIT" + }, + "node_modules/@octokit/endpoint/node_modules/@octokit/types": { + "version": "17.0.0", + "resolved": "https://registry.npmjs.org/@octokit/types/-/types-17.0.0.tgz", + "integrity": "sha512-ByP1v7YL5SMveFPP7+sj0/ZuWCOOg/Chs4NafOMpq6WNIM/hdGY0S7C0TCGDBWu1aGmOxmUIhMx3cO+IdwYZ1Q==", + "license": "MIT", + "dependencies": { + "@octokit/openapi-types": "^28.0.0" + } + }, "node_modules/@octokit/graphql": { - "version": "9.0.3", - "resolved": "https://registry.npmjs.org/@octokit/graphql/-/graphql-9.0.3.tgz", - "integrity": "sha512-grAEuupr/C1rALFnXTv6ZQhFuL1D8G5y8CN04RgrO4FIPMrtm+mcZzFG7dcBm+nq+1ppNixu+Jd78aeJOYxlGA==", - "dev": true, + "version": "9.0.4", + "resolved": "https://registry.npmjs.org/@octokit/graphql/-/graphql-9.0.4.tgz", + "integrity": "sha512-5s15CCiY8XXQ+FG+b1YQcl6Z2FA++nwAz/tg2VUrTmnMncP+2nnGUEYANImdnxsA2Fnq+Mbl7hDjUTw7cFAwcg==", "license": "MIT", "dependencies": { - "@octokit/request": "^10.0.6", - "@octokit/types": "^16.0.0", + "@octokit/request": "^10.0.13", + "@octokit/types": "^17.0.0", "universal-user-agent": "^7.0.0" }, "engines": { "node": ">= 20" } }, + "node_modules/@octokit/graphql/node_modules/@octokit/openapi-types": { + "version": "28.0.0", + "resolved": "https://registry.npmjs.org/@octokit/openapi-types/-/openapi-types-28.0.0.tgz", + "integrity": "sha512-0rFyLuyHvIj6uuZWuDslxkowFYdPXoNIkeAv4b27dzm2Tf4vGWXnPsMcxs7d65kLdMERgP3wc1AEPlqMz8e1cQ==", + "license": "MIT" + }, + "node_modules/@octokit/graphql/node_modules/@octokit/types": { + "version": "17.0.0", + "resolved": "https://registry.npmjs.org/@octokit/types/-/types-17.0.0.tgz", + "integrity": "sha512-ByP1v7YL5SMveFPP7+sj0/ZuWCOOg/Chs4NafOMpq6WNIM/hdGY0S7C0TCGDBWu1aGmOxmUIhMx3cO+IdwYZ1Q==", + "license": "MIT", + "dependencies": { + "@octokit/openapi-types": "^28.0.0" + } + }, + "node_modules/@octokit/oauth-app": { + "version": "8.0.4", + "resolved": "https://registry.npmjs.org/@octokit/oauth-app/-/oauth-app-8.0.4.tgz", + "integrity": "sha512-Ji5JpRwAJcbOJkO4ij0tW6+GrQ8efpICnAca3o5xI8/HUNjqJu3hhG4cCZdXAHMKaOaQTFX0Yl+cpu61Rsx94A==", + "license": "MIT", + "dependencies": { + "@octokit/auth-oauth-app": "^9.0.4", + "@octokit/auth-oauth-user": "^6.0.3", + "@octokit/auth-unauthenticated": "^7.0.4", + "@octokit/core": "^7.0.7", + "@octokit/oauth-authorization-url": "^8.0.0", + "@octokit/oauth-methods": "^6.0.3", + "@types/aws-lambda": "^8.10.83", + "universal-user-agent": "^7.0.0" + }, + "engines": { + "node": ">= 20" + } + }, + "node_modules/@octokit/oauth-authorization-url": { + "version": "8.0.0", + "resolved": "https://registry.npmjs.org/@octokit/oauth-authorization-url/-/oauth-authorization-url-8.0.0.tgz", + "integrity": "sha512-7QoLPRh/ssEA/HuHBHdVdSgF8xNLz/Bc5m9fZkArJE5bb6NmVkDm3anKxXPmN1zh6b5WKZPRr3697xKT/yM3qQ==", + "license": "MIT", + "engines": { + "node": ">= 20" + } + }, + "node_modules/@octokit/oauth-methods": { + "version": "6.0.4", + "resolved": "https://registry.npmjs.org/@octokit/oauth-methods/-/oauth-methods-6.0.4.tgz", + "integrity": "sha512-96RsnxS7Hk/BQhUA1Qo2pmcYP6LWQRWMdo7bVbNE7ZCkFLhSUYBXVUj9tVnvhl4jzbwHYt/dcIG+ioY427b2sg==", + "license": "MIT", + "dependencies": { + "@octokit/oauth-authorization-url": "^8.0.0", + "@octokit/request": "^10.0.13", + "@octokit/request-error": "^7.1.1", + "@octokit/types": "^17.0.0" + }, + "engines": { + "node": ">= 20" + } + }, + "node_modules/@octokit/oauth-methods/node_modules/@octokit/openapi-types": { + "version": "28.0.0", + "resolved": "https://registry.npmjs.org/@octokit/openapi-types/-/openapi-types-28.0.0.tgz", + "integrity": "sha512-0rFyLuyHvIj6uuZWuDslxkowFYdPXoNIkeAv4b27dzm2Tf4vGWXnPsMcxs7d65kLdMERgP3wc1AEPlqMz8e1cQ==", + "license": "MIT" + }, + "node_modules/@octokit/oauth-methods/node_modules/@octokit/types": { + "version": "17.0.0", + "resolved": "https://registry.npmjs.org/@octokit/types/-/types-17.0.0.tgz", + "integrity": "sha512-ByP1v7YL5SMveFPP7+sj0/ZuWCOOg/Chs4NafOMpq6WNIM/hdGY0S7C0TCGDBWu1aGmOxmUIhMx3cO+IdwYZ1Q==", + "license": "MIT", + "dependencies": { + "@octokit/openapi-types": "^28.0.0" + } + }, "node_modules/@octokit/openapi-types": { "version": "27.0.0", "resolved": "https://registry.npmjs.org/@octokit/openapi-types/-/openapi-types-27.0.0.tgz", "integrity": "sha512-whrdktVs1h6gtR+09+QsNk2+FO+49j6ga1c55YZudfEG+oKJVvJLQi3zkOm5JjiUXAagWK2tI2kTGKJ2Ys7MGA==", - "dev": true, "license": "MIT" }, + "node_modules/@octokit/openapi-webhooks-types": { + "version": "12.1.0", + "resolved": "https://registry.npmjs.org/@octokit/openapi-webhooks-types/-/openapi-webhooks-types-12.1.0.tgz", + "integrity": "sha512-WiuzhOsiOvb7W3Pvmhf8d2C6qaLHXrWiLBP4nJ/4kydu+wpagV5Fkz9RfQwV2afYzv3PB+3xYgp4mAdNGjDprA==", + "license": "MIT" + }, + "node_modules/@octokit/plugin-paginate-graphql": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/@octokit/plugin-paginate-graphql/-/plugin-paginate-graphql-6.0.0.tgz", + "integrity": "sha512-crfpnIoFiBtRkvPqOyLOsw12XsveYuY2ieP6uYDosoUegBJpSVxGwut9sxUgFFcll3VTOTqpUf8yGd8x1OmAkQ==", + "license": "MIT", + "engines": { + "node": ">= 20" + }, + "peerDependencies": { + "@octokit/core": ">=6" + } + }, "node_modules/@octokit/plugin-paginate-rest": { "version": "14.0.0", "resolved": "https://registry.npmjs.org/@octokit/plugin-paginate-rest/-/plugin-paginate-rest-14.0.0.tgz", "integrity": "sha512-fNVRE7ufJiAA3XUrha2omTA39M6IXIc6GIZLvlbsm8QOQCYvpq/LkMNGyFlB1d8hTDzsAXa3OKtybdMAYsV/fw==", - "dev": true, "license": "MIT", "dependencies": { "@octokit/types": "^16.0.0" @@ -385,7 +703,6 @@ "version": "17.0.0", "resolved": "https://registry.npmjs.org/@octokit/plugin-rest-endpoint-methods/-/plugin-rest-endpoint-methods-17.0.0.tgz", "integrity": "sha512-B5yCyIlOJFPqUUeiD0cnBJwWJO8lkJs5d8+ze9QDP6SvfiXSz1BF+91+0MeI1d2yxgOhU/O+CvtiZ9jSkHhFAw==", - "dev": true, "license": "MIT", "dependencies": { "@octokit/types": "^16.0.0" @@ -397,18 +714,80 @@ "@octokit/core": ">=6" } }, + "node_modules/@octokit/plugin-retry": { + "version": "8.1.1", + "resolved": "https://registry.npmjs.org/@octokit/plugin-retry/-/plugin-retry-8.1.1.tgz", + "integrity": "sha512-VCVvZ/R1+u3WuiBWpNavZ0mY4aaJNAsENrpBP9aLSR2QyOpQgd7DhM5j4AW7z4MQpnJYgwBPf0XqPQoNBRdQwg==", + "license": "MIT", + "dependencies": { + "@octokit/request-error": "^7.1.1", + "@octokit/types": "^17.0.0", + "bottleneck": "^2.15.3" + }, + "engines": { + "node": ">= 20" + }, + "peerDependencies": { + "@octokit/core": ">=7" + } + }, + "node_modules/@octokit/plugin-retry/node_modules/@octokit/openapi-types": { + "version": "28.0.0", + "resolved": "https://registry.npmjs.org/@octokit/openapi-types/-/openapi-types-28.0.0.tgz", + "integrity": "sha512-0rFyLuyHvIj6uuZWuDslxkowFYdPXoNIkeAv4b27dzm2Tf4vGWXnPsMcxs7d65kLdMERgP3wc1AEPlqMz8e1cQ==", + "license": "MIT" + }, + "node_modules/@octokit/plugin-retry/node_modules/@octokit/types": { + "version": "17.0.0", + "resolved": "https://registry.npmjs.org/@octokit/types/-/types-17.0.0.tgz", + "integrity": "sha512-ByP1v7YL5SMveFPP7+sj0/ZuWCOOg/Chs4NafOMpq6WNIM/hdGY0S7C0TCGDBWu1aGmOxmUIhMx3cO+IdwYZ1Q==", + "license": "MIT", + "dependencies": { + "@octokit/openapi-types": "^28.0.0" + } + }, + "node_modules/@octokit/plugin-throttling": { + "version": "11.0.5", + "resolved": "https://registry.npmjs.org/@octokit/plugin-throttling/-/plugin-throttling-11.0.5.tgz", + "integrity": "sha512-LIdrkrUv+DWbKeg/49rGuFJ3SU0d3hUS+B4MhNZLepBoNUFXms8Ic9edJjrlx+zycqJHjrMRudVpVb/bAXM2Lw==", + "license": "MIT", + "dependencies": { + "@octokit/types": "^17.0.0", + "bottleneck": "^2.15.3" + }, + "engines": { + "node": ">= 20" + }, + "peerDependencies": { + "@octokit/core": "^7.0.0" + } + }, + "node_modules/@octokit/plugin-throttling/node_modules/@octokit/openapi-types": { + "version": "28.0.0", + "resolved": "https://registry.npmjs.org/@octokit/openapi-types/-/openapi-types-28.0.0.tgz", + "integrity": "sha512-0rFyLuyHvIj6uuZWuDslxkowFYdPXoNIkeAv4b27dzm2Tf4vGWXnPsMcxs7d65kLdMERgP3wc1AEPlqMz8e1cQ==", + "license": "MIT" + }, + "node_modules/@octokit/plugin-throttling/node_modules/@octokit/types": { + "version": "17.0.0", + "resolved": "https://registry.npmjs.org/@octokit/types/-/types-17.0.0.tgz", + "integrity": "sha512-ByP1v7YL5SMveFPP7+sj0/ZuWCOOg/Chs4NafOMpq6WNIM/hdGY0S7C0TCGDBWu1aGmOxmUIhMx3cO+IdwYZ1Q==", + "license": "MIT", + "dependencies": { + "@octokit/openapi-types": "^28.0.0" + } + }, "node_modules/@octokit/request": { - "version": "10.0.10", - "resolved": "https://registry.npmjs.org/@octokit/request/-/request-10.0.10.tgz", - "integrity": "sha512-KxNC2pTqqhszMNrf12ZRd4PonRgyJdsM4F/jySiddQK+DsRcfBtUvqn8t7UsyZhnRJHvX46OohDt5N3VqIWC2w==", - "dev": true, + "version": "10.0.15", + "resolved": "https://registry.npmjs.org/@octokit/request/-/request-10.0.15.tgz", + "integrity": "sha512-3CBg9aJ0hO9Pjyij8LbK/xYtEaPws9SW7xKz67daPNxQB1q5Y9OMA7DDOG0A6Hwf9ygGu3tvzusg0LXQ8/wAjA==", "license": "MIT", "dependencies": { "@octokit/endpoint": "^11.0.3", - "@octokit/request-error": "^7.0.2", - "@octokit/types": "^16.0.0", - "content-type": "^2.0.0", - "json-with-bigint": "^3.5.3", + "@octokit/request-error": "^7.1.1", + "@octokit/types": "^17.0.0", + "content-type": "^3.0.0", + "json-with-bigint": "^3.5.12", "universal-user-agent": "^7.0.2" }, "engines": { @@ -416,18 +795,47 @@ } }, "node_modules/@octokit/request-error": { - "version": "7.1.0", - "resolved": "https://registry.npmjs.org/@octokit/request-error/-/request-error-7.1.0.tgz", - "integrity": "sha512-KMQIfq5sOPpkQYajXHwnhjCC0slzCNScLHs9JafXc4RAJI+9f+jNDlBNaIMTvazOPLgb4BnlhGJOTbnN0wIjPw==", - "dev": true, + "version": "7.1.1", + "resolved": "https://registry.npmjs.org/@octokit/request-error/-/request-error-7.1.1.tgz", + "integrity": "sha512-+eaY7G2VVpSf2pc5Gn1+mph837V/d/TYTJAgWL9Tb0ogGYcpN3IlAVFgjL+Vv93F/sevrxkvsYCedtpLdcFLzA==", "license": "MIT", "dependencies": { - "@octokit/types": "^16.0.0" + "@octokit/types": "^17.0.0" }, "engines": { "node": ">= 20" } }, + "node_modules/@octokit/request-error/node_modules/@octokit/openapi-types": { + "version": "28.0.0", + "resolved": "https://registry.npmjs.org/@octokit/openapi-types/-/openapi-types-28.0.0.tgz", + "integrity": "sha512-0rFyLuyHvIj6uuZWuDslxkowFYdPXoNIkeAv4b27dzm2Tf4vGWXnPsMcxs7d65kLdMERgP3wc1AEPlqMz8e1cQ==", + "license": "MIT" + }, + "node_modules/@octokit/request-error/node_modules/@octokit/types": { + "version": "17.0.0", + "resolved": "https://registry.npmjs.org/@octokit/types/-/types-17.0.0.tgz", + "integrity": "sha512-ByP1v7YL5SMveFPP7+sj0/ZuWCOOg/Chs4NafOMpq6WNIM/hdGY0S7C0TCGDBWu1aGmOxmUIhMx3cO+IdwYZ1Q==", + "license": "MIT", + "dependencies": { + "@octokit/openapi-types": "^28.0.0" + } + }, + "node_modules/@octokit/request/node_modules/@octokit/openapi-types": { + "version": "28.0.0", + "resolved": "https://registry.npmjs.org/@octokit/openapi-types/-/openapi-types-28.0.0.tgz", + "integrity": "sha512-0rFyLuyHvIj6uuZWuDslxkowFYdPXoNIkeAv4b27dzm2Tf4vGWXnPsMcxs7d65kLdMERgP3wc1AEPlqMz8e1cQ==", + "license": "MIT" + }, + "node_modules/@octokit/request/node_modules/@octokit/types": { + "version": "17.0.0", + "resolved": "https://registry.npmjs.org/@octokit/types/-/types-17.0.0.tgz", + "integrity": "sha512-ByP1v7YL5SMveFPP7+sj0/ZuWCOOg/Chs4NafOMpq6WNIM/hdGY0S7C0TCGDBWu1aGmOxmUIhMx3cO+IdwYZ1Q==", + "license": "MIT", + "dependencies": { + "@octokit/openapi-types": "^28.0.0" + } + }, "node_modules/@octokit/rest": { "version": "22.0.1", "resolved": "https://registry.npmjs.org/@octokit/rest/-/rest-22.0.1.tgz", @@ -448,12 +856,40 @@ "version": "16.0.0", "resolved": "https://registry.npmjs.org/@octokit/types/-/types-16.0.0.tgz", "integrity": "sha512-sKq+9r1Mm4efXW1FCk7hFSeJo4QKreL/tTbR0rz/qx/r1Oa2VV83LTA/H/MuCOX7uCIJmQVRKBcbmWoySjAnSg==", - "dev": true, "license": "MIT", "dependencies": { "@octokit/openapi-types": "^27.0.0" } }, + "node_modules/@octokit/webhooks": { + "version": "14.2.0", + "resolved": "https://registry.npmjs.org/@octokit/webhooks/-/webhooks-14.2.0.tgz", + "integrity": "sha512-da6KbdNCV5sr1/txD896V+6W0iamFWrvVl8cHkBSPT+YlvmT3DwXa4jxZnQc+gnuTEqSWbBeoSZYTayXH9wXcw==", + "license": "MIT", + "dependencies": { + "@octokit/openapi-webhooks-types": "12.1.0", + "@octokit/request-error": "^7.0.0", + "@octokit/webhooks-methods": "^6.0.0" + }, + "engines": { + "node": ">= 20" + } + }, + "node_modules/@octokit/webhooks-methods": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/@octokit/webhooks-methods/-/webhooks-methods-6.0.0.tgz", + "integrity": "sha512-MFlzzoDJVw/GcbfzVC1RLR36QqkTLUf79vLVO3D+xn7r0QgxnFoLZgtrzxiQErAjFUOdH6fas2KeQJ1yr/qaXQ==", + "license": "MIT", + "engines": { + "node": ">= 20" + } + }, + "node_modules/@types/aws-lambda": { + "version": "8.10.162", + "resolved": "https://registry.npmjs.org/@types/aws-lambda/-/aws-lambda-8.10.162.tgz", + "integrity": "sha512-Fn658grtLOci1oxi1391vvDWJRKNGWRSqfxRkmN/Iy3c0tQH1USMKEXcPYHLvope+ZgTFocx9FRQJx1muBL6qw==", + "license": "MIT" + }, "node_modules/@types/esrecurse": { "version": "4.3.1", "resolved": "https://registry.npmjs.org/@types/esrecurse/-/esrecurse-4.3.1.tgz", @@ -1068,9 +1504,14 @@ "version": "4.0.0", "resolved": "https://registry.npmjs.org/before-after-hook/-/before-after-hook-4.0.0.tgz", "integrity": "sha512-q6tR3RPqIB1pMiTRMFcZwuG5T8vwp+vUvEG0vuI6B+Rikh5BfPp2fQ82c925FOs+b0lcFQ8CFrL+KbilfZFhOQ==", - "dev": true, "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": "5.0.6", "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-5.0.6.tgz", @@ -1278,13 +1719,12 @@ "license": "MIT" }, "node_modules/content-type": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/content-type/-/content-type-2.0.0.tgz", - "integrity": "sha512-j/O/d7GcZCyNl7/hwZAb606rzqkyvaDctLmckbxLzHvFBzTJHuGEdodATcP3yIRoDrLHkIATJuvzbFlp/ki2cQ==", - "dev": true, + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/content-type/-/content-type-3.0.0.tgz", + "integrity": "sha512-AIi5H6p0xk5uknXcN3/rmhP8jgp69OfSe/JuKiQAFprJ7UGw7mwj7m4XcmDzlrnJDG+cGpphAINGdU3g3g7kDw==", "license": "MIT", "engines": { - "node": ">=18" + "node": ">=22" }, "funding": { "type": "opencollective", @@ -2183,10 +2623,9 @@ "license": "MIT" }, "node_modules/json-with-bigint": { - "version": "3.5.8", - "resolved": "https://registry.npmjs.org/json-with-bigint/-/json-with-bigint-3.5.8.tgz", - "integrity": "sha512-eq/4KP6K34kwa7TcFdtvnftvHCD9KvHOGGICWwMFc4dOOKF5t4iYqnfLK8otCRCRv06FXOzGGyqE8h8ElMvvdw==", - "dev": true, + "version": "3.5.12", + "resolved": "https://registry.npmjs.org/json-with-bigint/-/json-with-bigint-3.5.12.tgz", + "integrity": "sha512-uwbF/wSSuOgC7qqlq27Xp5B6a2MHVug3t0idZdTqu0JnlFvgJuH7ju+KAk/J06C7GfhoYy2gnb9wz2INqcne7w==", "license": "MIT" }, "node_modules/jsonfile": { @@ -2409,6 +2848,28 @@ "node": ">=18" } }, + "node_modules/octokit": { + "version": "5.0.5", + "resolved": "https://registry.npmjs.org/octokit/-/octokit-5.0.5.tgz", + "integrity": "sha512-4+/OFSqOjoyULo7eN7EA97DE0Xydj/PW5aIckxqQIoFjFwqXKuFCvXUJObyJfBF9Khu4RL/jlDRI9FPaMGfPnw==", + "license": "MIT", + "dependencies": { + "@octokit/app": "^16.1.2", + "@octokit/core": "^7.0.6", + "@octokit/oauth-app": "^8.0.3", + "@octokit/plugin-paginate-graphql": "^6.0.0", + "@octokit/plugin-paginate-rest": "^14.0.0", + "@octokit/plugin-rest-endpoint-methods": "^17.0.0", + "@octokit/plugin-retry": "^8.0.3", + "@octokit/plugin-throttling": "^11.0.3", + "@octokit/request-error": "^7.0.2", + "@octokit/types": "^16.0.0", + "@octokit/webhooks": "^14.0.0" + }, + "engines": { + "node": ">= 20" + } + }, "node_modules/optionator": { "version": "0.9.4", "resolved": "https://registry.npmjs.org/optionator/-/optionator-0.9.4.tgz", @@ -3085,6 +3546,15 @@ "node": ">=8.0" } }, + "node_modules/toad-cache": { + "version": "3.7.4", + "resolved": "https://registry.npmjs.org/toad-cache/-/toad-cache-3.7.4.tgz", + "integrity": "sha512-m1TdR/rvT7kgGJZhspNtXdsdYk0fddFpJJFlG5s+UkPFo6lkLoZ3YLOaovPYjq1R75NP5JfeTlSHaOsE09peCg==", + "license": "MIT", + "engines": { + "node": ">=20" + } + }, "node_modules/ts-api-utils": { "version": "2.5.0", "resolved": "https://registry.npmjs.org/ts-api-utils/-/ts-api-utils-2.5.0.tgz", @@ -3159,11 +3629,16 @@ "dev": true, "license": "MIT" }, + "node_modules/universal-github-app-jwt": { + "version": "2.2.2", + "resolved": "https://registry.npmjs.org/universal-github-app-jwt/-/universal-github-app-jwt-2.2.2.tgz", + "integrity": "sha512-dcmbeSrOdTnsjGjUfAlqNDJrhxXizjAz94ija9Qw8YkZ1uu0d+GoZzyH+Jb9tIIqvGsadUfwg+22k5aDqqwzbw==", + "license": "MIT" + }, "node_modules/universal-user-agent": { "version": "7.0.3", "resolved": "https://registry.npmjs.org/universal-user-agent/-/universal-user-agent-7.0.3.tgz", "integrity": "sha512-TmnEAEAsBJVZM/AADELsK76llnwcf9vMKuPz8JflO1frO8Lchitr0fNaN9d+Ap0BjKtqWqd/J17qeDnXh8CL2A==", - "dev": true, "license": "ISC" }, "node_modules/universalify": { diff --git a/package.json b/package.json index d7f98be..5fe98a9 100644 --- a/package.json +++ b/package.json @@ -260,6 +260,7 @@ "ansi-colors": "^4.1.3", "compare-versions": "^6.1.1", "node-fetch": "^3.3.2", + "octokit": "^5.0.5", "tmp": "^0.2.5" }, "devDependencies": { diff --git a/src/components/rpgUnit/github.ts b/src/components/rpgUnit/github.ts new file mode 100644 index 0000000..a654f2a --- /dev/null +++ b/src/components/rpgUnit/github.ts @@ -0,0 +1,64 @@ +import { Octokit } from "octokit"; +import * as fs from 'fs'; +import { components } from "@octokit/openapi-types"; +import fetch from "node-fetch"; +import * as path from "path"; +import { OWNER, REPO } from "./version"; + +export interface Response { + data: T, + error?: string +} +export type Release = components["schemas"]["release"]; +export type ReleaseAsset = components["schemas"]["release-asset"]; + +export namespace GitHub { + export async function getReleases(): Promise> { + const releases: Response = { + data: [] + }; + + try { + const octokit = new Octokit(); + const response = await octokit.rest.repos.listReleases({ + owner: OWNER, + repo: REPO + }); + + if (response.status === 200) { + releases.data = response.data; + } else { + releases.error = response.status; + } + } catch (error: any) { + releases.error = error.message ? error.message : error; + } + + return releases; + } + + export async function downloadReleaseAsset(asset: ReleaseAsset, downloadDirectory: string): Promise> { + const isDownloaded: Response = { + data: false + }; + + try { + // Fetch asset + const response = await fetch(asset.browser_download_url); + const buffer = await response.arrayBuffer(); + + // Download asset to specified path + if (response.status === 200) { + const filePath = path.join(downloadDirectory, asset.name); + await fs.promises.writeFile(filePath, Buffer.from(buffer)); + isDownloaded.data = true; + } else { + isDownloaded.error = response.statusText; + } + } catch (error: any) { + isDownloaded.error = error.message ? error.message : error; + } + + return isDownloaded; + } +} \ No newline at end of file diff --git a/src/components/rpgUnit/rpgUnit.ts b/src/components/rpgUnit/rpgUnit.ts index 94e50a0..2a3aa46 100644 --- a/src/components/rpgUnit/rpgUnit.ts +++ b/src/components/rpgUnit/rpgUnit.ts @@ -3,13 +3,16 @@ import { Tools } from "@halcyontech/vscode-ibmi-types/api/Tools"; import IBMi from "@halcyontech/vscode-ibmi-types/api/IBMi"; import { Configuration, Section } from "../../configuration"; import { compareVersions } from 'compare-versions'; -import { commands, env, ExtensionContext, LogLevel, ProgressLocation, Uri, window } from "vscode"; +import { commands, env, ExtensionContext, LogLevel, ProgressLocation, QuickPickItem, QuickPickItemKind, Uri, window } from "vscode"; import { existsSync } from "fs"; +import * as fs from "fs"; import * as path from "path"; +import * as os from "os"; import { getInstance } from "../../extensions/ibmi"; import { testOutputLogger } from "../../extension"; import { LOCAL_SAVE_FILE, OWNER, REPO, GITHUB_SAVE_FILE, SERVER_VERSION_TAG, VERSION } from "./version"; import { GlobalState } from "../../globalState"; +import { GitHub, Release } from "./github"; export class RPGUnit implements IBMiComponent { static readonly ID: string = "RPGUnit"; @@ -106,66 +109,172 @@ export class RPGUnit implements IBMiComponent { } async update(connection: IBMi, installDirectory: string): Promise { + const errorButtons = [ + { + label: 'Try Again', + func: async () => { + const componentManager = connection.getComponentManager(); + await componentManager.installComponent(RPGUnit.ID); + } + } + ]; + // Get current component state const state = await this.getRemoteState(connection, installDirectory); - // Prompt user to confirm installation process + // Create quick pick item for bundled save file + const bundledQuickPickItem: QuickPickItem = { + label: `$(package) v${VERSION}`, + description: `Recommended`, + detail: `Install the minimum compatible version using a bundled save file` + }; + + // Fetch GitHub releases and filter for compatible versions + const releasesResponse = await GitHub.getReleases(); + const supportedReleases: Release[] = []; + if (releasesResponse.error) { + await testOutputLogger.log(LogLevel.Error, `Failed to retrieve GitHub releases: ${releasesResponse.error}`); + } else { + for await (const release of releasesResponse.data) { + let version = release.name || release.tag_name; + version = version.startsWith('v') ? version.substring(1) : version; + const isValid = (release.draft === false) && + (release.assets.some(asset => asset.name === GITHUB_SAVE_FILE)) && + (await this.compareVersions(version, VERSION)) >= 0; + if (isValid) { + supportedReleases.push(release); + } + } + } + + // Create quick pick items for compatible GitHub releases + const githubReleaseItems: (QuickPickItem & { release: Release })[] = supportedReleases.map(release => { + const version = release?.name || release?.tag_name; + const publishedAt = release.published_at ? new Date(release.published_at).toLocaleString() : undefined; + const preRelease = release.prerelease ? ` (Pre-release)` : ``; + const description = publishedAt + ? (preRelease ? `${publishedAt}${preRelease}` : publishedAt) + : (preRelease || ``); + return { + label: `$(github) ${version}`, + description, + release + }; + }); + + // Prompt user for installation source + const selectedSource = await window.showQuickPick( + [ + { label: `Bundled Save File`, kind: QuickPickItemKind.Separator }, + bundledQuickPickItem, + { label: `GitHub Releases`, kind: QuickPickItemKind.Separator }, + ...githubReleaseItems + ], + { + placeHolder: `Installation Source`, + prompt: `Select where you would like the RPGUnit save file to be retrieved from.`, + ignoreFocusOut: true + } + ); + if (!selectedSource) { + await testOutputLogger.appendWithNotification(LogLevel.Error, `Installation aborted as installation source was not selected`, undefined, errorButtons); + return state; + } + const content = connection.getContent(); const config = connection.getConfig(); const tempDir = connection.getTempDirectory(); - const remotePath = path.posix.join(tempDir, LOCAL_SAVE_FILE); const productLibrary = connection.upperCaseName(Configuration.getOrFallback(Section.productLibrary)); const tempLibrary = connection.upperCaseName(config.tempLibrary); const extensionVersion = RPGUnit.context.extension.packageJSON.version; - const proceed = await window.showInformationMessage( - `RPGUnit Installation`, - { - modal: true, - detail: [ - `IBM i Testing v${extensionVersion} is compatible with RPGUnit v${VERSION}. The following steps will be performed to install this version of RPGUnit:`, - ``, - ` 1. Locate the bundled save file (${LOCAL_SAVE_FILE}) shipped with the extension.`, - ` 2. Delete the existing product library ${productLibrary}.LIB (if present).`, - ` 3. Upload the bundled save file (${LOCAL_SAVE_FILE}) to a temporary IFS directory (${tempDir}).`, - ` 4. Create a save file object (RPGUNIT.FILE) in the temporary library (${tempLibrary}.LIB).`, - ` 5. Copy the uploaded save file into the save file object.`, - ` 6. Restore the save file contents into ${productLibrary}.LIB.`, - ` 7. Delete all temporary files and objects.` - ].join(`\n`) - }, - `Proceed` - ); - if (proceed !== `Proceed`) { - return state; - } - const errorButtons = [ - { - label: 'Try Again', - func: async () => { - const componentManager = connection.getComponentManager(); - await componentManager.installComponent(RPGUnit.ID); - } + let localAssetPath: string; + let saveFileName: string; + + const isGitHubRelease = selectedSource !== bundledQuickPickItem; + if (!isGitHubRelease) { + // Prompt user to confirm bundled installation process + const proceed = await window.showInformationMessage( + `RPGUnit Installation via Bundled Save File`, + { + modal: true, + detail: [ + `IBM i Testing v${extensionVersion} is compatible with RPGUnit v${VERSION}. The following steps will be performed to install this version of RPGUnit:`, + ``, + ` 1. Locate the bundled save file (${LOCAL_SAVE_FILE}) shipped with the extension.`, + ` 2. Delete the existing product library ${productLibrary}.LIB (if present).`, + ` 3. Upload the bundled save file (${LOCAL_SAVE_FILE}) to a temporary IFS directory (${tempDir}).`, + ` 4. Create a save file object (RPGUNIT.FILE) in the temporary library (${tempLibrary}.LIB).`, + ` 5. Copy the uploaded save file into the save file object.`, + ` 6. Restore the save file contents into ${productLibrary}.LIB.`, + ` 7. Delete all temporary files and objects.` + ].join(`\n`) + }, + `Proceed` + ); + if (proceed !== `Proceed`) { + await testOutputLogger.appendWithNotification(LogLevel.Error, `Installation aborted as permission was not granted to proceed with RPGUnit installation via bundled save file`, undefined, errorButtons); + return state; } - ]; - testOutputLogger.show(); + testOutputLogger.show(); - // Check if bundled save file exists - await testOutputLogger.log(LogLevel.Info, `Locating bundled ${LOCAL_SAVE_FILE}: ${this.localAssetPath}`); - if (!existsSync(this.localAssetPath)) { - await testOutputLogger.appendWithNotification(LogLevel.Error, `Bundled save file not found at ${this.localAssetPath}. Navigate to the GitHub releases page and install it manually.`, undefined, [ - ...errorButtons, + // Check if bundled save file exists + await testOutputLogger.log(LogLevel.Info, `Locating bundled ${LOCAL_SAVE_FILE}: ${this.localAssetPath}`); + if (!existsSync(this.localAssetPath)) { + await testOutputLogger.appendWithNotification(LogLevel.Error, `Bundled save file not found at ${this.localAssetPath}`, undefined, [ + ...errorButtons + ]); + return state; + } + + localAssetPath = this.localAssetPath; + saveFileName = LOCAL_SAVE_FILE; + } else { + // Prompt user to confirm GitHub installation process + const selectedRelease = (selectedSource as QuickPickItem & { release: Release }).release; + const releaseVersion = selectedRelease?.name || selectedRelease?.tag_name; + const releaseAsset = selectedRelease?.assets.find(asset => asset.name === GITHUB_SAVE_FILE)!; + const proceed = await window.showInformationMessage( + `RPGUnit Installation via GitHub Release`, { - label: 'View GitHub Release', - func: async () => { - await env.openExternal(Uri.parse(`https://github.com/${OWNER}/${REPO}/releases/tag/${SERVER_VERSION_TAG}`)); - } - } - ]); - return state; + modal: true, + detail: [ + `The following steps will be performed to install RPGUnit ${releaseVersion} from GitHub:`, + ``, + ` 1. Download the save file (${releaseAsset.name}) from the GitHub release.`, + ` 2. Delete the existing product library ${productLibrary}.LIB (if present).`, + ` 3. Upload the downloaded save file (${releaseAsset.name}) to a temporary IFS directory (${tempDir}).`, + ` 4. Create a save file object (RPGUNIT.FILE) in the temporary library (${tempLibrary}.LIB).`, + ` 5. Copy the uploaded save file into the save file object.`, + ` 6. Restore the save file contents into ${productLibrary}.LIB.`, + ` 7. Delete all temporary files and objects.` + ].join(`\n`) + }, + `Proceed` + ); + if (proceed !== `Proceed`) { + await testOutputLogger.appendWithNotification(LogLevel.Error, `Installation aborted as permission was not granted to proceed with RPGUnit installation via GitHub release`, undefined, errorButtons); + return state; + } + + testOutputLogger.show(); + + // Download the release asset to a local temp directory + const downloadDir = os.tmpdir(); + await testOutputLogger.log(LogLevel.Info, `Downloading ${releaseAsset.name} from GitHub release ${releaseVersion} to ${downloadDir}`); + const downloadResult = await GitHub.downloadReleaseAsset(releaseAsset, downloadDir); + if (!downloadResult.data) { + await testOutputLogger.appendWithNotification(LogLevel.Error, `Failed to download ${releaseAsset.name} from GitHub`, downloadResult.error, errorButtons); + return state; + } + + localAssetPath = path.join(downloadDir, releaseAsset.name); + saveFileName = releaseAsset.name; } + const remotePath = path.posix.join(tempDir, saveFileName); + // Check if product library exists const productLibraryExists = await content.checkObject({ library: 'QSYS', name: productLibrary, type: '*LIB' }); if (productLibraryExists) { @@ -201,10 +310,10 @@ export class RPGUnit implements IBMiComponent { // Uploading save file to IFS try { - await testOutputLogger.log(LogLevel.Info, `Uploading ${LOCAL_SAVE_FILE} to ${remotePath}`); - await content.uploadFiles([{ local: this.localAssetPath, remote: remotePath }]); + await testOutputLogger.log(LogLevel.Info, `Uploading ${saveFileName} to ${remotePath}`); + await content.uploadFiles([{ local: localAssetPath, remote: remotePath }]); } catch (error: any) { - await testOutputLogger.appendWithNotification(LogLevel.Error, `Failed to upload ${LOCAL_SAVE_FILE}`, error, errorButtons); + await testOutputLogger.appendWithNotification(LogLevel.Error, `Failed to upload ${saveFileName}`, error, errorButtons); return state; } @@ -226,10 +335,10 @@ export class RPGUnit implements IBMiComponent { 'STMFCCSID': 37, 'MBROPT': `*REPLACE` }); - await testOutputLogger.log(LogLevel.Info, `Transferring ${LOCAL_SAVE_FILE} to ${tempLibrary}.LIB: ${transferCommand}`); + await testOutputLogger.log(LogLevel.Info, `Transferring ${saveFileName} to ${tempLibrary}.LIB: ${transferCommand}`); const transferResult = await connection.runCommand({ command: transferCommand, environment: `ile`, noLibList: true }); if (transferResult.code !== 0) { - await testOutputLogger.appendWithNotification(LogLevel.Error, `Failed to transfer ${LOCAL_SAVE_FILE}`, transferResult.stderr, errorButtons); + await testOutputLogger.appendWithNotification(LogLevel.Error, `Failed to transfer ${saveFileName}`, transferResult.stderr, errorButtons); return state; } @@ -247,9 +356,19 @@ export class RPGUnit implements IBMiComponent { return state; } - // Deleting temporary file + // Deleting locally downloaded save file (GitHub release installation only) + if (isGitHubRelease) { + try { + await testOutputLogger.log(LogLevel.Info, `Deleting locally downloaded save file: ${localAssetPath}`); + await fs.promises.unlink(localAssetPath); + } catch (error: any) { + await testOutputLogger.appendWithNotification(LogLevel.Error, `Failed to delete locally downloaded save file: ${localAssetPath}`, error); + } + } + + // Deleting temporary file in the IFS try { - await testOutputLogger.log(LogLevel.Info, `Deleting ${remotePath}`); + await testOutputLogger.log(LogLevel.Info, `Deleting temporary file in the IFS: ${remotePath}`); await connection.runCommand({ command: `rm -rf ${remotePath}` }); } catch (error: any) { await testOutputLogger.appendWithNotification(LogLevel.Error, `Failed to delete ${remotePath}`, error); From 1be0640ea9d9b65ea5ca1ff6bb27a1fb262fc739 Mon Sep 17 00:00:00 2001 From: Sanjula Ganepola Date: Thu, 27 Aug 2026 15:50:54 -0400 Subject: [PATCH 4/4] Add draft and latest tags Signed-off-by: Sanjula Ganepola --- src/components/rpgUnit/github.ts | 24 ++++++++++++++++ src/components/rpgUnit/rpgUnit.ts | 46 +++++++++++++++---------------- 2 files changed, 47 insertions(+), 23 deletions(-) diff --git a/src/components/rpgUnit/github.ts b/src/components/rpgUnit/github.ts index a654f2a..bd7d003 100644 --- a/src/components/rpgUnit/github.ts +++ b/src/components/rpgUnit/github.ts @@ -37,6 +37,30 @@ export namespace GitHub { return releases; } + export async function getLatestRelease(): Promise> { + const result: Response = { + data: undefined! + }; + + try { + const octokit = new Octokit(); + const response = await octokit.rest.repos.getLatestRelease({ + owner: OWNER, + repo: REPO + }); + + if (response.status === 200) { + result.data = response.data; + } else { + result.error = response.status; + } + } catch (error: any) { + result.error = error.message ? error.message : error; + } + + return result; + } + export async function downloadReleaseAsset(asset: ReleaseAsset, downloadDirectory: string): Promise> { const isDownloaded: Response = { data: false diff --git a/src/components/rpgUnit/rpgUnit.ts b/src/components/rpgUnit/rpgUnit.ts index 2a3aa46..b7ff43e 100644 --- a/src/components/rpgUnit/rpgUnit.ts +++ b/src/components/rpgUnit/rpgUnit.ts @@ -10,7 +10,7 @@ import * as path from "path"; import * as os from "os"; import { getInstance } from "../../extensions/ibmi"; import { testOutputLogger } from "../../extension"; -import { LOCAL_SAVE_FILE, OWNER, REPO, GITHUB_SAVE_FILE, SERVER_VERSION_TAG, VERSION } from "./version"; +import { LOCAL_SAVE_FILE, GITHUB_SAVE_FILE, VERSION } from "./version"; import { GlobalState } from "../../globalState"; import { GitHub, Release } from "./github"; @@ -126,42 +126,42 @@ export class RPGUnit implements IBMiComponent { const bundledQuickPickItem: QuickPickItem = { label: `$(package) v${VERSION}`, description: `Recommended`, - detail: `Install the minimum compatible version using a bundled save file` + detail: `Install the minimum compatible version using a save file bundled in the extension` }; - // Fetch GitHub releases and filter for compatible versions - const releasesResponse = await GitHub.getReleases(); - const supportedReleases: Release[] = []; + // Fetch GitHub releases + const [releasesResponse, latestReleaseResponse] = await Promise.all([ + GitHub.getReleases(), + GitHub.getLatestRelease() + ]); + + // Create quick pick items for compatible GitHub releases + const githubReleaseItems: (QuickPickItem & { release: Release })[] = []; if (releasesResponse.error) { await testOutputLogger.log(LogLevel.Error, `Failed to retrieve GitHub releases: ${releasesResponse.error}`); } else { for await (const release of releasesResponse.data) { let version = release.name || release.tag_name; version = version.startsWith('v') ? version.substring(1) : version; - const isValid = (release.draft === false) && - (release.assets.some(asset => asset.name === GITHUB_SAVE_FILE)) && + const isValid = (release.assets.some(asset => asset.name === GITHUB_SAVE_FILE)) && (await this.compareVersions(version, VERSION)) >= 0; if (isValid) { - supportedReleases.push(release); + const publishedAt = release.published_at ? new Date(release.published_at).toLocaleString() : ``; + const description = [ + publishedAt, + (release.prerelease ? `(Pre-release)` : ``), + (latestReleaseResponse.data?.id && release.id === latestReleaseResponse.data?.id ? `(Latest)` : ``), + (release.draft ? `- Draft` : ``), + ].filter(Boolean).join(` `); + githubReleaseItems.push({ + label: `$(github) ${version}`, + description, + release + }); } } } - // Create quick pick items for compatible GitHub releases - const githubReleaseItems: (QuickPickItem & { release: Release })[] = supportedReleases.map(release => { - const version = release?.name || release?.tag_name; - const publishedAt = release.published_at ? new Date(release.published_at).toLocaleString() : undefined; - const preRelease = release.prerelease ? ` (Pre-release)` : ``; - const description = publishedAt - ? (preRelease ? `${publishedAt}${preRelease}` : publishedAt) - : (preRelease || ``); - return { - label: `$(github) ${version}`, - description, - release - }; - }); - // Prompt user for installation source const selectedSource = await window.showQuickPick( [