PRCommentTask codebase from Tsuyoshi's repo (#20)
|
@ -9,6 +9,9 @@ on:
|
|||
- "CreatePrComment/**"
|
||||
workflow_dispatch:
|
||||
|
||||
env:
|
||||
WORKDIR: "CreatePrComment"
|
||||
|
||||
defaults:
|
||||
run:
|
||||
working-directory: "CreatePrComment"
|
||||
|
@ -23,7 +26,6 @@ jobs:
|
|||
- windows-latest
|
||||
node-version: [10, 14]
|
||||
runs-on: ${{ matrix.os }}
|
||||
|
||||
steps:
|
||||
- uses: actions/checkout@v2
|
||||
- name: "Use Node.js ${{ matrix.node-version }} on ${{ matrix.os }}"
|
||||
|
@ -31,6 +33,7 @@ jobs:
|
|||
with:
|
||||
node-version: ${{ matrix.node-version }}
|
||||
cache: npm
|
||||
cache-dependency-path: "${{ env.WORKDIR }}/package-lock.json"
|
||||
- run: npm ci
|
||||
- run: npm run build --if-present
|
||||
- run: npm run test --if-present
|
||||
|
|
|
@ -1,3 +1,5 @@
|
|||
.vscode
|
||||
|
||||
# Logs
|
||||
logs
|
||||
*.log
|
||||
|
|
|
@ -1,9 +1,9 @@
|
|||
# Microsoft Open Source Code of Conduct
|
||||
|
||||
This project has adopted the [Microsoft Open Source Code of Conduct](https://opensource.microsoft.com/codeofconduct/).
|
||||
|
||||
Resources:
|
||||
|
||||
- [Microsoft Open Source Code of Conduct](https://opensource.microsoft.com/codeofconduct/)
|
||||
- [Microsoft Code of Conduct FAQ](https://opensource.microsoft.com/codeofconduct/faq/)
|
||||
- Contact [opencode@microsoft.com](mailto:opencode@microsoft.com) with questions or concerns
|
||||
# Microsoft Open Source Code of Conduct
|
||||
|
||||
This project has adopted the [Microsoft Open Source Code of Conduct](https://opensource.microsoft.com/codeofconduct/).
|
||||
|
||||
Resources:
|
||||
|
||||
- [Microsoft Open Source Code of Conduct](https://opensource.microsoft.com/codeofconduct/)
|
||||
- [Microsoft Code of Conduct FAQ](https://opensource.microsoft.com/codeofconduct/faq/)
|
||||
- Contact [opencode@microsoft.com](mailto:opencode@microsoft.com) with questions or concerns
|
||||
|
|
|
@ -0,0 +1,11 @@
|
|||
node_modules
|
||||
*.js
|
||||
*.vsix
|
||||
*.map
|
||||
.vscode
|
||||
test-results.xml
|
||||
.nyc_output
|
||||
.coverage_output
|
||||
.bin
|
||||
.dist
|
||||
.taskkey
|
|
@ -0,0 +1,47 @@
|
|||
# Contribution
|
||||
|
||||
This article explains how to build an development environment.
|
||||
|
||||
## Prerequisite
|
||||
|
||||
- Node.js 14.0.x or later and NPM
|
||||
- [TFS Cross Platform Command Line Interface(tfx-cli)](https://github.com/microsoft/tfs-cli) 0.9.3 or latter
|
||||
- [Typescript](https://www.typescriptlang.org/docs/handbook/typescript-in-5-minutes.html) 4.4.4 or latter
|
||||
|
||||
## installation
|
||||
|
||||
Clone this repo. Then install the npm packages.
|
||||
|
||||
```shell
|
||||
npm install
|
||||
```
|
||||
|
||||
## npm commands
|
||||
|
||||
### Build
|
||||
|
||||
Compile the TypeScript files to the js file.
|
||||
|
||||
```shell
|
||||
npm run build
|
||||
```
|
||||
|
||||
### Pack
|
||||
|
||||
Compile and Pack the code to the task directory
|
||||
|
||||
```shell
|
||||
npm run pack
|
||||
```
|
||||
|
||||
### Create
|
||||
|
||||
Compile, Pack and Create an extension `vsix` file.
|
||||
|
||||
```shell
|
||||
npm run create
|
||||
```
|
||||
|
||||
### Test
|
||||
|
||||
Currently Not Supported, however, it is coming soon.
|
|
@ -0,0 +1,21 @@
|
|||
MIT License
|
||||
|
||||
Copyright (c) Microsoft Corporation
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
of this software and associated documentation files (the "Software"), to deal
|
||||
in the Software without restriction, including without limitation the rights
|
||||
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
copies of the Software, and to permit persons to whom the Software is
|
||||
furnished to do so, subject to the following conditions:
|
||||
|
||||
The above copyright notice and this permission notice shall be included in all
|
||||
copies or substantial portions of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
SOFTWARE.
|
|
@ -0,0 +1,21 @@
|
|||
# MIT License
|
||||
|
||||
Copyright (c) Microsoft Corporation
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
of this software and associated documentation files (the "Software"), to deal
|
||||
in the Software without restriction, including without limitation the rights
|
||||
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
copies of the Software, and to permit persons to whom the Software is
|
||||
furnished to do so, subject to the following conditions:
|
||||
|
||||
The above copyright notice and this permission notice shall be included in all
|
||||
copies or substantial portions of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
SOFTWARE.
|
|
@ -0,0 +1,43 @@
|
|||
# Create PR Comment Task
|
||||
|
||||
![Comment](https://raw.githubusercontent.com/microsoft/CSEDevOps/main/CreatePrComment/docs/images/Comment.png)
|
||||
|
||||
Create a Pull Request comment if a CI is triggered by Pull Request.
|
||||
|
||||
## How to use
|
||||
|
||||
### Configuration
|
||||
|
||||
Install this extension to your project. Find the CreatePRCommentTask.
|
||||
|
||||
![CreatePRCommentTask](https://raw.githubusercontent.com/microsoft/CSEDevOps/main/CreatePrComment/docs/images/CreatePRCommentTask.png)
|
||||
|
||||
### Details
|
||||
|
||||
![Task details](https://raw.githubusercontent.com/microsoft/CSEDevOps/main/CreatePrComment/docs/images/CommentTask.png)
|
||||
|
||||
| Name | Description |
|
||||
| ---------------- | ------------------------------------------------------------------------------------------------ |
|
||||
| Azure DevOps PAT | Select Azure DevOps Personal Access Token. or you can create new one |
|
||||
| Comment | If the pipeline is executed by Pull Request Validation, this task create a Pull Request Comment. |
|
||||
|
||||
On the Comment, you can use Variables. The variables will be substituted by the actual value. e.g. `$(CWI.Id)`.
|
||||
The comment becomes message body of your Pull Request Comment.
|
||||
|
||||
### Personal Access Token Service Connection
|
||||
|
||||
Put your Azure DevOps Personal Access Token in `PAT`. The PAT requires permission to write Code. For more detail, [Pull Request Thread Comments - Create](https://docs.microsoft.com/en-us/rest/api/azure/devops/git/pull-request-thread-comments/create?view=azure-devops-rest-6.1). `Connection name` is just a label of this service connection. `Server URL` is not used currently, however it might be good as memo which you use it for.
|
||||
|
||||
![ServiceConnection](https://raw.githubusercontent.com/microsoft/CSEDevOps/main/CreatePrComment/docs/images/ServiceConnection.png)
|
||||
|
||||
### Example
|
||||
|
||||
Sample of the Comment.
|
||||
|
||||
```text
|
||||
CredScan reports a <a href="https://dev.azure.com/csedevops/DevSecOps/_workitems/edit/$(CWI.Id)">Bug</a>. Please review it.
|
||||
```
|
||||
|
||||
## Contribution
|
||||
|
||||
For more details [here](https://github.com/microsoft/CSEDevOps/blob/main/CreatePrComment/CONTRIBUTION.md).
|
После Ширина: | Высота: | Размер: 126 KiB |
После Ширина: | Высота: | Размер: 79 KiB |
После Ширина: | Высота: | Размер: 33 KiB |
После Ширина: | Высота: | Размер: 26 KiB |
|
@ -0,0 +1,81 @@
|
|||
{
|
||||
"name": "create-pr-comment-task",
|
||||
"version": "1.0.0",
|
||||
"description": "Create a comment on a Pull Request",
|
||||
"main": "task.js",
|
||||
"scripts": {
|
||||
"clean": "rimraf ./.bin && rimraf ./.dist && rimraf ./*.vsix",
|
||||
"deps": "rimraf ./node_modules && npm install",
|
||||
"build": "rimraf ./.bin && tsc -p .",
|
||||
"start": "copyfiles -f ./task/CreatePRCommentTaskV1/task.json ./src && ts-node ./src/task.ts && rimraf ./src/task.json",
|
||||
"pack": "rimraf ./.dist && copyfiles vss-extension.json vss-extension-icon.png LICENSE.md README.md \"./task/**\" ./.dist && copyfiles -f package.json \"./.bin/*.js\" \"./.bin/**/*.js\" -e \"./.bin/tests/*\" ./.dist/task/CreatePRCommentTaskV0 && copyfiles -f package.json \"./.bin/*.js\" \"./.bin/**/*.js\" -e \"./.bin/tests/*\" ./.dist/task/CreatePRCommentTaskV1 && loop \"npm install --only=prod\" --cwd ./.dist/task",
|
||||
"create": "tfx extension create -r ./.dist",
|
||||
"createdev": "tfx extension create --rev-version --root ./.dist --publisher daporo-dev",
|
||||
"test": "tsc -p . && copyfiles -f ./task/CreatePRCommentTaskV1/task.json ./.bin/src && nyc mocha ./.bin/tests/L0.js",
|
||||
"report": "tsc -p . && mocha ./.bin/tests/L0.js --reporter mocha-junit-reporter && nyc report",
|
||||
"lint": "ts-standard",
|
||||
"lintfix": "ts-standard --fix"
|
||||
},
|
||||
"nyc": {
|
||||
"extension": [
|
||||
".ts"
|
||||
],
|
||||
"include": [
|
||||
"src"
|
||||
],
|
||||
"reporter": [
|
||||
"text",
|
||||
"cobertura",
|
||||
"html"
|
||||
],
|
||||
"report-dir": "./.coverage_output/coverage",
|
||||
"all": "true",
|
||||
"check-coverage": true,
|
||||
"statements": 70,
|
||||
"functions": 70,
|
||||
"branches": 70,
|
||||
"lines": 70
|
||||
},
|
||||
"repository": {
|
||||
"type": "git",
|
||||
"url": "git+https://github.com/microsoft/CSEDevOps.git"
|
||||
},
|
||||
"keywords": [
|
||||
"Pull Request",
|
||||
"PR",
|
||||
"PR Comment",
|
||||
"Utility task",
|
||||
"Utility",
|
||||
"Azure Pipelines"
|
||||
],
|
||||
"author": "CSE-DevOps",
|
||||
"license": "MIT",
|
||||
"bugs": {
|
||||
"url": "https://github.com/microsoft/CSEDevOps/issues?q=is:issue+label:CreatePrComment"
|
||||
},
|
||||
"homepage": "https://github.com/microsoft/CSEDevOps/tree/main/CreatePrComment",
|
||||
"dependencies": {
|
||||
"azure-devops-node-api": "^11.0.1",
|
||||
"azure-pipelines-task-lib": "^3.1.10"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@types/chai": "^4.2.22",
|
||||
"@types/mocha": "^9.0.0",
|
||||
"@types/node": "^16.11.1",
|
||||
"@types/q": "^1.5.5",
|
||||
"@types/sinon": "^10.0.4",
|
||||
"chai": "^4.3.4",
|
||||
"copyfiles": "^2.4.1",
|
||||
"loop": "^3.3.6",
|
||||
"mocha": "^9.1.3",
|
||||
"mocha-junit-reporter": "^2.0.2",
|
||||
"nyc": "^15.1.0",
|
||||
"rewiremock": "^3.14.3",
|
||||
"rimraf": "^3.0.2",
|
||||
"sinon": "^11.1.2",
|
||||
"source-map-support": "^0.5.20",
|
||||
"tfx-cli": "^0.9.3",
|
||||
"ts-node": "^10.3.0",
|
||||
"typescript": "^4.4.4"
|
||||
}
|
||||
}
|
|
@ -0,0 +1,101 @@
|
|||
import * as tl from 'azure-pipelines-task-lib'
|
||||
import * as wa from 'azure-devops-node-api/WebApi'
|
||||
import * as GitInterfaces from 'azure-devops-node-api/interfaces/GitInterfaces'
|
||||
import VariableResolver from './variableresolver'
|
||||
import { IGitApi, GitApi } from 'azure-devops-node-api/GitApi'
|
||||
import path from 'path'
|
||||
import { IRequestHandler } from 'azure-devops-node-api/interfaces/common/VsoBaseInterfaces'
|
||||
|
||||
export interface IClientFactory {
|
||||
create: () => Promise<IGitApi>
|
||||
}
|
||||
|
||||
class ClientFactory implements IClientFactory {
|
||||
public async create (): Promise<IGitApi> {
|
||||
const authType = tl.getInput('AuthType') || 'patService' // default for V0
|
||||
let credHandler: IRequestHandler
|
||||
|
||||
switch (authType) {
|
||||
case 'patService': {
|
||||
const patService = tl.getInput('AzureDevOpsService')!
|
||||
const pat = tl.getEndpointAuthorizationParameter(patService, 'pat', false)!
|
||||
credHandler = wa.getPersonalAccessTokenHandler(pat)
|
||||
break
|
||||
}
|
||||
case 'pat': {
|
||||
const pat = tl.getInput('AzureDevOpsPat')!
|
||||
credHandler = wa.getPersonalAccessTokenHandler(pat)
|
||||
break
|
||||
}
|
||||
case 'system': {
|
||||
const token = tl.getVariable('System.AccessToken')!
|
||||
credHandler = wa.getBearerHandler(token)
|
||||
break
|
||||
}
|
||||
default:
|
||||
throw 'Unknown authentication type'
|
||||
}
|
||||
|
||||
const connection = new wa.WebApi(tl.getVariable('System.TeamFoundationCollectionUri')!, credHandler)
|
||||
return await connection.getGitApi()
|
||||
}
|
||||
}
|
||||
|
||||
export class CreatePRCommentTask {
|
||||
factory: IClientFactory
|
||||
|
||||
constructor (clientFactory: IClientFactory) {
|
||||
this.factory = clientFactory
|
||||
}
|
||||
|
||||
public async run (): Promise<void> {
|
||||
try {
|
||||
tl.setResourcePath(path.join(__dirname, 'task.json'), true)
|
||||
const commentOriginal = tl.getInput('Comment', true)!
|
||||
tl.debug('commentOriginal:' + commentOriginal)
|
||||
const comment = VariableResolver.resolveVariables(commentOriginal)
|
||||
tl.debug('comment:' + comment)
|
||||
|
||||
const client = await this.factory.create()
|
||||
|
||||
const commentObject = <GitInterfaces.Comment> {
|
||||
content: comment,
|
||||
commentType: GitInterfaces.CommentType.System
|
||||
}
|
||||
const thread: GitInterfaces.GitPullRequestCommentThread = <GitInterfaces.GitPullRequestCommentThread> {
|
||||
comments: [
|
||||
commentObject
|
||||
],
|
||||
status: GitInterfaces.CommentThreadStatus.ByDesign
|
||||
}
|
||||
const repositoryId = tl.getVariable('Build.Repository.ID')!
|
||||
const pullRequestIdString = tl.getVariable('System.PullRequest.PullRequestId')
|
||||
|
||||
if (pullRequestIdString === undefined) {
|
||||
// If the build is not pull request, do nothing.
|
||||
return
|
||||
}
|
||||
|
||||
const pullRequestId: number = pullRequestIdString ? parseInt(pullRequestIdString) : 0
|
||||
|
||||
const currentThreads = await client.getThreads(repositoryId, pullRequestId)
|
||||
for (var currentThread of currentThreads) {
|
||||
if (currentThread.comments !== null && currentThread.comments !== undefined) {
|
||||
for (var threadComment of currentThread.comments) {
|
||||
if (threadComment.content === comment) {
|
||||
return // If the same comment is already there.
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (pullRequestId != 0) {
|
||||
const createdThread = await client.createThread(thread, repositoryId, pullRequestId)
|
||||
}
|
||||
} catch (e) {
|
||||
throw new Error(tl.loc('FailToCreateComment', e))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
new CreatePRCommentTask(new ClientFactory()).run()
|
|
@ -0,0 +1,30 @@
|
|||
import { getVariable, debug } from 'azure-pipelines-task-lib'
|
||||
|
||||
export default class VariableResolver {
|
||||
private static readonly variableRegExp = /\$\(([^)]+)\)/g
|
||||
|
||||
public static resolveVariables (origValue: string): string {
|
||||
let newValue = origValue
|
||||
|
||||
let match: RegExpExecArray | null
|
||||
while ((match = this.variableRegExp.exec(newValue)) !== null) {
|
||||
const variableValue = getVariable(match[1])
|
||||
if (variableValue && variableValue !== '') {
|
||||
newValue = this.replaceAll(newValue, match[0], variableValue)
|
||||
this.variableRegExp.lastIndex = 0
|
||||
} else {
|
||||
debug('Variable \'' + match[1] + '\' not defined.')
|
||||
}
|
||||
}
|
||||
|
||||
return newValue
|
||||
}
|
||||
|
||||
private static escapeRegExp (expression: string): string {
|
||||
return expression.replace(/([.*+?^=!:${}()|\[\]\/\\])/g, '\\$1')
|
||||
}
|
||||
|
||||
private static replaceAll (origValue: string, searchValue: string, replaceValue: string) {
|
||||
return origValue.replace(new RegExp(this.escapeRegExp(searchValue), 'g'), replaceValue)
|
||||
}
|
||||
}
|
После Ширина: | Высота: | Размер: 501 B |
|
@ -0,0 +1,40 @@
|
|||
{
|
||||
"id": "0fef745c-60e7-4f36-8541-e4d9ce6729b1",
|
||||
"name": "CreatePRCommentTask",
|
||||
"friendlyName": "Create PR comment task",
|
||||
"description": "Create a Pull Request comment from pipeline",
|
||||
"helpMarkDown": "[CreatePrComment](https://github.com/microsoft/CSEDevOps/tree/main/CreatePrComment)",
|
||||
"category": "Utility",
|
||||
"author": "CSE-DevOps",
|
||||
"preview": true,
|
||||
"version": {
|
||||
"Major": 0,
|
||||
"Minor": 1,
|
||||
"Patch": 15
|
||||
},
|
||||
"instanceNameFormat": "Create PR Comment",
|
||||
"inputs": [
|
||||
{
|
||||
"name": "AzureDevOpsService",
|
||||
"type": "connectedService:azuredevops",
|
||||
"label": "Azure DevOps PAT",
|
||||
"required": true,
|
||||
"helpMarkDown": "Select the Azure DevOps PAT"
|
||||
},
|
||||
{
|
||||
"name": "Comment",
|
||||
"type": "multiLine",
|
||||
"label": "Comment",
|
||||
"required": true,
|
||||
"helpMarkDown": "Comment which is created as an Pull Request comment"
|
||||
}
|
||||
],
|
||||
"execution": {
|
||||
"Node10": {
|
||||
"target": "task.js"
|
||||
}
|
||||
},
|
||||
"messages": {
|
||||
"FailToCreateComment": "Failed to create a comment. For more details: %s"
|
||||
}
|
||||
}
|
После Ширина: | Высота: | Размер: 501 B |
|
@ -0,0 +1,56 @@
|
|||
{
|
||||
"id": "0fef745c-60e7-4f36-8541-e4d9ce6729b1",
|
||||
"name": "CreatePRCommentTask",
|
||||
"friendlyName": "Create PR comment task",
|
||||
"description": "Create a Pull Request comment from pipeline",
|
||||
"helpMarkDown": "[CreatePrComment](https://github.com/microsoft/CSEDevOps/tree/main/CreatePrComment)",
|
||||
"category": "Utility",
|
||||
"author": "CSE-DevOps",
|
||||
"preview": true,
|
||||
"version": {
|
||||
"Major": 1,
|
||||
"Minor": 0,
|
||||
"Patch": 0
|
||||
},
|
||||
"instanceNameFormat": "Create PR Comment",
|
||||
"inputs": [
|
||||
{
|
||||
"name": "AuthType",
|
||||
"label": "Authentication type",
|
||||
"type": "pickList",
|
||||
"defaultValue": "system",
|
||||
"required": true,
|
||||
"helpMarkDown": "Use personal or system authorization token",
|
||||
"properties": {
|
||||
"editableOptions": false
|
||||
},
|
||||
"options": {
|
||||
"system": "System Access Token",
|
||||
"pat": "Personal Access Token"
|
||||
}
|
||||
},
|
||||
{
|
||||
"name": "AzureDevOpsPat",
|
||||
"type": "string",
|
||||
"label": "Azure DevOps PAT",
|
||||
"required": true,
|
||||
"helpMarkDown": "Provide the Azure DevOps PAT",
|
||||
"visibleRule": "AuthType = pat"
|
||||
},
|
||||
{
|
||||
"name": "Comment",
|
||||
"type": "multiLine",
|
||||
"label": "Comment",
|
||||
"required": true,
|
||||
"helpMarkDown": "Comment which is created as an Pull Request comment"
|
||||
}
|
||||
],
|
||||
"execution": {
|
||||
"Node10": {
|
||||
"target": "task.js"
|
||||
}
|
||||
},
|
||||
"messages": {
|
||||
"FailToCreateComment": "Failed to create a comment. For more details: %s"
|
||||
}
|
||||
}
|
|
@ -0,0 +1,130 @@
|
|||
import * as assert from 'assert'
|
||||
import * as sinon from 'sinon'
|
||||
import rewiremock from 'rewiremock'
|
||||
import { should as Should, expect } from 'chai'
|
||||
import { IGitApi, GitApi } from 'azure-devops-node-api/GitApi'
|
||||
import * as GitInterfaces from 'azure-devops-node-api/interfaces/GitInterfaces'
|
||||
|
||||
var should = Should()
|
||||
|
||||
const debugMessages: string[] = []
|
||||
let variables: {[key: string]: string} = {}
|
||||
const inputs: {[key: string]: string} = {}
|
||||
|
||||
rewiremock('azure-pipelines-task-lib')
|
||||
.with({
|
||||
debug: sinon.stub().callsFake(m => debugMessages.push(m)),
|
||||
getInput: sinon.stub().callsFake(i => { return inputs[i] || null }),
|
||||
getVariable: sinon.stub().callsFake(v => { return variables[v] || null }),
|
||||
getEndpointAuthorizationParameter: () => 'fooPAT',
|
||||
loc: sinon.stub().returnsArg(0)
|
||||
})
|
||||
|
||||
rewiremock.enable()
|
||||
|
||||
import { IClientFactory, CreatePRCommentTask } from '../src/task'
|
||||
|
||||
class ClientFactoryMock implements IClientFactory {
|
||||
called: boolean = false
|
||||
createdCommentThread: GitInterfaces.GitPullRequestCommentThread = {}
|
||||
createdRepositoryId: string = ''
|
||||
createdPullRequestId: number = 0
|
||||
expectedThreads: GitInterfaces.GitPullRequestCommentThread[] = []
|
||||
|
||||
public async create (): Promise<IGitApi> {
|
||||
const gitApiStub = <IGitApi> {
|
||||
getThreads: async (repositoryId: string, pullRequestId: number, project?: string, iteration?: number, baseIteration?: number): Promise<GitInterfaces.GitPullRequestCommentThread[]> => {
|
||||
return await new Promise<GitInterfaces.GitPullRequestCommentThread[]>(
|
||||
(resolve: (value: GitInterfaces.GitPullRequestCommentThread[]) => void, reject: (reason?: any) => void) => {
|
||||
resolve(this.expectedThreads)
|
||||
})
|
||||
},
|
||||
createThread: async (commentThread: GitInterfaces.GitPullRequestCommentThread, repositoryId: string, pullRequestId: number, project?: string): Promise<GitInterfaces.GitPullRequestCommentThread> => {
|
||||
return await new Promise<GitInterfaces.GitPullRequestCommentThread>(
|
||||
(resolve: (value: GitInterfaces.GitPullRequestCommentThread) => void, reject: (reason?: any) => void) => {
|
||||
this.createdCommentThread = commentThread
|
||||
this.createdRepositoryId = repositoryId
|
||||
this.createdPullRequestId = pullRequestId
|
||||
this.called = true
|
||||
resolve(undefined!) // currently not used.
|
||||
})
|
||||
}
|
||||
}
|
||||
return gitApiStub
|
||||
}
|
||||
}
|
||||
|
||||
describe('CreatePRCommentTaskV0 Tests', function () {
|
||||
it('run all inputs function', async () => {
|
||||
const factoryMock: IClientFactory = new ClientFactoryMock()
|
||||
variables['Build.Repository.ID'] = '3'
|
||||
variables['System.PullRequest.PullRequestId'] = '4'
|
||||
inputs.AzureDevOpsService = 'devopspat'
|
||||
inputs.Comment = 'foo'
|
||||
|
||||
const commentTask = new CreatePRCommentTask(factoryMock)
|
||||
await commentTask.run();
|
||||
|
||||
(factoryMock as ClientFactoryMock).called.should.be.true
|
||||
})
|
||||
|
||||
it('run substitution', async () => {
|
||||
const factoryMock: IClientFactory = new ClientFactoryMock()
|
||||
variables['Build.Repository.ID'] = '3'
|
||||
variables['System.PullRequest.PullRequestId'] = '4'
|
||||
variables.Bar = 'bar'
|
||||
inputs.AzureDevOpsService = 'devopspat'
|
||||
inputs.Comment = 'foo, $(Bar)'
|
||||
|
||||
const commentTask = new CreatePRCommentTask(factoryMock)
|
||||
await commentTask.run()
|
||||
const comments = (factoryMock as ClientFactoryMock).createdCommentThread.comments
|
||||
if (comments !== undefined) {
|
||||
const content = comments[0].content
|
||||
if (content !== undefined) {
|
||||
content.should.be.equal('foo, bar')
|
||||
} else {
|
||||
assert.fail('content is undefined')
|
||||
}
|
||||
} else {
|
||||
assert.fail('comments is undefined')
|
||||
}
|
||||
})
|
||||
|
||||
it('ignored if it is non-pullrequest pipeline', async () => {
|
||||
const factoryMock: IClientFactory = new ClientFactoryMock()
|
||||
variables = {}
|
||||
variables['Build.Repository.ID'] = '3'
|
||||
inputs.AzureDevOpsService = 'devopspat'
|
||||
inputs.Comment = 'foo'
|
||||
|
||||
const commentTask = new CreatePRCommentTask(factoryMock)
|
||||
await commentTask.run();
|
||||
|
||||
(factoryMock as ClientFactoryMock).called.should.be.false
|
||||
})
|
||||
|
||||
it('suppress the comment if there is already created', async () => {
|
||||
const factoryMock: IClientFactory = new ClientFactoryMock()
|
||||
variables['Build.Repository.ID'] = '3'
|
||||
variables['System.PullRequest.PullRequestId'] = '4'
|
||||
inputs.AzureDevOpsService = 'devopspat'
|
||||
inputs.Comment = 'foo'
|
||||
const commentObject = <GitInterfaces.Comment> {
|
||||
content: 'foo'
|
||||
}
|
||||
const thread: GitInterfaces.GitPullRequestCommentThread = <GitInterfaces.GitPullRequestCommentThread> {
|
||||
comments: [
|
||||
commentObject
|
||||
]
|
||||
};
|
||||
(factoryMock as ClientFactoryMock).expectedThreads = [thread]
|
||||
|
||||
const commentTask = new CreatePRCommentTask(factoryMock)
|
||||
await commentTask.run();
|
||||
|
||||
(factoryMock as ClientFactoryMock).called.should.be.false
|
||||
})
|
||||
})
|
||||
|
||||
rewiremock.disable()
|
|
@ -0,0 +1,60 @@
|
|||
{
|
||||
"compilerOptions": {
|
||||
/* Basic Options */
|
||||
// "incremental": true, /* Enable incremental compilation */
|
||||
"target": "es6", /* Specify ECMAScript target version: 'ES3' (default), 'ES5', 'ES2015', 'ES2016', 'ES2017', 'ES2018', 'ES2019' or 'ESNEXT'. */
|
||||
"module": "commonjs", /* Specify module code generation: 'none', 'commonjs', 'amd', 'system', 'umd', 'es2015', or 'ESNext'. */
|
||||
// "lib": [], /* Specify library files to be included in the compilation. */
|
||||
// "allowJs": true, /* Allow javascript files to be compiled. */
|
||||
// "checkJs": true, /* Report errors in .js files. */
|
||||
// "jsx": "preserve", /* Specify JSX code generation: 'preserve', 'react-native', or 'react'. */
|
||||
// "declaration": true, /* Generates corresponding '.d.ts' file. */
|
||||
// "declarationMap": true, /* Generates a sourcemap for each corresponding '.d.ts' file. */
|
||||
"sourceMap": false, /* Generates corresponding '.map' file. */
|
||||
// "outFile": "./", /* Concatenate and emit output to single file. */
|
||||
"outDir": ".bin/", /* Redirect output structure to the directory. */
|
||||
// "rootDir": "./", /* Specify the root directory of input files. Use to control the output directory structure with --outDir. */
|
||||
// "composite": true, /* Enable project compilation */
|
||||
// "tsBuildInfoFile": "./", /* Specify file to store incremental compilation information */
|
||||
// "removeComments": true, /* Do not emit comments to output. */
|
||||
// "noEmit": true, /* Do not emit outputs. */
|
||||
// "importHelpers": true, /* Import emit helpers from 'tslib'. */
|
||||
// "downlevelIteration": true, /* Provide full support for iterables in 'for-of', spread, and destructuring when targeting 'ES5' or 'ES3'. */
|
||||
// "isolatedModules": true, /* Transpile each file as a separate module (similar to 'ts.transpileModule'). */
|
||||
/* Strict Type-Checking Options */
|
||||
"strict": true, /* Enable all strict type-checking options. */
|
||||
// "noImplicitAny": true, /* Raise error on expressions and declarations with an implied 'any' type. */
|
||||
// "strictNullChecks": true, /* Enable strict null checks. */
|
||||
// "strictFunctionTypes": true, /* Enable strict checking of function types. */
|
||||
// "strictBindCallApply": true, /* Enable strict 'bind', 'call', and 'apply' methods on functions. */
|
||||
// "strictPropertyInitialization": true, /* Enable strict checking of property initialization in classes. */
|
||||
// "noImplicitThis": true, /* Raise error on 'this' expressions with an implied 'any' type. */
|
||||
// "alwaysStrict": true, /* Parse in strict mode and emit "use strict" for each source file. */
|
||||
/* Additional Checks */
|
||||
// "noUnusedLocals": true, /* Report errors on unused locals. */
|
||||
// "noUnusedParameters": true, /* Report errors on unused parameters. */
|
||||
// "noImplicitReturns": true, /* Report error when not all code paths in function return a value. */
|
||||
// "noFallthroughCasesInSwitch": true, /* Report errors for fallthrough cases in switch statement. */
|
||||
/* Module Resolution Options */
|
||||
// "moduleResolution": "node", /* Specify module resolution strategy: 'node' (Node.js) or 'classic' (TypeScript pre-1.6). */
|
||||
// "baseUrl": "./", /* Base directory to resolve non-absolute module names. */
|
||||
// "paths": {}, /* A series of entries which re-map imports to lookup locations relative to the 'baseUrl'. */
|
||||
// "rootDirs": [], /* List of root folders whose combined content represents the structure of the project at runtime. */
|
||||
// "typeRoots": [], /* List of folders to include type definitions from. */
|
||||
// "types": [], /* Type declaration files to be included in compilation. */
|
||||
// "allowSyntheticDefaultImports": true, /* Allow default imports from modules with no default export. This does not affect code emit, just typechecking. */
|
||||
"esModuleInterop": true, /* Enables emit interoperability between CommonJS and ES Modules via creation of namespace objects for all imports. Implies 'allowSyntheticDefaultImports'. */
|
||||
// "preserveSymlinks": true, /* Do not resolve the real path of symlinks. */
|
||||
// "allowUmdGlobalAccess": true, /* Allow accessing UMD globals from modules. */
|
||||
/* Source Map Options */
|
||||
// "sourceRoot": "", /* Specify the location where debugger should locate TypeScript files instead of source locations. */
|
||||
// "mapRoot": "", /* Specify the location where debugger should locate map files instead of generated locations. */
|
||||
// "inlineSourceMap": true, /* Emit a single file with source maps instead of having a separate file. */
|
||||
// "inlineSources": true, /* Emit the source alongside the sourcemaps within a single file; requires '--inlineSourceMap' or '--sourceMap' to be set. */
|
||||
/* Experimental Options */
|
||||
// "experimentalDecorators": true, /* Enables experimental support for ES7 decorators. */
|
||||
// "emitDecoratorMetadata": true, /* Enables experimental support for emitting type metadata for decorators. */
|
||||
"skipLibCheck": true,
|
||||
"forceConsistentCasingInFileNames": true
|
||||
}
|
||||
}
|
После Ширина: | Высота: | Размер: 850 B |
|
@ -0,0 +1,119 @@
|
|||
{
|
||||
"manifestVersion": 1,
|
||||
"id": "create-pr-comment-task",
|
||||
"name": "Create Pull Request Comment",
|
||||
"publisher": "CSE-DevOps",
|
||||
"version": "0.1.0",
|
||||
"galleryFlags": [
|
||||
"Public",
|
||||
"Preview"
|
||||
],
|
||||
"targets": [
|
||||
{
|
||||
"id": "Microsoft.VisualStudio.Services"
|
||||
}
|
||||
],
|
||||
"description": "Create Pull Request Comment",
|
||||
"categories": [
|
||||
"Azure Pipelines"
|
||||
],
|
||||
"tags": [
|
||||
"Pull Request",
|
||||
"PR",
|
||||
"PR Comment",
|
||||
"Utility task",
|
||||
"Utility",
|
||||
"Azure Pipelines"
|
||||
],
|
||||
"content": {
|
||||
"details": {
|
||||
"path": "README.md"
|
||||
},
|
||||
"license": {
|
||||
"path": "LICENSE.md"
|
||||
}
|
||||
},
|
||||
"icons": {
|
||||
"default": "vss-extension-icon.png"
|
||||
},
|
||||
"files": [
|
||||
{
|
||||
"path": "task"
|
||||
}
|
||||
],
|
||||
"links": {
|
||||
"home": {
|
||||
"uri": "https://github.com/microsoft/CSEDevOps/tree/main/CreatePrComment"
|
||||
},
|
||||
"getstarted": {
|
||||
"uri": "https://github.com/microsoft/CSEDevOps/blob/main/CreatePrComment/README.md"
|
||||
},
|
||||
"learn": {
|
||||
"uri": "https://github.com/microsoft/CSEDevOps/blob/main/CreatePrComment/README.md"
|
||||
},
|
||||
"support": {
|
||||
"uri": "https://github.com/microsoft/CSEDevOps/discussions/categories/createprcomment"
|
||||
},
|
||||
"repository": {
|
||||
"uri": "https://github.com/microsoft/CSEDevOps/tree/main/CreatePrComment"
|
||||
},
|
||||
"issues": {
|
||||
"uri": "https://github.com/microsoft/CSEDevOps/issues?q=is:issue+label:CreatePrComment"
|
||||
},
|
||||
"license": {
|
||||
"uri": "https://github.com/microsoft/CSEDevOps/blob/main/CreatePrComment/LICENSE.md"
|
||||
}
|
||||
},
|
||||
"repository": {
|
||||
"type": "git",
|
||||
"uri": "https://github.com/microsoft/CSEDevOps"
|
||||
},
|
||||
"contributions": [
|
||||
{
|
||||
"id": "service-endpoint",
|
||||
"description": "Service Endpoint type for Azure DevOps PAT",
|
||||
"type": "ms.vss-endpoint.service-endpoint-type",
|
||||
"targets": [
|
||||
"ms.vss-endpoint.endpoint-types"
|
||||
],
|
||||
"properties": {
|
||||
"name": "azuredevops",
|
||||
"displayName": "CreatePRCommentTaskV0 Azure DevOps PAT",
|
||||
"config": {
|
||||
"type": "string",
|
||||
"description": "Put your Personal Access Token of the Azure DevOps",
|
||||
"required": true
|
||||
},
|
||||
"authenticationSchemes": [
|
||||
{
|
||||
"type": "ms.vss-endpoint.endpoint-auth-scheme-none",
|
||||
"inputDescriptors": [
|
||||
{
|
||||
"id": "pat",
|
||||
"name": "PAT",
|
||||
"description": "Azure DevOps Personal Access Token here.",
|
||||
"inputMode": "passwordbox",
|
||||
"isConfidential": true,
|
||||
"validation": {
|
||||
"isRequired": true,
|
||||
"dataType": "string"
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
],
|
||||
"helpMarkDown": "Create a Pull Request Comment."
|
||||
}
|
||||
},
|
||||
{
|
||||
"id": "custom-build-release-task",
|
||||
"type": "ms.vss-distributed-task.task",
|
||||
"targets": [
|
||||
"ms.vss-distributed-task.tasks"
|
||||
],
|
||||
"properties": {
|
||||
"name": "task"
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
42
LICENSE
|
@ -1,21 +1,21 @@
|
|||
MIT License
|
||||
|
||||
Copyright (c) Microsoft Corporation.
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
of this software and associated documentation files (the "Software"), to deal
|
||||
in the Software without restriction, including without limitation the rights
|
||||
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
copies of the Software, and to permit persons to whom the Software is
|
||||
furnished to do so, subject to the following conditions:
|
||||
|
||||
The above copyright notice and this permission notice shall be included in all
|
||||
copies or substantial portions of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
SOFTWARE
|
||||
MIT License
|
||||
|
||||
Copyright (c) Microsoft Corporation
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
of this software and associated documentation files (the "Software"), to deal
|
||||
in the Software without restriction, including without limitation the rights
|
||||
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
copies of the Software, and to permit persons to whom the Software is
|
||||
furnished to do so, subject to the following conditions:
|
||||
|
||||
The above copyright notice and this permission notice shall be included in all
|
||||
copies or substantial portions of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
SOFTWARE
|
||||
|
|
22
README.md
|
@ -1,11 +1,11 @@
|
|||
# CSEDevOps Extensions
|
||||
|
||||
## Available Extensions
|
||||
|
||||
- [CommentPRTask](./CommentPRTask)
|
||||
- [OWASP/ZAP on Azure](./Zap)
|
||||
- [PipelineRunnerExtension](./PipelineRunnerExtension)
|
||||
|
||||
## Getting Help
|
||||
|
||||
Please see the guidance provided in the documentation for each perspective extension linked above as well as the Marketplace. Please submit issues with a descriptions of problems you encounter while using these extension.
|
||||
# CSE DevOps Extensions
|
||||
|
||||
## Available Extensions
|
||||
|
||||
- [Create Pull Request Comment](./CreatePrComment)
|
||||
- [OWASP ZAP Scanner](./Zap)
|
||||
- [Configurable Pipeline Runner](./PipelineRunnerExtension)
|
||||
|
||||
## Getting Help
|
||||
|
||||
Please see the guidance provided in the documentation for each perspective extension linked above as well as the Marketplace. Please submit issues with a descriptions of problems you encounter while using these extension.
|
||||
|
|
82
SECURITY.md
|
@ -1,41 +1,41 @@
|
|||
<!-- BEGIN MICROSOFT SECURITY.MD V0.0.2 BLOCK -->
|
||||
|
||||
# Security
|
||||
|
||||
Microsoft takes the security of our software products and services seriously, which includes all source code repositories managed through our GitHub organizations, which include [Microsoft](https://github.com/Microsoft), [Azure](https://github.com/Azure), [DotNet](https://github.com/dotnet), [AspNet](https://github.com/aspnet), [Xamarin](https://github.com/xamarin), and [many more](https://opensource.microsoft.com/).
|
||||
|
||||
If you believe you have found a security vulnerability in any Microsoft-owned repository that meets Microsoft's [definition](https://docs.microsoft.com/en-us/previous-versions/tn-archive/cc751383(v=technet.10)) of a security vulnerability, please report it to us as described below.
|
||||
|
||||
## Reporting Security Issues
|
||||
|
||||
**Please do not report security vulnerabilities through public GitHub issues.**
|
||||
|
||||
Instead, please report them to the Microsoft Security Response Center (MSRC) at [https://msrc.microsoft.com/create-report](https://msrc.microsoft.com/create-report).
|
||||
|
||||
If you prefer to submit without logging in, send email to [secure@microsoft.com](mailto:secure@microsoft.com). If possible, encrypt your message with our PGP key; please download it from the the [Microsoft Security Response Center PGP Key page](https://www.microsoft.com/en-us/msrc/pgp-key-msrc).
|
||||
|
||||
You should receive a response within 24 hours. If for some reason you do not, please follow up via email to ensure we received your original message. Additional information can be found at [microsoft.com/msrc](https://www.microsoft.com/msrc).
|
||||
|
||||
Please include the requested information listed below (as much as you can provide) to help us better understand the nature and scope of the possible issue:
|
||||
|
||||
* Type of issue (e.g. buffer overflow, SQL injection, cross-site scripting, etc.)
|
||||
* Full paths of source file(s) related to the manifestation of the issue
|
||||
* The location of the affected source code (tag/branch/commit or direct URL)
|
||||
* Any special configuration required to reproduce the issue
|
||||
* Step-by-step instructions to reproduce the issue
|
||||
* Proof-of-concept or exploit code (if possible)
|
||||
* Impact of the issue, including how an attacker might exploit the issue
|
||||
|
||||
This information will help us triage your report more quickly.
|
||||
|
||||
If you are reporting for a bug bounty, more complete reports can contribute to a higher bounty award. Please visit our [Microsoft Bug Bounty Program](https://microsoft.com/msrc/bounty) page for more details about our active programs.
|
||||
|
||||
## Preferred Languages
|
||||
|
||||
We prefer all communications to be in English.
|
||||
|
||||
## Policy
|
||||
|
||||
Microsoft follows the principle of [Coordinated Vulnerability Disclosure](https://www.microsoft.com/en-us/msrc/cvd).
|
||||
|
||||
<!-- END MICROSOFT SECURITY.MD BLOCK -->
|
||||
<!-- BEGIN MICROSOFT SECURITY.MD V0.0.2 BLOCK -->
|
||||
|
||||
# Security
|
||||
|
||||
Microsoft takes the security of our software products and services seriously, which includes all source code repositories managed through our GitHub organizations, which include [Microsoft](https://github.com/Microsoft), [Azure](https://github.com/Azure), [DotNet](https://github.com/dotnet), [AspNet](https://github.com/aspnet), [Xamarin](https://github.com/xamarin), and [many more](https://opensource.microsoft.com/).
|
||||
|
||||
If you believe you have found a security vulnerability in any Microsoft-owned repository that meets Microsoft's [definition](<https://docs.microsoft.com/en-us/previous-versions/tn-archive/cc751383(v=technet.10)>) of a security vulnerability, please report it to us as described below.
|
||||
|
||||
## Reporting Security Issues
|
||||
|
||||
**Please do not report security vulnerabilities through public GitHub issues.**
|
||||
|
||||
Instead, please report them to the Microsoft Security Response Center (MSRC) at [https://msrc.microsoft.com/create-report](https://msrc.microsoft.com/create-report).
|
||||
|
||||
If you prefer to submit without logging in, send email to [secure@microsoft.com](mailto:secure@microsoft.com). If possible, encrypt your message with our PGP key; please download it from the the [Microsoft Security Response Center PGP Key page](https://www.microsoft.com/en-us/msrc/pgp-key-msrc).
|
||||
|
||||
You should receive a response within 24 hours. If for some reason you do not, please follow up via email to ensure we received your original message. Additional information can be found at [microsoft.com/msrc](https://www.microsoft.com/msrc).
|
||||
|
||||
Please include the requested information listed below (as much as you can provide) to help us better understand the nature and scope of the possible issue:
|
||||
|
||||
- Type of issue (e.g. buffer overflow, SQL injection, cross-site scripting, etc.)
|
||||
- Full paths of source file(s) related to the manifestation of the issue
|
||||
- The location of the affected source code (tag/branch/commit or direct URL)
|
||||
- Any special configuration required to reproduce the issue
|
||||
- Step-by-step instructions to reproduce the issue
|
||||
- Proof-of-concept or exploit code (if possible)
|
||||
- Impact of the issue, including how an attacker might exploit the issue
|
||||
|
||||
This information will help us triage your report more quickly.
|
||||
|
||||
If you are reporting for a bug bounty, more complete reports can contribute to a higher bounty award. Please visit our [Microsoft Bug Bounty Program](https://microsoft.com/msrc/bounty) page for more details about our active programs.
|
||||
|
||||
## Preferred Languages
|
||||
|
||||
We prefer all communications to be in English.
|
||||
|
||||
## Policy
|
||||
|
||||
Microsoft follows the principle of [Coordinated Vulnerability Disclosure](https://www.microsoft.com/en-us/msrc/cvd).
|
||||
|
||||
<!-- END MICROSOFT SECURITY.MD BLOCK -->
|
||||
|
|