diff --git a/.config/dotnet-tools.json b/.config/dotnet-tools.json index 40d4373b..305bdb11 100644 --- a/.config/dotnet-tools.json +++ b/.config/dotnet-tools.json @@ -3,7 +3,7 @@ "isRoot": true, "tools": { "dotnet-ef": { - "version": "8.0.10", + "version": "9.0.0", "commands": [ "dotnet-ef" ] diff --git a/.github/dependabot.yml b/.github/dependabot.yml index 39a7e0c8..04a4cb1d 100644 --- a/.github/dependabot.yml +++ b/.github/dependabot.yml @@ -36,6 +36,9 @@ updates: schedule: interval: 'weekly' time: '02:00' + groups: + dotnet-docker: + patterns: ['dotnet/*'] - package-ecosystem: 'nuget' directory: '/' diff --git a/.github/workflows/cleanup.yml b/.github/workflows/cleanup.yml index 8a8864f9..adf2e097 100644 --- a/.github/workflows/cleanup.yml +++ b/.github/workflows/cleanup.yml @@ -16,6 +16,7 @@ jobs: - { ecosystem: cargo } - { ecosystem: composer } - { ecosystem: docker } + - { ecosystem: dotnet-sdk } - { ecosystem: elm } - { ecosystem: gitsubmodule } - { ecosystem: github-actions } diff --git a/.github/workflows/extension.yml b/.github/workflows/extension.yml index 2fa3e844..a9c74db8 100644 --- a/.github/workflows/extension.yml +++ b/.github/workflows/extension.yml @@ -36,13 +36,13 @@ jobs: fetch-depth: 0 # Required for GitVersion - name: Install GitVersion - uses: gittools/actions/gitversion/setup@v3.0.0 + uses: gittools/actions/gitversion/setup@v3.0.1 with: versionSpec: '6.x' - name: Determine Version id: gitversion - uses: gittools/actions/gitversion/execute@v3.0.0 + uses: gittools/actions/gitversion/execute@v3.0.1 with: useConfigFile: true diff --git a/.github/workflows/server.yml b/.github/workflows/server.yml index d5539ecd..6f0be3e7 100644 --- a/.github/workflows/server.yml +++ b/.github/workflows/server.yml @@ -40,12 +40,12 @@ jobs: fetch-depth: 0 # Required for GitVersion - name: Install GitVersion - uses: gittools/actions/gitversion/setup@v3.0.0 + uses: gittools/actions/gitversion/setup@v3.0.1 with: versionSpec: '6.x' - name: Determine Version - uses: gittools/actions/gitversion/execute@v3.0.0 + uses: gittools/actions/gitversion/execute@v3.0.1 id: gitversion with: useConfigFile: true @@ -53,7 +53,7 @@ jobs: - name: Setup .NET SDK uses: actions/setup-dotnet@v4 with: - dotnet-version: '8.x' + dotnet-version: '9.x' - name: Test run: dotnet test -c Release --collect "Code coverage" diff --git a/.github/workflows/updater.yml b/.github/workflows/updater.yml index 8544175d..24a376cf 100644 --- a/.github/workflows/updater.yml +++ b/.github/workflows/updater.yml @@ -35,6 +35,7 @@ jobs: - { ecosystem: cargo } - { ecosystem: composer } - { ecosystem: docker } + - { ecosystem: dotnet-sdk } - { ecosystem: elm } - { ecosystem: gitsubmodule } - { ecosystem: github-actions } @@ -63,12 +64,12 @@ jobs: fetch-depth: 0 # Required for GitVersion - name: Install GitVersion - uses: gittools/actions/gitversion/setup@v3.0.0 + uses: gittools/actions/gitversion/setup@v3.0.1 with: versionSpec: '6.x' - name: Determine Version - uses: gittools/actions/gitversion/execute@v3.0.0 + uses: gittools/actions/gitversion/execute@v3.0.1 id: gitversion with: useConfigFile: true diff --git a/.husky/pre-commit b/.husky/pre-commit new file mode 100644 index 00000000..f6c345f5 --- /dev/null +++ b/.husky/pre-commit @@ -0,0 +1,2 @@ +pretty-quick --staged +codespell \ No newline at end of file diff --git a/Rakefile b/Rakefile index 7bd302a5..1c5491de 100644 --- a/Rakefile +++ b/Rakefile @@ -34,6 +34,7 @@ GEMSPECS = %w( silent/dependabot-silent.gemspec swift/dependabot-swift.gemspec devcontainers/dependabot-devcontainers.gemspec + dotnet_sdk/dependabot-dotnet_sdk.gemspec ).freeze def run_command(command) diff --git a/extension/package-lock.json b/extension/package-lock.json index 329acf54..5531768f 100644 --- a/extension/package-lock.json +++ b/extension/package-lock.json @@ -17,7 +17,7 @@ "devDependencies": { "@types/jest": "29.5.14", "@types/js-yaml": "4.0.9", - "@types/node": "22.8.1", + "@types/node": "22.9.0", "@types/q": "1.5.8", "jest": "29.7.0", "ts-jest": "29.2.5", @@ -1148,9 +1148,9 @@ "dev": true }, "node_modules/@types/node": { - "version": "22.8.1", - "resolved": "https://registry.npmjs.org/@types/node/-/node-22.8.1.tgz", - "integrity": "sha512-k6Gi8Yyo8EtrNtkHXutUu2corfDf9su95VYVP10aGYMMROM6SAItZi0w1XszA6RtWTHSVp5OeFof37w0IEqCQg==", + "version": "22.9.0", + "resolved": "https://registry.npmjs.org/@types/node/-/node-22.9.0.tgz", + "integrity": "sha512-vuyHg81vvWA1Z1ELfvLko2c8f34gyA0zaic0+Rllc5lbCnbSyuvb2Oxpm6TAUAC/2xZN3QGqxBNggD1nNR2AfQ==", "dev": true, "license": "MIT", "dependencies": { diff --git a/extension/package.json b/extension/package.json index b4098066..a5295c0b 100644 --- a/extension/package.json +++ b/extension/package.json @@ -36,7 +36,7 @@ "devDependencies": { "@types/jest": "29.5.14", "@types/js-yaml": "4.0.9", - "@types/node": "22.8.1", + "@types/node": "22.9.0", "@types/q": "1.5.8", "jest": "29.7.0", "ts-jest": "29.2.5", diff --git a/extension/tasks/dependabotV1/task.json b/extension/tasks/dependabotV1/task.json index 3d193856..802a585f 100644 --- a/extension/tasks/dependabotV1/task.json +++ b/extension/tasks/dependabotV1/task.json @@ -7,10 +7,11 @@ "helpMarkDown": "For help please visit https://github.com/tinglesoftware/dependabot-azure-devops/issues", "helpUrl": "https://github.com/tinglesoftware/dependabot-azure-devops/issues", "releaseNotes": "https://github.com/tinglesoftware/dependabot-azure-devops/releases", - "category": "Utility", + "author": "Tingle Software", + "category": "Azure Pipelines", "visibility": ["Build", "Release"], "runsOn": ["Agent", "DeploymentGroup"], - "author": "Tingle Software", + "minimumAgentVersion": "3.232.1", "demands": [], "version": { "Major": 1, @@ -19,8 +20,7 @@ }, "deprecated": true, "deprecationMessage": "This task version is deprecated and is no longer maintained. Please upgrade to the latest version to continue receiving fixes and features. More details: https://github.com/tinglesoftware/dependabot-azure-devops/discussions/1317.", - "instanceNameFormat": "Dependabot", - "minimumAgentVersion": "3.232.1", + "instanceNameFormat": "Dependabot update", "groups": [ { "name": "security_updates", @@ -255,11 +255,9 @@ "helpMarkDown": "Ensure that the host ssh socket is forwarded to the container to authenticate with ssh" } ], - "dataSourceBindings": [], "execution": { "Node20_1": { - "target": "index.js", - "argumentFormat": "" + "target": "index.js" } } } diff --git a/extension/tasks/dependabotV2/index.ts b/extension/tasks/dependabotV2/index.ts index 06a36f25..faaa8e84 100644 --- a/extension/tasks/dependabotV2/index.ts +++ b/extension/tasks/dependabotV2/index.ts @@ -64,11 +64,13 @@ async function run() { const prAuthorClient = new AzureDevOpsWebApiClient( taskInputs.organizationUrl.toString(), taskInputs.systemAccessToken, + taskInputs.debug, ); const prApproverClient = taskInputs.autoApprove ? new AzureDevOpsWebApiClient( taskInputs.organizationUrl.toString(), taskInputs.autoApproveUserToken || taskInputs.systemAccessToken, + taskInputs.debug, ) : null; @@ -87,8 +89,9 @@ async function run() { taskInputs, prAuthorClient, prApproverClient, - existingPullRequests, existingBranchNames, + existingPullRequests, + taskInputs.debug, ), taskInputs.debug, ); @@ -161,24 +164,34 @@ async function run() { } // Run an update job for "all dependencies"; this will create new pull requests for dependencies that need updating - const dependenciesHaveVulnerabilities = dependencyNamesToUpdate.length && securityAdvisories.length; - if (!securityUpdatesOnly || dependenciesHaveVulnerabilities) { - failedTasks += handleUpdateOperationResults( - await dependabot.update( - DependabotJobBuilder.updateAllDependenciesJob( - taskInputs, - updateId, - update, - dependabotConfig.registries, - dependencyNamesToUpdate, - existingPullRequestDependenciesForPackageEcosystem, - securityAdvisories, + const openPullRequestsLimit = update['open-pull-requests-limit']; + const openPullRequestsCount = Object.entries(existingPullRequestsForPackageEcosystem).length; + const hasReachedOpenPullRequestLimit = + openPullRequestsLimit > 0 && openPullRequestsCount >= openPullRequestsLimit; + if (!hasReachedOpenPullRequestLimit) { + const dependenciesHaveVulnerabilities = dependencyNamesToUpdate.length && securityAdvisories.length; + if (!securityUpdatesOnly || dependenciesHaveVulnerabilities) { + failedTasks += handleUpdateOperationResults( + await dependabot.update( + DependabotJobBuilder.updateAllDependenciesJob( + taskInputs, + updateId, + update, + dependabotConfig.registries, + dependencyNamesToUpdate, + existingPullRequestDependenciesForPackageEcosystem, + securityAdvisories, + ), + dependabotUpdaterOptions, ), - dependabotUpdaterOptions, - ), - ); + ); + } else { + console.info('Nothing to update; dependencies are not affected by any known vulnerability'); + } } else { - console.info('Nothing to update; dependencies are not affected by any known vulnerability'); + warning( + `Skipping update for ${packageEcosystem} packages as the open pull requests limit (${openPullRequestsLimit}) has already been reached`, + ); } // If there are existing pull requests, run an update job for each one; this will resolve merge conflicts and close pull requests that are no longer needed @@ -202,7 +215,7 @@ async function run() { } } else { warning( - `Skipping update of ${numberOfPullRequestsToUpdate} existing pull request(s) as 'skipPullRequests' is set to 'true'`, + `Skipping update of ${numberOfPullRequestsToUpdate} existing ${packageEcosystem} package pull request(s) as 'skipPullRequests' is set to 'true'`, ); } } diff --git a/extension/tasks/dependabotV2/task.json b/extension/tasks/dependabotV2/task.json index 25b8702f..3d1a6485 100644 --- a/extension/tasks/dependabotV2/task.json +++ b/extension/tasks/dependabotV2/task.json @@ -7,10 +7,11 @@ "helpMarkDown": "For help please visit https://github.com/tinglesoftware/dependabot-azure-devops/issues", "helpUrl": "https://github.com/tinglesoftware/dependabot-azure-devops/issues", "releaseNotes": "https://github.com/tinglesoftware/dependabot-azure-devops/releases", - "category": "Utility", + "author": "Tingle Software", + "category": "Azure Pipelines", "visibility": ["Build", "Release"], "runsOn": ["Agent", "DeploymentGroup"], - "author": "Tingle Software", + "minimumAgentVersion": "3.232.1", "demands": [], "version": { "Major": 2, @@ -18,8 +19,7 @@ "Patch": 0 }, "preview": true, - "instanceNameFormat": "Dependabot", - "minimumAgentVersion": "3.232.1", + "instanceNameFormat": "Dependabot update", "groups": [ { "name": "pull_requests", @@ -229,11 +229,9 @@ "helpMarkDown": "Comma-seperated list of key/value pairs representing the enabled Dependabot experiments e.g. `experiments: 'tidy=true,vendor=true,goprivate=*'`. Available options vary depending on the package ecosystem. See [configuring experiments](https://github.com/tinglesoftware/dependabot-azure-devops/#configuring-experiments) for more details." } ], - "dataSourceBindings": [], "execution": { "Node20_1": { - "target": "index.js", - "argumentFormat": "" + "target": "index.js" } } } diff --git a/extension/tasks/dependabotV2/utils/azure-devops/AzureDevOpsWebApiClient.test.ts b/extension/tasks/dependabotV2/utils/azure-devops/AzureDevOpsWebApiClient.test.ts new file mode 100644 index 00000000..d4091b0f --- /dev/null +++ b/extension/tasks/dependabotV2/utils/azure-devops/AzureDevOpsWebApiClient.test.ts @@ -0,0 +1,95 @@ +import { jest } from '@jest/globals'; + +import { VersionControlChangeType } from 'azure-devops-node-api/interfaces/TfvcInterfaces'; + +import { AzureDevOpsWebApiClient } from './AzureDevOpsWebApiClient'; +import { ICreatePullRequest } from './interfaces/IPullRequest'; +import exp = require('constants'); + +jest.mock('azure-devops-node-api'); +jest.mock('azure-pipelines-task-lib/task'); + +describe('AzureDevOpsWebApiClient', () => { + const organisationApiUrl = 'https://dev.azure.com/mock-organization'; + const accessToken = 'mock-access-token'; + let client: AzureDevOpsWebApiClient; + + beforeEach(() => { + client = new AzureDevOpsWebApiClient(organisationApiUrl, accessToken); + }); + + afterEach(() => { + jest.clearAllMocks(); + }); + + describe('createPullRequest', () => { + let pr: ICreatePullRequest; + + beforeEach(() => { + pr = { + project: 'project', + repository: 'repository', + source: { + branch: 'update-branch', + commit: 'commit-id', + }, + target: { + branch: 'main', + }, + title: 'PR Title', + description: 'PR Description', + commitMessage: 'Commit Message', + changes: [ + { + path: 'file.txt', + content: 'hello world', + encoding: 'utf-8', + changeType: VersionControlChangeType.Add, + }, + ], + }; + }); + + it('should create a pull request without duplicate reviewer and assignee identities', async () => { + // Arange + const mockGetUserId = jest.spyOn(client, 'getUserId').mockResolvedValue('my-user-id'); + const mockResolveIdentityId = jest + .spyOn(client, 'resolveIdentityId') + .mockImplementation(async (identity?: string) => { + return identity || ''; + }); + const mockRestApiPost = jest + .spyOn(client as any, 'restApiPost') + .mockResolvedValueOnce({ + commits: [{ commitId: 'new-commit-id' }], + }) + .mockResolvedValueOnce({ + pullRequestId: 1, + }); + const mockRestApiPatch = jest.spyOn(client as any, 'restApiPatch').mockResolvedValueOnce({ + count: 1, + }); + + // Act + pr.assignees = ['user1', 'user2']; + pr.reviewers = ['user1', 'user3']; + const pullRequestId = await client.createPullRequest(pr); + + // Assert + expect(mockRestApiPost).toHaveBeenCalledTimes(2); + expect((mockRestApiPost.mock.calls[1] as any)[1].reviewers.length).toBe(3); + expect((mockRestApiPost.mock.calls[1] as any)[1].reviewers).toContainEqual({ + id: 'user1', + isRequired: true, + isFlagged: true, + }); + expect((mockRestApiPost.mock.calls[1] as any)[1].reviewers).toContainEqual({ + id: 'user2', + isRequired: true, + isFlagged: true, + }); + expect((mockRestApiPost.mock.calls[1] as any)[1].reviewers).toContainEqual({ id: 'user3' }); + expect(pullRequestId).toBe(1); + }); + }); +}); diff --git a/extension/tasks/dependabotV2/utils/azure-devops/AzureDevOpsWebApiClient.ts b/extension/tasks/dependabotV2/utils/azure-devops/AzureDevOpsWebApiClient.ts index c9cf2de9..e310986a 100644 --- a/extension/tasks/dependabotV2/utils/azure-devops/AzureDevOpsWebApiClient.ts +++ b/extension/tasks/dependabotV2/utils/azure-devops/AzureDevOpsWebApiClient.ts @@ -23,18 +23,19 @@ import { export class AzureDevOpsWebApiClient { private readonly organisationApiUrl: string; private readonly identityApiUrl: string; - private readonly accessToken: string; private readonly connection: WebApi; + private readonly debug: boolean; + private authenticatedUserId: string; private resolvedUserIds: Record; public static API_VERSION = '5.0'; // this is the same version used by dependabot-core - constructor(organisationApiUrl: string, accessToken: string) { + constructor(organisationApiUrl: string, accessToken: string, debug: boolean = false) { this.organisationApiUrl = organisationApiUrl.replace(/\/$/, ''); // trim trailing slash this.identityApiUrl = getIdentityApiUrl(organisationApiUrl).replace(/\/$/, ''); // trim trailing slash - this.accessToken = accessToken; this.connection = new WebApi(organisationApiUrl, getPersonalAccessTokenHandler(accessToken)); + this.debug = debug; this.resolvedUserIds = {}; } @@ -189,26 +190,26 @@ export class AzureDevOpsWebApiClient { if (pr.assignees?.length > 0) { for (const assignee of pr.assignees) { const identityId = isGuid(assignee) ? assignee : await this.resolveIdentityId(assignee); - if (identityId) { + if (identityId && !allReviewers.some((r) => r.id === identityId)) { allReviewers.push({ id: identityId, isRequired: true, isFlagged: true, }); } else { - warning(` ! Unable to resolve assignee identity '${assignee}'`); + warning(`Unable to resolve assignee identity '${assignee}'`); } } } if (pr.reviewers?.length > 0) { for (const reviewer of pr.reviewers) { const identityId = isGuid(reviewer) ? reviewer : await this.resolveIdentityId(reviewer); - if (identityId) { + if (identityId && !allReviewers.some((r) => r.id === identityId)) { allReviewers.push({ id: identityId, }); } else { - warning(` ! Unable to resolve reviewer identity '${reviewer}'`); + warning(`Unable to resolve reviewer identity '${reviewer}'`); } } } @@ -628,7 +629,7 @@ export class AzureDevOpsWebApiClient { .map((key) => `${key}=${params[key]}`) .join('&'); const fullUrl = `${url}?api-version=${apiVersion}${queryString ? `&${queryString}` : ''}`; - return await this.restApiRequest('GET', fullUrl, () => + return await this.restApiRequest('GET', fullUrl, undefined, () => this.connection.rest.client.get(fullUrl, { Accept: 'application/json', }), @@ -641,7 +642,7 @@ export class AzureDevOpsWebApiClient { apiVersion: string = AzureDevOpsWebApiClient.API_VERSION, ): Promise { const fullUrl = `${url}?api-version=${apiVersion}`; - return await this.restApiRequest('POST', fullUrl, () => + return await this.restApiRequest('POST', fullUrl, data, () => this.connection.rest.client.post(fullUrl, JSON.stringify(data), { 'Content-Type': 'application/json', }), @@ -654,7 +655,7 @@ export class AzureDevOpsWebApiClient { apiVersion: string = AzureDevOpsWebApiClient.API_VERSION, ): Promise { const fullUrl = `${url}?api-version=${apiVersion}`; - return await this.restApiRequest('PUT', fullUrl, () => + return await this.restApiRequest('PUT', fullUrl, data, () => this.connection.rest.client.put(fullUrl, JSON.stringify(data), { 'Content-Type': 'application/json', }), @@ -668,7 +669,7 @@ export class AzureDevOpsWebApiClient { apiVersion: string = AzureDevOpsWebApiClient.API_VERSION, ): Promise { const fullUrl = `${url}?api-version=${apiVersion}`; - return await this.restApiRequest('PATCH', fullUrl, () => + return await this.restApiRequest('PATCH', fullUrl, data, () => this.connection.rest.client.patch(fullUrl, JSON.stringify(data), { 'Content-Type': contentType || 'application/json', }), @@ -678,21 +679,40 @@ export class AzureDevOpsWebApiClient { private async restApiRequest( method: string, url: string, - request: () => Promise, + payload: any, + requestAsync: () => Promise, ): Promise { - console.debug(`🌎 🠊 [${method}] ${url}`); - const response = await request(); + // Send the request, ready the response + if (this.debug) console.debug(`🌎 🠊 [${method}] ${url}`); + const response = await requestAsync(); const body = await response.readBody(); - console.debug(`🌎 🠈 [${response.message.statusCode}] ${response.message.statusMessage}`); + if (this.debug) console.debug(`🌎 🠈 [${response.message.statusCode}] ${response.message.statusMessage}`); + try { + // Check that the request was successful if (response.message.statusCode < 200 || response.message.statusCode > 299) { - throw new Error(`Request to '${url}' failed: ${response.message.statusCode} ${response.message.statusMessage}`); + throw new Error( + `HTTP ${method} '${url}' failed: ${response.message.statusCode} ${response.message.statusMessage}`, + ); } + + // Parse the response return JSON.parse(body); } catch (e) { - if (body) { - console.debug(body); + // In debug mode, log the error, request, and response for debugging + if (this.debug) { + if (payload) { + console.debug('REQUEST:', JSON.stringify(payload, null, 2)); + } + if (body) { + try { + console.debug('RESPONSE:', JSON.stringify(JSON.parse(body), null, 2)); + } catch { + console.debug('RESPONSE:', body); // If the response is not JSON, just log the raw body + } + } } + throw e; } } diff --git a/extension/tasks/dependabotV2/utils/dependabot-cli/DependabotCli.ts b/extension/tasks/dependabotV2/utils/dependabot-cli/DependabotCli.ts index 32a10c65..b884a9fe 100644 --- a/extension/tasks/dependabotV2/utils/dependabot-cli/DependabotCli.ts +++ b/extension/tasks/dependabotV2/utils/dependabot-cli/DependabotCli.ts @@ -23,7 +23,7 @@ export class DependabotCli { public static readonly CLI_IMAGE_LATEST = 'github.com/dependabot/cli/cmd/dependabot@latest'; - constructor(cliToolImage: string, outputProcessor: IDependabotUpdateOutputProcessor, debug: boolean) { + constructor(cliToolImage: string, outputProcessor: IDependabotUpdateOutputProcessor, debug: boolean = false) { this.jobsPath = path.join(os.tmpdir(), 'dependabot-jobs'); this.toolImage = cliToolImage; this.outputProcessor = outputProcessor; diff --git a/extension/tasks/dependabotV2/utils/dependabot-cli/DependabotOutputProcessor.ts b/extension/tasks/dependabotV2/utils/dependabot-cli/DependabotOutputProcessor.ts index 5b97a563..774a9895 100644 --- a/extension/tasks/dependabotV2/utils/dependabot-cli/DependabotOutputProcessor.ts +++ b/extension/tasks/dependabotV2/utils/dependabot-cli/DependabotOutputProcessor.ts @@ -16,9 +16,11 @@ import { IDependabotUpdateOutputProcessor } from './interfaces/IDependabotUpdate export class DependabotOutputProcessor implements IDependabotUpdateOutputProcessor { private readonly prAuthorClient: AzureDevOpsWebApiClient; private readonly prApproverClient: AzureDevOpsWebApiClient; - private readonly existingPullRequests: IPullRequestProperties[]; private readonly existingBranchNames: string[]; + private readonly existingPullRequests: IPullRequestProperties[]; + private readonly createdPullRequestIds: number[]; private readonly taskInputs: ISharedVariables; + private readonly debug: boolean; // Custom properties used to store dependabot metadata in projects. // https://learn.microsoft.com/en-us/rest/api/azure/devops/core/projects/set-project-properties @@ -36,14 +38,17 @@ export class DependabotOutputProcessor implements IDependabotUpdateOutputProcess taskInputs: ISharedVariables, prAuthorClient: AzureDevOpsWebApiClient, prApproverClient: AzureDevOpsWebApiClient, - existingPullRequests: IPullRequestProperties[], existingBranchNames: string[], + existingPullRequests: IPullRequestProperties[], + debug: boolean = false, ) { this.taskInputs = taskInputs; this.prAuthorClient = prAuthorClient; this.prApproverClient = prApproverClient; - this.existingPullRequests = existingPullRequests; this.existingBranchNames = existingBranchNames; + this.existingPullRequests = existingPullRequests; + this.createdPullRequestIds = []; + this.debug = debug; } /** @@ -54,10 +59,14 @@ export class DependabotOutputProcessor implements IDependabotUpdateOutputProcess * @returns */ public async process(update: IDependabotUpdateOperation, type: string, data: any): Promise { - section(`Processing '${type}'`); - console.debug('Data:', data); const project = this.taskInputs.project; const repository = this.taskInputs.repository; + const packageManager = update?.job?.['package-manager']; + + section(`Processing '${type}'`); + if (this.debug) { + console.debug(JSON.stringify(data, null, 2)); + } switch (type) { // Documentation on the 'data' model for each output type can be found here: // See: https://github.com/dependabot/cli/blob/main/internal/model/update.go @@ -65,14 +74,13 @@ export class DependabotOutputProcessor implements IDependabotUpdateOutputProcess case 'update_dependency_list': // Store the dependency list snapshot in project properties, if configured if (this.taskInputs.storeDependencyList) { - console.info(`Updating the dependency list snapshot for project '${project}'...`); return await this.prAuthorClient.updateProjectProperty( this.taskInputs.projectId, DependabotOutputProcessor.PROJECT_PROPERTY_NAME_DEPENDENCY_LIST, function (existingValue: string) { const repoDependencyLists = JSON.parse(existingValue || '{}'); repoDependencyLists[repository] = repoDependencyLists[repository] || {}; - repoDependencyLists[repository][update.job['package-manager']] = { + repoDependencyLists[repository][packageManager] = { 'dependencies': data['dependencies'], 'dependency-files': data['dependency_files'], 'last-updated': new Date().toISOString(), @@ -86,16 +94,18 @@ export class DependabotOutputProcessor implements IDependabotUpdateOutputProcess return true; case 'create_pull_request': + const title = data['pr-title']; if (this.taskInputs.skipPullRequests) { - warning(`Skipping pull request creation as 'skipPullRequests' is set to 'true'`); + warning(`Skipping pull request creation of '${title}' as 'skipPullRequests' is set to 'true'`); return true; } // Skip if active pull request limit reached. - const openPullRequestLimit = update.config['open-pull-requests-limit']; - if (openPullRequestLimit > 0 && this.existingPullRequests.length >= openPullRequestLimit) { + const openPullRequestsLimit = update.config['open-pull-requests-limit']; + const openPullRequestsCount = this.createdPullRequestIds.length + this.existingPullRequests.length; + if (openPullRequestsLimit > 0 && openPullRequestsCount >= openPullRequestsLimit) { warning( - `Skipping pull request creation as the maximum number of active pull requests (${openPullRequestLimit}) has been reached`, + `Skipping pull request creation of '${title}' as the open pull requests limit (${openPullRequestsLimit}) has been reached`, ); return true; } @@ -117,14 +127,14 @@ export class DependabotOutputProcessor implements IDependabotUpdateOutputProcess const existingBranch = this.existingBranchNames?.find((branch) => sourceBranch == branch) || []; if (existingBranch.length) { error( - `Unable to create pull request as source branch '${sourceBranch}' already exists; Delete the existing branch and try again.`, + `Unable to create pull request '${title}' as source branch '${sourceBranch}' already exists; Delete the existing branch and try again.`, ); return false; } const conflictingBranches = this.existingBranchNames?.filter((branch) => sourceBranch.startsWith(branch)) || []; if (conflictingBranches.length) { error( - `Unable to create pull request as source branch '${sourceBranch}' would conflict with existing branch(es) '${conflictingBranches.join(', ')}'; Delete the conflicting branch(es) and try again.`, + `Unable to create pull request '${title}' as source branch '${sourceBranch}' would conflict with existing branch(es) '${conflictingBranches.join(', ')}'; Delete the conflicting branch(es) and try again.`, ); return false; } @@ -145,7 +155,7 @@ export class DependabotOutputProcessor implements IDependabotUpdateOutputProcess name: this.taskInputs.authorName || DependabotOutputProcessor.PR_DEFAULT_AUTHOR_NAME, }, title: data['pr-title'], - description: data['pr-body'], + description: getPullRequestDescription(packageManager, data['pr-body'], data['dependencies']), commitMessage: data['commit-message'], autoComplete: this.taskInputs.setAutoComplete ? { @@ -171,7 +181,7 @@ export class DependabotOutputProcessor implements IDependabotUpdateOutputProcess labels: update.config.labels?.map((label) => label?.trim()) || [], workItems: update.config.milestone ? [update.config.milestone] : [], changes: changedFiles, - properties: buildPullRequestProperties(update.job['package-manager'], dependencies), + properties: buildPullRequestProperties(packageManager, dependencies), }); // Auto-approve the pull request, if required @@ -183,7 +193,13 @@ export class DependabotOutputProcessor implements IDependabotUpdateOutputProcess }); } - return newPullRequestId > 0; + // Store the new pull request ID, so we can keep track of the total number of open pull requests + if (newPullRequestId > 0) { + this.createdPullRequestIds.push(newPullRequestId); + return true; + } else { + return false; + } case 'update_pull_request': if (this.taskInputs.skipPullRequests) { @@ -192,13 +208,10 @@ export class DependabotOutputProcessor implements IDependabotUpdateOutputProcess } // Find the pull request to update - const pullRequestToUpdate = this.getPullRequestForDependencyNames( - update.job['package-manager'], - data['dependency-names'], - ); + const pullRequestToUpdate = this.getPullRequestForDependencyNames(packageManager, data['dependency-names']); if (!pullRequestToUpdate) { error( - `Could not find pull request to update for package manager '${update.job['package-manager']}' and dependencies '${data['dependency-names'].join(', ')}'`, + `Could not find pull request to update for package manager '${packageManager}' with dependencies '${data['dependency-names'].join(', ')}'`, ); return false; } @@ -238,13 +251,10 @@ export class DependabotOutputProcessor implements IDependabotUpdateOutputProcess } // Find the pull request to close - const pullRequestToClose = this.getPullRequestForDependencyNames( - update.job['package-manager'], - data['dependency-names'], - ); + const pullRequestToClose = this.getPullRequestForDependencyNames(packageManager, data['dependency-names']); if (!pullRequestToClose) { error( - `Could not find pull request to close for package manager '${update.job['package-manager']}' and dependencies '${data['dependency-names'].join(', ')}'`, + `Could not find pull request to close for package manager '${packageManager}' with dependencies '${data['dependency-names'].join(', ')}'`, ); return false; } @@ -429,3 +439,29 @@ function areEqual(a: string[], b: string[]): boolean { if (a.length !== b.length) return false; return a.every((name) => b.includes(name)); } + +function getPullRequestDescription(packageManager: string, body: string, dependencies: any[]): string { + let header = ''; + let footer = ''; + + // Fix up GitHub mentions encoding issues by removing instances of the zero-width space '\u200B' as it does not render correctly in Azure DevOps. + // https://github.com/dependabot/dependabot-core/issues/9572 + // https://github.com/dependabot/dependabot-core/blob/313fcff149b3126cb78b38d15f018907d729f8cc/common/lib/dependabot/pull_request_creator/message_builder/link_and_mention_sanitizer.rb#L245-L252 + const description = (body || '').replace(new RegExp(decodeURIComponent('%EF%BF%BD%EF%BF%BD%EF%BF%BD'), 'g'), ''); + + // If there is exactly one dependency, add a compatibility score badge to the description header. + // Compatibility scores are intended for single dependency security updates, not group updates. + // https://docs.github.com/en/github/managing-security-vulnerabilities/about-dependabot-security-updates#about-compatibility-scores + if (dependencies.length === 1) { + const compatibilityScoreBadges = dependencies.map((dep) => { + return `![Dependabot compatibility score](https://dependabot-badges.githubapp.com/badges/compatibility_score?dependency-name=${dep['name']}&package-manager=${packageManager}&previous-version=${dep['previous-version']}&new-version=${dep['version']})`; + }); + header += compatibilityScoreBadges.join(' ') + '\n\n'; + } + + // Build the full pull request description. + // The header/footer must not be truncated. If the description is too long, we truncate the body. + const maxDescriptionLength = 4000; + const maxDescriptionLengthAfterHeaderAndFooter = maxDescriptionLength - header.length - footer.length; + return `${header}${description.substring(0, maxDescriptionLengthAfterHeaderAndFooter)}${footer}`; +} diff --git a/extension/tasks/dependabotV2/utils/dependabot/parseConfigFile.ts b/extension/tasks/dependabotV2/utils/dependabot/parseConfigFile.ts index 5440cf0a..f8a5db8b 100644 --- a/extension/tasks/dependabotV2/utils/dependabot/parseConfigFile.ts +++ b/extension/tasks/dependabotV2/utils/dependabot/parseConfigFile.ts @@ -158,6 +158,8 @@ function parseUpdates(config: any): IDependabotUpdate[] { switch (ecosystem) { case 'devcontainer': return 'devcontainers'; + case 'dotnet-sdk': + return 'dotnet_sdk'; case 'github-actions': return 'github_actions'; case 'gitsubmodule': diff --git a/extension/vss-extension.json b/extension/vss-extension.json index 2f4c8707..c8ff00f7 100644 --- a/extension/vss-extension.json +++ b/extension/vss-extension.json @@ -3,6 +3,7 @@ "manifestVersion": 1, "id": "dependabot", "name": "Dependabot", + "description": "Automatically update dependencies and vulnerabilities in your code", "version": "2.0.0", "publisher": "tingle-software", "public": false, @@ -11,16 +12,42 @@ "id": "Microsoft.VisualStudio.Services" } ], - "description": "Automatically update dependencies and vulnerabilities in your code", + "demands": ["api-version/5.0"], "categories": ["Azure Pipelines"], + "tags": [ + "dependabot", + "dependency", + "dependencies", + "package", + "update", + "vulnerability", + "vulnerabilities", + "security" + ], "icons": { "default": "images/icon.png" }, "links": { + "home": { + "uri": "https://github.com/tinglesoftware/dependabot-azure-devops" + }, + "getstarted": { + "uri": "https://github.com/tinglesoftware/dependabot-azure-devops/blob/main/extension/README.md#usage" + }, + "issues": { + "uri": "https://github.com/tinglesoftware/dependabot-azure-devops/issues" + }, "support": { "uri": "https://github.com/tinglesoftware/dependabot-azure-devops/issues" + }, + "license": { + "uri": "https://github.com/tinglesoftware/dependabot-azure-devops/blob/main/LICENSE" } }, + "customerQnASupport": { + "enablemarketplaceqna": true, + "url": "https://github.com/tinglesoftware/dependabot-azure-devops/discussions" + }, "repository": { "type": "git", "uri": "https://github.com/tinglesoftware/dependabot-azure-devops" diff --git a/package-lock.json b/package-lock.json index 28ce1a9d..9849f04f 100644 --- a/package-lock.json +++ b/package-lock.json @@ -7,9 +7,260 @@ "": { "name": "dependabot-azure-devops", "version": "0.0.0", + "hasInstallScript": true, "devDependencies": { + "husky": "^9.1.6", "prettier": "3.3.3", - "prettier-plugin-organize-imports": "4.1.0" + "prettier-plugin-organize-imports": "4.1.0", + "pretty-quick": "^4.0.0" + } + }, + "node_modules/cross-spawn": { + "version": "7.0.5", + "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-7.0.5.tgz", + "integrity": "sha512-ZVJrKKYunU38/76t0RMOulHOnUcbU9GbpWKAOZ0mhjr7CX6FVrH+4FrAapSOekrgFQ3f/8gwMEuIft0aKq6Hug==", + "dev": true, + "dependencies": { + "path-key": "^3.1.0", + "shebang-command": "^2.0.0", + "which": "^2.0.1" + }, + "engines": { + "node": ">= 8" + } + }, + "node_modules/execa": { + "version": "5.1.1", + "resolved": "https://registry.npmjs.org/execa/-/execa-5.1.1.tgz", + "integrity": "sha512-8uSpZZocAZRBAPIEINJj3Lo9HyGitllczc27Eh5YYojjMFMn8yHMDMaUHE2Jqfq05D/wucwI4JGURyXt1vchyg==", + "dev": true, + "dependencies": { + "cross-spawn": "^7.0.3", + "get-stream": "^6.0.0", + "human-signals": "^2.1.0", + "is-stream": "^2.0.0", + "merge-stream": "^2.0.0", + "npm-run-path": "^4.0.1", + "onetime": "^5.1.2", + "signal-exit": "^3.0.3", + "strip-final-newline": "^2.0.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sindresorhus/execa?sponsor=1" + } + }, + "node_modules/find-up": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/find-up/-/find-up-5.0.0.tgz", + "integrity": "sha512-78/PXT1wlLLDgTzDs7sjq9hzz0vXD+zn+7wypEe4fXQxCmdmqfGsEPQxmiCSQI3ajFV91bVSsvNtrJRiW6nGng==", + "dev": true, + "dependencies": { + "locate-path": "^6.0.0", + "path-exists": "^4.0.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/get-stream": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/get-stream/-/get-stream-6.0.1.tgz", + "integrity": "sha512-ts6Wi+2j3jQjqi70w5AlN8DFnkSwC+MqmxEzdEALB2qXZYV3X/b1CTfgPLGJNMeAWxdPfU8FO1ms3NUfaHCPYg==", + "dev": true, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/human-signals": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/human-signals/-/human-signals-2.1.0.tgz", + "integrity": "sha512-B4FFZ6q/T2jhhksgkbEW3HBvWIfDW85snkQgawt07S7J5QXTk6BkNV+0yAeZrM5QpMAdYlocGoljn0sJ/WQkFw==", + "dev": true, + "engines": { + "node": ">=10.17.0" + } + }, + "node_modules/husky": { + "version": "9.1.6", + "resolved": "https://registry.npmjs.org/husky/-/husky-9.1.6.tgz", + "integrity": "sha512-sqbjZKK7kf44hfdE94EoX8MZNk0n7HeW37O4YrVGCF4wzgQjp+akPAkfUK5LZ6KuR/6sqeAVuXHji+RzQgOn5A==", + "dev": true, + "bin": { + "husky": "bin.js" + }, + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/typicode" + } + }, + "node_modules/ignore": { + "version": "5.3.2", + "resolved": "https://registry.npmjs.org/ignore/-/ignore-5.3.2.tgz", + "integrity": "sha512-hsBTNUqQTDwkWtcdYI2i06Y/nUBEsNEDJKjWdigLvegy8kDuJAS8uRlpkkcQpyEXL0Z/pjDy5HBmMjRCJ2gq+g==", + "dev": true, + "engines": { + "node": ">= 4" + } + }, + "node_modules/is-stream": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/is-stream/-/is-stream-2.0.1.tgz", + "integrity": "sha512-hFoiJiTl63nn+kstHGBtewWSKnQLpyb155KHheA1l39uvtO9nWIop1p3udqPcUd/xbF1VLMO4n7OI6p7RbngDg==", + "dev": true, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/isexe": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/isexe/-/isexe-2.0.0.tgz", + "integrity": "sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw==", + "dev": true + }, + "node_modules/locate-path": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-6.0.0.tgz", + "integrity": "sha512-iPZK6eYjbxRu3uB4/WZ3EsEIMJFMqAoopl3R+zuq0UjcAm/MO6KCweDgPfP3elTztoKP3KtnVHxTn2NHBSDVUw==", + "dev": true, + "dependencies": { + "p-locate": "^5.0.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/merge-stream": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/merge-stream/-/merge-stream-2.0.0.tgz", + "integrity": "sha512-abv/qOcuPfk3URPfDzmZU1LKmuw8kT+0nIHvKrKgFrwifol/doWcdA4ZqsWQ8ENrFKkd67Mfpo/LovbIUsbt3w==", + "dev": true + }, + "node_modules/mimic-fn": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/mimic-fn/-/mimic-fn-2.1.0.tgz", + "integrity": "sha512-OqbOk5oEQeAZ8WXWydlu9HJjz9WVdEIvamMCcXmuqUYjTknH/sqsWvhQ3vgwKFRR1HpjvNBKQ37nbJgYzGqGcg==", + "dev": true, + "engines": { + "node": ">=6" + } + }, + "node_modules/mri": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/mri/-/mri-1.2.0.tgz", + "integrity": "sha512-tzzskb3bG8LvYGFF/mDTpq3jpI6Q9wc3LEmBaghu+DdCssd1FakN7Bc0hVNmEyGq1bq3RgfkCb3cmQLpNPOroA==", + "dev": true, + "engines": { + "node": ">=4" + } + }, + "node_modules/npm-run-path": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/npm-run-path/-/npm-run-path-4.0.1.tgz", + "integrity": "sha512-S48WzZW777zhNIrn7gxOlISNAqi9ZC/uQFnRdbeIHhZhCA6UqpkOT8T1G7BvfdgP4Er8gF4sUbaS0i7QvIfCWw==", + "dev": true, + "dependencies": { + "path-key": "^3.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/onetime": { + "version": "5.1.2", + "resolved": "https://registry.npmjs.org/onetime/-/onetime-5.1.2.tgz", + "integrity": "sha512-kbpaSSGJTWdAY5KPVeMOKXSrPtr8C8C7wodJbcsd51jRnmD+GZu8Y0VoU6Dm5Z4vWr0Ig/1NKuWRKf7j5aaYSg==", + "dev": true, + "dependencies": { + "mimic-fn": "^2.1.0" + }, + "engines": { + "node": ">=6" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/p-limit": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-3.1.0.tgz", + "integrity": "sha512-TYOanM3wGwNGsZN2cVTYPArw454xnXj5qmWF1bEoAc4+cU/ol7GVh7odevjp1FNHduHc3KZMcFduxU5Xc6uJRQ==", + "dev": true, + "dependencies": { + "yocto-queue": "^0.1.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/p-locate": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/p-locate/-/p-locate-5.0.0.tgz", + "integrity": "sha512-LaNjtRWUBY++zB5nE/NwcaoMylSPk+S+ZHNB1TzdbMJMny6dynpAGt7X/tl/QYq3TIeE6nxHppbo2LGymrG5Pw==", + "dev": true, + "dependencies": { + "p-limit": "^3.0.2" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/path-exists": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/path-exists/-/path-exists-4.0.0.tgz", + "integrity": "sha512-ak9Qy5Q7jYb2Wwcey5Fpvg2KoAc/ZIhLSLOSBmRmygPsGwkVVt0fZa0qrtMz+m6tJTAHfZQ8FnmB4MG4LWy7/w==", + "dev": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/path-key": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/path-key/-/path-key-3.1.1.tgz", + "integrity": "sha512-ojmeN0qd+y0jszEtoY48r0Peq5dwMEkIlCOu6Q5f41lfkswXuKtYrhgoTpLnyIcHm24Uhqx+5Tqm2InSwLhE6Q==", + "dev": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/picocolors": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/picocolors/-/picocolors-1.1.1.tgz", + "integrity": "sha512-xceH2snhtb5M9liqDsmEw56le376mTZkEX/jEb/RxNFyegNul7eNslCXP9FDj/Lcu0X8KEyMceP2ntpaHrDEVA==", + "dev": true + }, + "node_modules/picomatch": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-3.0.1.tgz", + "integrity": "sha512-I3EurrIQMlRc9IaAZnqRR044Phh2DXY+55o7uJ0V+hYZAcQYSuFWsc9q5PvyDHUSCe1Qxn/iBz+78s86zWnGag==", + "dev": true, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/jonschlinkert" } }, "node_modules/prettier": { @@ -44,6 +295,72 @@ } } }, + "node_modules/pretty-quick": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/pretty-quick/-/pretty-quick-4.0.0.tgz", + "integrity": "sha512-M+2MmeufXb/M7Xw3Afh1gxcYpj+sK0AxEfnfF958ktFeAyi5MsKY5brymVURQLgPLV1QaF5P4pb2oFJ54H3yzQ==", + "dev": true, + "dependencies": { + "execa": "^5.1.1", + "find-up": "^5.0.0", + "ignore": "^5.3.0", + "mri": "^1.2.0", + "picocolors": "^1.0.0", + "picomatch": "^3.0.1", + "tslib": "^2.6.2" + }, + "bin": { + "pretty-quick": "lib/cli.mjs" + }, + "engines": { + "node": ">=14" + }, + "peerDependencies": { + "prettier": "^3.0.0" + } + }, + "node_modules/shebang-command": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/shebang-command/-/shebang-command-2.0.0.tgz", + "integrity": "sha512-kHxr2zZpYtdmrN1qDjrrX/Z1rR1kG8Dx+gkpK1G4eXmvXswmcE1hTWBWYUzlraYw1/yZp6YuDY77YtvbN0dmDA==", + "dev": true, + "dependencies": { + "shebang-regex": "^3.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/shebang-regex": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/shebang-regex/-/shebang-regex-3.0.0.tgz", + "integrity": "sha512-7++dFhtcx3353uBaq8DDR4NuxBetBzC7ZQOhmTQInHEd6bSrXdiEyzCvG07Z44UYdLShWUyXt5M/yhz8ekcb1A==", + "dev": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/signal-exit": { + "version": "3.0.7", + "resolved": "https://registry.npmjs.org/signal-exit/-/signal-exit-3.0.7.tgz", + "integrity": "sha512-wnD2ZE+l+SPC/uoS0vXeE9L1+0wuaMqKlfz9AMUo38JsyLSBWSFcHR1Rri62LZc12vLr1gb3jl7iwQhgwpAbGQ==", + "dev": true + }, + "node_modules/strip-final-newline": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/strip-final-newline/-/strip-final-newline-2.0.0.tgz", + "integrity": "sha512-BrpvfNAE3dcvq7ll3xVumzjKjZQ5tI1sEUIKr3Uoks0XUl45St3FlatVqef9prk4jRDzhW6WZg+3bk93y6pLjA==", + "dev": true, + "engines": { + "node": ">=6" + } + }, + "node_modules/tslib": { + "version": "2.8.1", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.8.1.tgz", + "integrity": "sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w==", + "dev": true + }, "node_modules/typescript": { "version": "5.5.4", "resolved": "https://registry.npmjs.org/typescript/-/typescript-5.5.4.tgz", @@ -58,6 +375,33 @@ "engines": { "node": ">=14.17" } + }, + "node_modules/which": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/which/-/which-2.0.2.tgz", + "integrity": "sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA==", + "dev": true, + "dependencies": { + "isexe": "^2.0.0" + }, + "bin": { + "node-which": "bin/node-which" + }, + "engines": { + "node": ">= 8" + } + }, + "node_modules/yocto-queue": { + "version": "0.1.0", + "resolved": "https://registry.npmjs.org/yocto-queue/-/yocto-queue-0.1.0.tgz", + "integrity": "sha512-rVksvsnNCdJ/ohGc6xgPwyN8eheCxsiLM8mxuE/t/mOVqJewPuO1miLpTHQiRgTKCLexL4MeAFVagts7HmNZ2Q==", + "dev": true, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } } } } diff --git a/package.json b/package.json index 7fa79e2e..c81cd6ba 100644 --- a/package.json +++ b/package.json @@ -3,11 +3,15 @@ "version": "0.0.0", "private": true, "scripts": { + "prepare": "husky", + "postinstall": "npm --prefix extension install", "format": "prettier --write .", "format:check": "prettier --check ." }, "devDependencies": { + "husky": "^9.1.6", "prettier": "3.3.3", - "prettier-plugin-organize-imports": "4.1.0" + "prettier-plugin-organize-imports": "4.1.0", + "pretty-quick": "^4.0.0" } } diff --git a/server/Directory.Build.props b/server/Directory.Build.props index e30a15f7..c9e06e41 100644 --- a/server/Directory.Build.props +++ b/server/Directory.Build.props @@ -2,7 +2,7 @@ - net8.0 + net9.0 latest enable enable diff --git a/server/Tingle.Dependabot.Tests/Tingle.Dependabot.Tests.csproj b/server/Tingle.Dependabot.Tests/Tingle.Dependabot.Tests.csproj index a98fb933..4cbd57a3 100644 --- a/server/Tingle.Dependabot.Tests/Tingle.Dependabot.Tests.csproj +++ b/server/Tingle.Dependabot.Tests/Tingle.Dependabot.Tests.csproj @@ -12,8 +12,8 @@ - - + + diff --git a/server/Tingle.Dependabot.Tests/Workflow/UpdateRunnerTests.cs b/server/Tingle.Dependabot.Tests/Workflow/UpdateRunnerTests.cs index 10dc0a55..6fd55cb9 100644 --- a/server/Tingle.Dependabot.Tests/Workflow/UpdateRunnerTests.cs +++ b/server/Tingle.Dependabot.Tests/Workflow/UpdateRunnerTests.cs @@ -309,6 +309,7 @@ public void ConvertEcosystemToPackageManager_Works(string ecosystem, string expe public static TheoryData ConvertEcosystemToPackageManagerValues => new() { + { "dotnet-sdk", "dotnet_sdk" }, { "github-actions", "github_actions" }, { "gitsubmodule", "submodules" }, { "gomod", "go_modules" }, diff --git a/server/Tingle.Dependabot/Dockerfile b/server/Tingle.Dependabot/Dockerfile index 74e73768..525e2994 100644 --- a/server/Tingle.Dependabot/Dockerfile +++ b/server/Tingle.Dependabot/Dockerfile @@ -1,13 +1,13 @@ #See https://aka.ms/customizecontainer to learn how to customize your debug container and how Visual Studio uses this Dockerfile to build your images for faster debugging. -FROM mcr.microsoft.com/dotnet/aspnet:8.0-bookworm-slim AS base +FROM mcr.microsoft.com/dotnet/aspnet:9.0-bookworm-slim AS base LABEL org.opencontainers.image.source="https://github.com/tinglesoftware/dependabot-azure-devops" USER app WORKDIR /app EXPOSE 8080 EXPOSE 8081 -FROM mcr.microsoft.com/dotnet/sdk:8.0-bookworm-slim AS build +FROM mcr.microsoft.com/dotnet/sdk:9.0-bookworm-slim AS build ARG BUILD_CONFIGURATION=Release WORKDIR /src COPY ["server/Directory.Build.props", "server/"] diff --git a/server/Tingle.Dependabot/Dockerfile.CI b/server/Tingle.Dependabot/Dockerfile.CI index 96024f38..67a333f0 100644 --- a/server/Tingle.Dependabot/Dockerfile.CI +++ b/server/Tingle.Dependabot/Dockerfile.CI @@ -4,7 +4,7 @@ # # As a result, we only copy publish output and put it in the container -FROM mcr.microsoft.com/dotnet/aspnet:8.0-bookworm-slim AS base +FROM mcr.microsoft.com/dotnet/aspnet:9.0-bookworm-slim AS base LABEL org.opencontainers.image.source="https://github.com/tinglesoftware/dependabot-azure-devops" USER app WORKDIR /app diff --git a/server/Tingle.Dependabot/Extensions/ConfigurationManagerExtensions.cs b/server/Tingle.Dependabot/Extensions/ConfigurationManagerExtensions.cs index ce5a176c..8ff90a7a 100644 --- a/server/Tingle.Dependabot/Extensions/ConfigurationManagerExtensions.cs +++ b/server/Tingle.Dependabot/Extensions/ConfigurationManagerExtensions.cs @@ -16,26 +16,24 @@ public static ConfigurationManager AddStandardAzureAppConfiguration(this Configu var section = manager.GetSection("AzureAppConfig"); var endpoint = section.GetValue("Endpoint"); var label = section.GetValue("Label") ?? environment.EnvironmentName; - if (endpoint is not null) + if (endpoint is null) return manager; + + var refreshInterval = section.GetValue("RefreshInterval") ?? TimeSpan.FromHours(1); + manager.AddAzureAppConfiguration(options => { - var cacheExpiration = section.GetValue("CacheExpiration") ?? TimeSpan.FromHours(1); - var cacheExpirationInterval = section.GetValue("CacheExpirationInterval") ?? TimeSpan.FromMinutes(30); - manager.AddAzureAppConfiguration(options => - { - options.Connect(endpoint: endpoint, credential: new DefaultAzureCredential()) - .Select("*", label) - .ConfigureRefresh(o => - { - o.Register("Refresh", label, refreshAll: true) // key to use to trigger refresh - .SetCacheExpiration(cacheExpiration); - }) - .UseFeatureFlags(o => - { - o.Label = label; - o.CacheExpirationInterval = cacheExpirationInterval; - }); - }); - } + options.Connect(endpoint: endpoint, credential: new DefaultAzureCredential()) + .Select("*", label) + .ConfigureRefresh(o => + { + o.Register("Refresh", label, refreshAll: true); // key to use to trigger refresh + o.SetRefreshInterval(refreshInterval); + }) + .UseFeatureFlags(o => + { + o.Label = label; + o.SetRefreshInterval(refreshInterval); + }); + }); return manager; } diff --git a/server/Tingle.Dependabot/Tingle.Dependabot.csproj b/server/Tingle.Dependabot/Tingle.Dependabot.csproj index b55243bf..308f6be4 100644 --- a/server/Tingle.Dependabot/Tingle.Dependabot.csproj +++ b/server/Tingle.Dependabot/Tingle.Dependabot.csproj @@ -25,14 +25,14 @@ - - - + + + - - - + + + @@ -40,11 +40,11 @@ - + - + diff --git a/server/Tingle.Dependabot/Workflow/UpdateRunner.cs b/server/Tingle.Dependabot/Workflow/UpdateRunner.cs index 0c41f804..fa340b27 100644 --- a/server/Tingle.Dependabot/Workflow/UpdateRunner.cs +++ b/server/Tingle.Dependabot/Workflow/UpdateRunner.cs @@ -474,6 +474,7 @@ internal static IList> MakeExtraCredentials(ICollecti return ecosystem switch { + "dotnet-sdk" => "dotnet_sdk", "github-actions" => "github_actions", "gitsubmodule" => "submodules", "gomod" => "go_modules", diff --git a/updater/Gemfile b/updater/Gemfile index 36ca4b07..2aa4ea51 100644 --- a/updater/Gemfile +++ b/updater/Gemfile @@ -8,7 +8,7 @@ source "https://rubygems.org" # They are so many, our reference won't be found for it to be updated. # Hence adding the branch. -gem "dependabot-omnibus", "~>0.282.0" +gem "dependabot-omnibus", "~>0.285.0" # gem "dependabot-omnibus", github: "dependabot/dependabot-core", branch: "main" # gem "dependabot-omnibus", github: "dependabot/dependabot-core", tag: "v0.232.0" # gem "dependabot-omnibus", github: "dependabot/dependabot-core", ref: "ffde6f6" @@ -34,12 +34,12 @@ group :test do gem "gpgme", "~> 2.0" gem "rake", "~> 13" gem "rspec", "~> 3.12" - gem "rspec-its", "~> 1.3" + gem "rspec-its", "~> 2.0" gem "rspec-sorbet", "~> 1.9.2" - gem "rubocop", "~> 1.67.0" - gem "rubocop-performance", "~> 1.22.1" + gem "rubocop", "~> 1.68.0" + gem "rubocop-performance", "~> 1.23.0" gem "rubocop-rspec", "~> 2.29.1" - gem "rubocop-sorbet", "~> 0.8.6" + gem "rubocop-sorbet", "~> 0.8.7" gem "simplecov", "~> 0.22.0" gem "turbo_tests", "~> 2.2.0" gem "vcr", "~> 6.3" diff --git a/updater/Gemfile.lock b/updater/Gemfile.lock index cf87d8a7..bee0d874 100644 --- a/updater/Gemfile.lock +++ b/updater/Gemfile.lock @@ -5,11 +5,11 @@ GEM public_suffix (>= 2.0.2, < 7.0) ast (2.4.2) aws-eventstream (1.3.0) - aws-partitions (1.996.0) + aws-partitions (1.1003.0) aws-sdk-codecommit (1.79.0) aws-sdk-core (~> 3, >= 3.210.0) aws-sigv4 (~> 1.5) - aws-sdk-core (3.211.0) + aws-sdk-core (3.212.0) aws-eventstream (~> 1, >= 1.3.0) aws-partitions (~> 1, >= 1.992.0) aws-sigv4 (~> 1.9) @@ -31,17 +31,17 @@ GEM debug (1.9.2) irb (~> 1.10) reline (>= 0.3.8) - dependabot-bundler (0.282.0) - dependabot-common (= 0.282.0) + dependabot-bundler (0.285.0) + dependabot-common (= 0.285.0) parallel (~> 1.24) - dependabot-cargo (0.282.0) - dependabot-common (= 0.282.0) - dependabot-common (0.282.0) + dependabot-cargo (0.285.0) + dependabot-common (= 0.285.0) + dependabot-common (0.285.0) aws-sdk-codecommit (~> 1.28) aws-sdk-ecr (~> 1.5) bundler (>= 1.16, < 3.0.0) commonmarker (>= 0.20.1, < 0.24.0) - docker_registry2 (~> 1.18.0) + docker_registry2 (~> 1.18.2) excon (~> 0.109) faraday (= 2.7.11) faraday-retry (= 2.2.0) @@ -56,61 +56,64 @@ GEM sorbet-runtime (~> 0.5.11577) stackprof (~> 0.2.16) toml-rb (>= 1.1.2, < 4.0) - dependabot-composer (0.282.0) - dependabot-common (= 0.282.0) - dependabot-devcontainers (0.282.0) - dependabot-common (= 0.282.0) - dependabot-docker (0.282.0) - dependabot-common (= 0.282.0) - dependabot-elm (0.282.0) - dependabot-common (= 0.282.0) - dependabot-git_submodules (0.282.0) - dependabot-common (= 0.282.0) + dependabot-composer (0.285.0) + dependabot-common (= 0.285.0) + dependabot-devcontainers (0.285.0) + dependabot-common (= 0.285.0) + dependabot-docker (0.285.0) + dependabot-common (= 0.285.0) + dependabot-dotnet_sdk (0.285.0) + dependabot-common (= 0.285.0) + dependabot-elm (0.285.0) + dependabot-common (= 0.285.0) + dependabot-git_submodules (0.285.0) + dependabot-common (= 0.285.0) parseconfig (~> 1.0, < 1.1.0) - dependabot-github_actions (0.282.0) - dependabot-common (= 0.282.0) - dependabot-go_modules (0.282.0) - dependabot-common (= 0.282.0) - dependabot-gradle (0.282.0) - dependabot-common (= 0.282.0) - dependabot-maven (= 0.282.0) - dependabot-hex (0.282.0) - dependabot-common (= 0.282.0) - dependabot-maven (0.282.0) - dependabot-common (= 0.282.0) - dependabot-npm_and_yarn (0.282.0) - dependabot-common (= 0.282.0) - dependabot-nuget (0.282.0) - dependabot-common (= 0.282.0) + dependabot-github_actions (0.285.0) + dependabot-common (= 0.285.0) + dependabot-go_modules (0.285.0) + dependabot-common (= 0.285.0) + dependabot-gradle (0.285.0) + dependabot-common (= 0.285.0) + dependabot-maven (= 0.285.0) + dependabot-hex (0.285.0) + dependabot-common (= 0.285.0) + dependabot-maven (0.285.0) + dependabot-common (= 0.285.0) + dependabot-npm_and_yarn (0.285.0) + dependabot-common (= 0.285.0) + dependabot-nuget (0.285.0) + dependabot-common (= 0.285.0) rubyzip (>= 2.3.2, < 3.0) - dependabot-omnibus (0.282.0) - dependabot-bundler (= 0.282.0) - dependabot-cargo (= 0.282.0) - dependabot-common (= 0.282.0) - dependabot-composer (= 0.282.0) - dependabot-devcontainers (= 0.282.0) - dependabot-docker (= 0.282.0) - dependabot-elm (= 0.282.0) - dependabot-git_submodules (= 0.282.0) - dependabot-github_actions (= 0.282.0) - dependabot-go_modules (= 0.282.0) - dependabot-gradle (= 0.282.0) - dependabot-hex (= 0.282.0) - dependabot-maven (= 0.282.0) - dependabot-npm_and_yarn (= 0.282.0) - dependabot-nuget (= 0.282.0) - dependabot-pub (= 0.282.0) - dependabot-python (= 0.282.0) - dependabot-swift (= 0.282.0) - dependabot-terraform (= 0.282.0) - dependabot-pub (0.282.0) - dependabot-common (= 0.282.0) - dependabot-python (0.282.0) - dependabot-common (= 0.282.0) - dependabot-swift (0.282.0) - dependabot-common (= 0.282.0) - dependabot-terraform (0.282.0) - dependabot-common (= 0.282.0) + dependabot-omnibus (0.285.0) + dependabot-bundler (= 0.285.0) + dependabot-cargo (= 0.285.0) + dependabot-common (= 0.285.0) + dependabot-composer (= 0.285.0) + dependabot-devcontainers (= 0.285.0) + dependabot-docker (= 0.285.0) + dependabot-dotnet_sdk (= 0.285.0) + dependabot-elm (= 0.285.0) + dependabot-git_submodules (= 0.285.0) + dependabot-github_actions (= 0.285.0) + dependabot-go_modules (= 0.285.0) + dependabot-gradle (= 0.285.0) + dependabot-hex (= 0.285.0) + dependabot-maven (= 0.285.0) + dependabot-npm_and_yarn (= 0.285.0) + dependabot-nuget (= 0.285.0) + dependabot-pub (= 0.285.0) + dependabot-python (= 0.285.0) + dependabot-swift (= 0.285.0) + dependabot-terraform (= 0.285.0) + dependabot-pub (0.285.0) + dependabot-common (= 0.285.0) + dependabot-python (0.285.0) + dependabot-common (= 0.285.0) + dependabot-swift (0.285.0) + dependabot-common (= 0.285.0) + dependabot-terraform (0.285.0) + dependabot-common (= 0.285.0) diff-lcs (1.5.1) docile (1.4.1) docker_registry2 (1.18.2) @@ -193,7 +196,7 @@ GEM mime-types (3.6.0) logger mime-types-data (~> 3.2015) - mime-types-data (3.2024.1001) + mime-types-data (3.2024.1105) mini_mime (1.1.5) mini_portile2 (2.8.7) multi_xml (0.7.1) @@ -271,10 +274,10 @@ GEM parallel_tests (4.7.2) parallel parseconfig (1.0.8) - parser (3.3.5.0) + parser (3.3.6.0) ast (~> 2.4.1) racc - psych (5.1.2) + psych (5.2.0) stringio public_suffix (6.0.1) racc (1.8.1) @@ -300,16 +303,16 @@ GEM rspec-expectations (3.13.3) diff-lcs (>= 1.2.0, < 2.0) rspec-support (~> 3.13.0) - rspec-its (1.3.1) - rspec-core (>= 3.0.0) - rspec-expectations (>= 3.0.0) + rspec-its (2.0.0) + rspec-core (>= 3.13.0) + rspec-expectations (>= 3.13.0) rspec-mocks (3.13.1) diff-lcs (>= 1.2.0, < 2.0) rspec-support (~> 3.13.0) rspec-sorbet (1.9.2) sorbet-runtime rspec-support (3.13.1) - rubocop (1.67.0) + rubocop (1.68.0) json (~> 2.3) language_server-protocol (>= 3.17.0) parallel (~> 1.10) @@ -319,13 +322,13 @@ GEM rubocop-ast (>= 1.32.2, < 2.0) ruby-progressbar (~> 1.7) unicode-display_width (>= 2.4.0, < 3.0) - rubocop-ast (1.32.3) + rubocop-ast (1.36.1) parser (>= 3.3.1.0) rubocop-capybara (2.21.0) rubocop (~> 1.41) rubocop-factory_bot (2.26.1) rubocop (~> 1.61) - rubocop-performance (1.22.1) + rubocop-performance (1.23.0) rubocop (>= 1.48.1, < 2.0) rubocop-ast (>= 1.31.1, < 2.0) rubocop-rspec (2.29.2) @@ -335,7 +338,7 @@ GEM rubocop-rspec_rails (~> 2.28) rubocop-rspec_rails (2.29.1) rubocop (~> 1.61) - rubocop-sorbet (0.8.6) + rubocop-sorbet (0.8.7) rubocop (>= 1) ruby-progressbar (1.13.0) ruby2_keywords (0.0.5) @@ -355,9 +358,9 @@ GEM simplecov_json_formatter (~> 0.1) simplecov-html (0.13.1) simplecov_json_formatter (0.1.4) - sorbet-runtime (0.5.11618) + sorbet-runtime (0.5.11645) stackprof (0.2.26) - stringio (3.1.1) + stringio (3.1.2) terminal-table (3.0.2) unicode-display_width (>= 1.1.1, < 3) toml-rb (3.0.1) @@ -373,7 +376,7 @@ GEM addressable (>= 2.8.0) crack (>= 0.3.2) hashdiff (>= 0.4.0, < 2.0.0) - webrick (1.8.2) + webrick (1.9.0) PLATFORMS aarch64-linux @@ -394,7 +397,7 @@ PLATFORMS DEPENDENCIES debug (~> 1.9.2) - dependabot-omnibus (~> 0.282.0) + dependabot-omnibus (~> 0.285.0) flamegraph (~> 0.9.5) gpgme (~> 2.0) http (~> 5.2) @@ -409,12 +412,12 @@ DEPENDENCIES opentelemetry-sdk (~> 1.5) rake (~> 13) rspec (~> 3.12) - rspec-its (~> 1.3) + rspec-its (~> 2.0) rspec-sorbet (~> 1.9.2) - rubocop (~> 1.67.0) - rubocop-performance (~> 1.22.1) + rubocop (~> 1.68.0) + rubocop-performance (~> 1.23.0) rubocop-rspec (~> 2.29.1) - rubocop-sorbet (~> 0.8.6) + rubocop-sorbet (~> 0.8.7) sentry-opentelemetry (~> 5.21) sentry-ruby (~> 5.17) simplecov (~> 0.22.0) diff --git a/updater/bin/update_script.rb b/updater/bin/update_script.rb index d69ec67a..01d99154 100644 --- a/updater/bin/update_script.rb +++ b/updater/bin/update_script.rb @@ -28,6 +28,7 @@ require "dependabot/cargo" require "dependabot/composer" require "dependabot/docker" +require "dependabot/dotnet_sdk" require "dependabot/elm" require "dependabot/git_submodules" require "dependabot/github_actions" @@ -131,6 +132,7 @@ # https://docs.github.com/en/github/administering-a-repository/configuration-options-for-dependency-updates#package-ecosystem # [Hash] PACKAGE_ECOSYSTEM_MAPPING = { + "dotnet-sdk" => "dotnet_sdk", "github-actions" => "github_actions", "gitsubmodule" => "submodules", "gomod" => "go_modules", diff --git a/updater/lib/dependabot/dependency_snapshot.rb b/updater/lib/dependabot/dependency_snapshot.rb index 02b03c4e..8c927107 100644 --- a/updater/lib/dependabot/dependency_snapshot.rb +++ b/updater/lib/dependabot/dependency_snapshot.rb @@ -67,9 +67,9 @@ def dependencies T.must(@dependencies[@current_directory]) end - sig { returns(T.nilable(Dependabot::PackageManagerBase)) } - def package_manager - @package_manager[@current_directory] + sig { returns(T.nilable(Dependabot::Ecosystem)) } + def ecosystem + @ecosystem[@current_directory] end sig { returns(T::Array[Dependabot::Notice]) } @@ -181,7 +181,7 @@ def initialize(job:, base_commit_sha:, dependency_files:) # rubocop:disable Metr @current_directory = T.let("", String) @dependencies = T.let({}, T::Hash[String, T::Array[Dependabot::Dependency]]) - @package_manager = T.let({}, T::Hash[String, T.nilable(Dependabot::PackageManagerBase)]) + @ecosystem = T.let({}, T::Hash[String, T.nilable(Dependabot::Ecosystem)]) @notices = T.let({}, T::Hash[String, T::Array[Dependabot::Notice]]) directories.each do |dir| @@ -241,12 +241,12 @@ def dependency_file_parser reject_external_code: job.reject_external_code?, options: job.experiments ) - # Add 'package_manager' to the dependency_snapshot to use it in operations - package_manager = parser.package_manager + # Add 'ecosystem' to the dependency_snapshot to use it in operations + ecosystem = parser.ecosystem # Raise an error if the package manager version is unsupported - package_manager&.raise_if_unsupported! + ecosystem&.raise_if_unsupported! - @package_manager[@current_directory] = package_manager + @ecosystem[@current_directory] = ecosystem # Log deprecation notices if the package manager is deprecated # and add them to the notices array @@ -255,7 +255,7 @@ def dependency_file_parser # add deprecation notices for the package manager add_deprecation_notice( notices: notices_for_current_directory, - package_manager: package_manager + package_manager: ecosystem&.package_manager ) @notices[@current_directory] = notices_for_current_directory diff --git a/updater/lib/dependabot/notices_helpers.rb b/updater/lib/dependabot/notices_helpers.rb index c8f52474..8c89d0da 100644 --- a/updater/lib/dependabot/notices_helpers.rb +++ b/updater/lib/dependabot/notices_helpers.rb @@ -3,7 +3,7 @@ require "sorbet-runtime" require "dependabot/notices" -require "dependabot/package_manager" +require "dependabot/ecosystem" # This module extracts helpers for notice generations that can be used # for showing notices in logs, pr messages and alert ui page. @@ -20,7 +20,7 @@ module NoticesHelpers sig do params( notices: T::Array[Dependabot::Notice], - package_manager: T.nilable(PackageManagerBase) + package_manager: T.nilable(Ecosystem::VersionManager) ) .void end @@ -58,11 +58,11 @@ def log_notice(notice) private - sig { params(package_manager: T.nilable(PackageManagerBase)).returns(T.nilable(Dependabot::Notice)) } + sig { params(package_manager: T.nilable(Ecosystem::VersionManager)).returns(T.nilable(Dependabot::Notice)) } def create_deprecation_notice(package_manager) return unless package_manager - return unless package_manager.is_a?(PackageManagerBase) + return unless package_manager.is_a?(Ecosystem::VersionManager) Notice.generate_pm_deprecation_notice( package_manager diff --git a/updater/lib/dependabot/setup.rb b/updater/lib/dependabot/setup.rb index 8ef255eb..24367425 100644 --- a/updater/lib/dependabot/setup.rb +++ b/updater/lib/dependabot/setup.rb @@ -30,6 +30,7 @@ terraform| elm| docker| + dotnet_sdk| git_submodules| github_actions| composer| @@ -60,6 +61,7 @@ require "dependabot/terraform" require "dependabot/elm" require "dependabot/docker" +require "dependabot/dotnet_sdk" require "dependabot/git_submodules" require "dependabot/github_actions" require "dependabot/composer" diff --git a/updater/lib/dependabot/updater/operations/refresh_security_update_pull_request.rb b/updater/lib/dependabot/updater/operations/refresh_security_update_pull_request.rb index 9acbffc8..ab94b2fa 100644 --- a/updater/lib/dependabot/updater/operations/refresh_security_update_pull_request.rb +++ b/updater/lib/dependabot/updater/operations/refresh_security_update_pull_request.rb @@ -132,7 +132,7 @@ def check_and_update_pull_request(dependencies) # Dependabot::Experiments.register(:lead_security_dependency, true) if Dependabot::Experiments.enabled?(:lead_security_dependency) - lead_dep_name = security_advisory_dependency + lead_dep_name = security_advisory_dependency.downcase # telemetry data collection Dependabot.logger.info( diff --git a/updater/lib/tinglesoftware/dependabot/job.rb b/updater/lib/tinglesoftware/dependabot/job.rb index b7ca9d8c..a1ecd335 100644 --- a/updater/lib/tinglesoftware/dependabot/job.rb +++ b/updater/lib/tinglesoftware/dependabot/job.rb @@ -190,6 +190,7 @@ def _package_manager # GitHub native implementation modifies some of the names in the config file # https://docs.github.com/en/github/administering-a-repository/configuration-options-for-dependency-updates#package-ecosystem { + "dotnet-sdk" => "dotnet_sdk", "github-actions" => "github_actions", "gitsubmodule" => "submodules", "gomod" => "go_modules", diff --git a/updater/lib/tinglesoftware/dependabot/setup.rb b/updater/lib/tinglesoftware/dependabot/setup.rb index ef2be58c..69ca4dd2 100644 --- a/updater/lib/tinglesoftware/dependabot/setup.rb +++ b/updater/lib/tinglesoftware/dependabot/setup.rb @@ -49,6 +49,7 @@ require "dependabot/terraform" require "dependabot/elm" require "dependabot/docker" +require "dependabot/dotnet_sdk" require "dependabot/git_submodules" require "dependabot/github_actions" require "dependabot/composer" diff --git a/updater/spec/support/dummy_pkg_helpers.rb b/updater/spec/support/dummy_pkg_helpers.rb index 2750b821..2ff6b3ae 100644 --- a/updater/spec/support/dummy_pkg_helpers.rb +++ b/updater/spec/support/dummy_pkg_helpers.rb @@ -1,7 +1,7 @@ # typed: false # frozen_string_literal: true -require "dependabot/package_manager" +require "dependabot/ecosystem" require "dependabot/dependency_file" # This module provides some shortcuts for working with our two mock RubyGems packages: @@ -63,22 +63,17 @@ def updated_bundler_files_hash(fixture: "bundler") updated_bundler_files(fixture: fixture).map(&:to_h) end - # Stub PackageManagerBase - class StubPackageManager < Dependabot::PackageManagerBase - def initialize(name:, version:, deprecated_versions: [], unsupported_versions: [], supported_versions: []) - @name = name - @version = version - @deprecated_versions = deprecated_versions - @unsupported_versions = unsupported_versions - @supported_versions = supported_versions + # Stub Ecosystem::VersionManager + class StubPackageManager < Dependabot::Ecosystem::VersionManager + def initialize(name:, version:, deprecated_versions: [], supported_versions: []) + super( + name, + Dependabot::Version.new(version), + deprecated_versions, + supported_versions + ) end - attr_reader :name - attr_reader :version - attr_reader :deprecated_versions - attr_reader :unsupported_versions - attr_reader :supported_versions - sig { override.returns(T::Boolean) } def deprecated? # If the version is unsupported, the unsupported error is getting raised separately.