* Add breaking change filter

* send labels if message type is error

* adding change log
This commit is contained in:
JianyeXi 2020-11-02 15:26:39 +08:00 коммит произвёл GitHub
Родитель ef4aedacb9
Коммит c366e859b7
Не найден ключ, соответствующий данной подписи
Идентификатор ключа GPG: 4AEE18F83AFDEB23
13 изменённых файлов: 670 добавлений и 94 удалений

Просмотреть файл

@ -1,5 +1,10 @@
# Changelog
## 0.14.0
- Support configuring breaking change rules base on config.
- Adding details for lintDiff & oad details in unified pipeline report.
## 0.13.4
- Upgrade oav to 0.22.9 with rule change in response in case of both x-ms-secret and required are annotated.

8
package-lock.json сгенерированный
Просмотреть файл

@ -1,6 +1,6 @@
{
"name": "@azure/rest-api-specs-scripts",
"version": "0.13.4",
"version": "0.14.0",
"lockfileVersion": 1,
"requires": true,
"dependencies": {
@ -101,9 +101,9 @@
}
},
"@azure/swagger-validation-common": {
"version": "0.0.3",
"resolved": "https://registry.npmjs.org/@azure/swagger-validation-common/-/swagger-validation-common-0.0.3.tgz",
"integrity": "sha512-do3uVDob4fTveCxOI1CojhXaMvI5zsEX9YRRIMqGuT3ccXXvge1C1x+2VSoa6WjGCmwetVMBG/anrFv4obtEkA=="
"version": "0.1.2",
"resolved": "https://registry.npmjs.org/@azure/swagger-validation-common/-/swagger-validation-common-0.1.2.tgz",
"integrity": "sha512-QjmSpAliTzc77WTCnm3+zS2qVGv5U9/2h2y1ICZKQp5+0JSMcy3dPbS9WiKKkpsGQ2b1BJbDugJqKVg6a+8AeA=="
},
"@babel/code-frame": {
"version": "7.10.4",

Просмотреть файл

@ -1,6 +1,6 @@
{
"name": "@azure/rest-api-specs-scripts",
"version": "0.13.4",
"version": "0.14.0",
"description": "Scripts for the Azure RestAPI specification repository 'azure-rest-api-specs'.",
"types": "dist/index.d.ts",
"main": "dist/index.js",
@ -45,7 +45,7 @@
"@azure/avocado": "^0.4.1",
"@azure/oad": "^0.8.1",
"@ts-common/string-map": "^0.3.0",
"@azure/swagger-validation-common": "^0.0.3",
"@azure/swagger-validation-common": "^0.1.2",
"commonmark": "0.27.0",
"fs-extra": "^7.0.1",
"glob": "^7.1.3",

Просмотреть файл

@ -10,6 +10,8 @@ import { targetHref } from "./utils";
import * as utils from "./utils";
import { glob } from 'glob';
import { getVersionFromInputFile } from './readmeUtils';
import { ruleManager } from './breakingChangeRuleManager'
import { UnifiedPipeLineStore, oadTracer } from './unifiedPipelineHelper';
// Copyright (c) Microsoft Corporation. All rights reserved.
// Licensed under the MIT License. See License in the project root for license information.
@ -84,7 +86,7 @@ function blobHref(file: string) {
*
* @param newSpec Path to the new swagger specification file.
*/
async function runOad(oldSpec: string, newSpec: string , isCrossVersion = false) {
async function runOad(oldSpec: string, newSpec: string) {
if (
oldSpec === null ||
oldSpec === undefined ||
@ -113,55 +115,14 @@ async function runOad(oldSpec: string, newSpec: string , isCrossVersion = false)
console.log(`>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>`);
let result = await oad.compare(oldSpec, newSpec, { consoleLogLevel: "warn" });
let oadResult = JSON.parse(result) as OadMessage[];
const pipelineResultData: format.ResultMessageRecord[] = oadResult.map(
(it) => ({
type: "Result",
level: isCrossVersion ? "Warning" : it.type as format.MessageLevel,
message: it.message,
code: it.code,
id: it.id,
docUrl: it.docUrl,
time: new Date(),
extra: {
mode: it.mode,
},
paths: [
{
tag: "New",
path: blobHref(
utils.getGithubStyleFilePath(
utils.getRelativeSwaggerPathToRepo(it.new.location || "")
)
),
},
{
tag: "Old",
path: targetHref(
utils.getGithubStyleFilePath(
utils.getRelativeSwaggerPathToRepo(it.old.location || "")
)
),
},
],
})
);
const pipelineResult: format.MessageLine = pipelineResultData;
console.log("Write to pipe.log");
fs.appendFileSync("pipe.log", JSON.stringify(pipelineResult) + "\n");
console.log(JSON.parse(result));
if (!result) {
return;
}
// fix up output from OAD, it does not output valid JSON
result = result.replace(/}\s+{/gi, "},{");
return JSON.parse(result);
let oadResult = JSON.parse(result) as OadMessage[];
oadTracer.add(utils.getRelativeSwaggerPathToRepo(oldSpec),newSpec)
console.log(JSON.parse(result));
return oadResult
}
type SwaggerVersionType = "preview" | "stable";
@ -279,20 +240,28 @@ export class SwaggerVersionManager {
}
export class CrossVersionBreakingDetector {
swaggers :string[]= []
pr : devOps.PullRequestProperties
versionManager: SwaggerVersionManager = new SwaggerVersionManager()
constructor(pullRequest: devOps.PullRequestProperties,newSwaggers:string[]) {
this.swaggers = newSwaggers
this.pr = pullRequest
swaggers: string[] = [];
pr: devOps.PullRequestProperties;
versionManager: SwaggerVersionManager = new SwaggerVersionManager();
unifiedStore = new UnifiedPipeLineStore("");
constructor(
pullRequest: devOps.PullRequestProperties,
newSwaggers: string[]
) {
this.swaggers = newSwaggers;
this.pr = pullRequest;
}
async diffOne(oldSpec: string ,newSpec : string) {
async diffOne(oldSpec: string, newSpec: string) {
try {
await runOad(path.resolve(this.pr!.workingDir, oldSpec), newSpec);
}
catch(e) {
const errors = []
const oadResult = await runOad(
path.resolve(this.pr!.workingDir, oldSpec),
newSpec
);
const filterResult = ruleManager.handleCrossApiVersion(oadResult);
this.unifiedStore.appendOadViolation(filterResult);
} catch (e) {
const errors = [];
errors.push({
error: e,
old: targetHref(
@ -302,28 +271,34 @@ export class CrossVersionBreakingDetector {
),
new: blobHref(utils.getRelativeSwaggerPathToRepo(newSpec)),
});
appendException(errors)
appendException(errors);
}
}
async checkBreakingChangeBaseOnPreviewVersion() {
for (const swagger of this.swaggers) {
const previous = await utils.doOnTargetBranch(this.pr, async () => {
return this.versionManager.getClosestPreview(swagger)
})
return this.versionManager.getClosestPreview(swagger);
});
if (previous) {
await this.diffOne(path.resolve(this.pr!.workingDir,previous), swagger);
await this.diffOne(
path.resolve(this.pr!.workingDir, previous),
swagger
);
}
}
}
async checkBreakingChangeBaseOnStableVersion() {
for (const swagger of this.swaggers) {
const previous = await utils.doOnTargetBranch(this.pr, async () => {
return this.versionManager.getClosestStale(swagger);
});
const previous = await utils.doOnTargetBranch(this.pr, async () => {
return this.versionManager.getClosestStale(swagger);
});
if (previous) {
await this.diffOne(path.resolve(this.pr!.workingDir,previous), swagger);
await this.diffOne(
path.resolve(this.pr!.workingDir, previous),
swagger
);
}
}
}
@ -356,6 +331,8 @@ export async function runCrossVersionBreakingChangeDetection(type:SwaggerVersion
else {
detector.checkBreakingChangeBaseOnStableVersion()
}
oadTracer.save()
ruleManager.addBreakingChangeLabels()
}
}
@ -390,6 +367,7 @@ function changeTargetBranch(pr: devOps.PullRequestProperties | undefined) {
}
}
//main function
export async function runScript() {
console.log(`ENV: ${JSON.stringify(process.env)}`);
@ -422,7 +400,7 @@ export async function runScript() {
const newFiles = [];
const errors: RuntimeError[] = [];
const unifiedStore = new UnifiedPipeLineStore("")
for (const swagger of swaggersToProcess) {
// If file does not exists in the previous commits then we ignore it as it's new file
if (newSwaggers.includes(swagger)) {
@ -437,9 +415,11 @@ export async function runScript() {
swagger // Since the swagger resolving will be done at the oad , here to ensure the position output is consistent with the origin swagger,do not use the resolved swagger
);
if (diffs) {
diffFiles[swagger] = diffs;
for (const diff of diffs) {
if (diff["type"] === "Error") {
const filterDiffs = ruleManager.handleSameApiVersion(diffs);
unifiedStore.appendOadViolation(filterDiffs);
diffFiles[swagger] = filterDiffs;
for (const diff of filterDiffs) {
if (diff["type"].toLowerCase() === "error") {
if (errorCnt === 0) {
console.log(
`There are potential breaking changes in this PR. Please review before moving forward. Thanks!`
@ -464,6 +444,8 @@ export async function runScript() {
});
}
}
oadTracer.save();
ruleManager.addBreakingChangeLabels();
if (errors.length > 0) {
process.exitCode = 1;

Просмотреть файл

@ -0,0 +1,207 @@
import * as fs from "fs-extra";
import * as yaml from "js-yaml";
import { OadMessage } from './breaking-change';
import { exception } from 'console';
import { sendLabels } from "@azure/swagger-validation-common";
import { devOps } from '@azure/avocado';
import * as path from "path";
type OverrideBody = string | {from:string,to:string}[]
interface BreakingChangeRule {
appliedTo: string;
override?: { code?: OverrideBody , message?:OverrideBody , type?:OverrideBody};
directive?: {addingLabels:string[]}
}
interface BreakingChangeScenario {
Scenario: string;
rules: BreakingChangeRule[];
}
interface RuleConfig<T> {
load(configPath:string):boolean
getConfig(sectionName: string): Map<string,T> | undefined
}
class LocalRuleConfig implements RuleConfig<BreakingChangeRule> {
private AllConfig: BreakingChangeScenario[] | undefined;
load(configPath: string): boolean {
try {
const config = fs.readFileSync(configPath).toString();
this.AllConfig = yaml.safeLoad(config) as BreakingChangeScenario[];
return !!this.AllConfig;
} catch (e) {
console.log(e);
return false;
}
}
getConfig(scenarioName: string): Map<string, BreakingChangeRule> | undefined {
if (this.AllConfig) {
try {
const sectionConfig = new Map<string, BreakingChangeRule>();
const rulesIndex = this.AllConfig.findIndex(
(v) => v.Scenario === scenarioName
);
if (rulesIndex === -1) {
return undefined;
}
const rules = this.AllConfig[rulesIndex].rules;
for (const key in Object.keys(rules)) {
const ruleContent = rules[key];
const rule = ruleContent as BreakingChangeRule;
if (!rule) {
throw exception("invalid config");
}
sectionConfig.set(rule.appliedTo.toLowerCase(), rule);
}
return sectionConfig;
} catch (e) {
return undefined;
}
}
}
}
const BreakingChangeLabels = new Set<string>()
interface ruleHandler {
process(message: OadMessage,rule: BreakingChangeRule): OadMessage
}
const overrideHandler = {
process(message: OadMessage, rule: BreakingChangeRule): OadMessage {
let result = {...message} as any
if (rule.override && typeof rule.override === "object") {
for (const [key,value] of Object.entries(rule.override)) {
if (typeof value === "string") {
if (result[key]) {
result[key] = value;
}
}
else if (value) {
for ( const pair of value) {
if ((result[key] as string).toLowerCase() === pair.from.toLowerCase()) {
result[key] = pair.to
break
}
}
}
}
}
return result
},
};
const directiveHandler = {
process(message: OadMessage, rule: BreakingChangeRule): OadMessage {
if (rule.directive && rule.directive.addingLabels && message.type.toLowerCase() === "error") {
for (const label of rule.directive.addingLabels){
BreakingChangeLabels.add(label)
}
}
return message
}
};
class OadMessageEngine {
private config: LocalRuleConfig;
private HandlersMap = new Map<string, ruleHandler>();
private scenario = "default";
constructor(config: LocalRuleConfig) {
this.config = config;
this.initHandlerMap();
}
initHandlerMap() {
this.HandlersMap.set("override", overrideHandler);
this.HandlersMap.set("directive", directiveHandler);
}
public setScenario(scenarioName: string) {
this.scenario = scenarioName;
return this
}
getRulesMap() {
return this.config.getConfig(this.scenario);
}
handle(messages: OadMessage[]): OadMessage[] {
const ruleMap = this.getRulesMap();
if (!ruleMap) {
return messages
}
console.log("---- begin breaking change filter ----")
const result: OadMessage[] = [];
for (const message of messages) {
const ruleId = message.id.toLowerCase();
const ruleCode = message.code.toLowerCase();
if (ruleMap && (ruleMap.has(ruleId) || ruleMap.has(ruleCode))) {
const rule = ruleMap.get(ruleId) || ruleMap.get(ruleCode);
let postMessage : OadMessage | undefined = message
for (const [key,handler] of this.HandlersMap.entries()) {
if (rule && Object.keys(rule).includes(key) && postMessage) {
postMessage = handler.process(postMessage, rule);
}
}
if (postMessage) {
result.push(postMessage)
}
}
else {
result.push(message);
}
}
console.log("----- end breaking change filter ----");
console.log(result)
return result;
}
}
class BreakingChangeRuleManager {
private getBreakingChangeConfigPath(){
let breakingChangeRulesConfigPath = "config/BreakingChangeRules.yml";
if (process.env.BREAKING_CHANGE_RULE_CONFIG_PATH) {
breakingChangeRulesConfigPath =
process.env.BREAKING_CHANGE_RULE_CONFIG_PATH;
}
return breakingChangeRulesConfigPath
}
private buildRuleConfig() {
const configPath = this.getBreakingChangeConfigPath()
if (!fs.existsSync(configPath)) {
console.log(`Config file:${configPath} was not existing.`);
return undefined;
}
const config = new LocalRuleConfig();
if (!config.load(configPath)) {
throw exception(`unable to load config file:${configPath}`);
}
return config;
}
public handleCrossApiVersion(messages: OadMessage[]) {
const ruleConfig = this.buildRuleConfig();
if (!ruleConfig) {
return messages;
}
return new OadMessageEngine(ruleConfig)
.setScenario("CrossVersion")
.handle(messages);
}
public handleSameApiVersion (messages: OadMessage[]) {
const ruleConfig = this.buildRuleConfig();
if (!ruleConfig) {
return messages;
}
return new OadMessageEngine(ruleConfig)
.setScenario("SameVersion")
.handle(messages);
};
public addBreakingChangeLabels() {
sendLabels([...BreakingChangeLabels.values()]);
};
}
export const ruleManager = new BreakingChangeRuleManager()

Просмотреть файл

@ -9,6 +9,7 @@ import * as utils from "./utils";
import * as fs from "fs";
import { devOps, cli } from "@azure/avocado";
import * as format from "@azure/swagger-validation-common";
import { lintTracer } from './unifiedPipelineHelper';
type TypeUtils = typeof utils;
type TypeDevOps = typeof devOps;
@ -58,6 +59,7 @@ export async function getLinterResult(
tagCmd +
swaggerPath;
console.log(`Executing: ${cmd}`);
const { err, stdout, stderr } = await new Promise((res) =>
exec(
cmd,
@ -159,6 +161,7 @@ class LinterRunner {
if (tags) {
for (const tag of tags) {
if (utils.isTagExisting(swagger, tag)) {
lintTracer.add(swagger,tag,beforeOrAfter === "before")
const linterErrors = await getLinterResult(swagger, tag);
console.log(linterErrors);
await this.updateResult(swagger, linterErrors, beforeOrAfter);
@ -168,6 +171,7 @@ class LinterRunner {
}
/* to ensure lint ran at least once */
if (runCnt == 0) {
lintTracer.add(swagger, "", beforeOrAfter === "before");
const linterErrors = await getLinterResult(swagger);
console.log(linterErrors);
await this.updateResult(swagger, linterErrors, beforeOrAfter);
@ -261,7 +265,8 @@ export async function lintDiff(utils: TypeUtils, devOps: TypeDevOps) {
await linter.runTools("before");
});
}
lintTracer.save()
store.writeContent(JSON.stringify(linter.getResult(), null, 2));
console.log("--- Lint Violation Result ----\n");

Просмотреть файл

@ -1,11 +1,6 @@
import * as fs from "fs-extra";
import * as util from "./utils"
import * as YAML from "js-yaml";
import {
getInputFilesForTag,
} from "@azure/openapi-markdown";
import { MarkDownEx, parse } from "@ts-common/commonmark-to-markdown";
export function getVersionFromInputFile(filePath: string): string | undefined {
const apiVersionRegex = /^\d{4}-\d{2}-\d{2}(|-preview)$/;

Просмотреть файл

@ -0,0 +1,21 @@
---
- Scenario: "SameVersion"
description: "this rule is a mapping between oad rules and breaking change rules"
rules:
- appliedTo: "1034"
override:
code: "Added Required Property"
type:
- from: "warning"
to: "error"
message: "override message"
- appliedTo: "RemovedPath"
override:
code: "Removed path"
type: "error"
message: "override message"
directive:
addingLabels:
- NewApiVersionRequired

Просмотреть файл

@ -0,0 +1,15 @@
---
- Scenario: "CrossVersion"
description: "this rule is a mapping between oad rules and breaking change rules"
rules:
- appliedTo: "1034"
override:
code: "Added Required Property"
type:
- from: "Error"
to: "info"
message: "override message"
directive:
addingLabels:
- NewApiVersionRequired

Просмотреть файл

@ -0,0 +1,161 @@
// Copyright (c) Microsoft Corporation. All rights reserved.
// Licensed under the MIT License. See License in the project root for license information.
import { suite, test, timeout } from "mocha-typescript";
import * as assert from "assert";
import {
ruleManager,
} from "../breakingChangeRuleManager";
import { lintTracer } from '../unifiedPipelineHelper';
import * as fs from "fs-extra";
@suite
class BreakingChangeRuleTest {
cwd = process.cwd();
before() {
process.env.BREAKING_CHANGE_RULE_CONFIG_PATH = "./breakingChangeRules.yaml";
}
@test testCrossApiVersion() {
process.chdir("./src/tests/Resource/breakingChangeRule");
const messages = [
{
id: "1034",
code: "AddedRequiredProperty",
message:
"The new version has new required property 'capacity' that was not found in the old version.",
old: {
ref:
"file:///home/vsts/work/1/c93b354fd9c14905bb574a8834c4d69b/specification/apimanagement/resource-manager/Microsoft.ApiManagement/preview/2019-12-01-preview/apimdeployment.json#/definitions/ApiManagementServiceResource/properties/sku",
path: "definitions.ApiManagementServiceResource.properties.sku",
location:
"file:///home/vsts/work/1/c93b354fd9c14905bb574a8834c4d69b/specification/apimanagement/resource-manager/Microsoft.ApiManagement/preview/2019-12-01-preview/apimdeployment.json:1268:9",
},
new: {
ref:
"file:///tmp/resolved/specification/apimanagement/resource-manager/Microsoft.ApiManagement/preview/2019-12-01-preview/apimdeployment.json#/definitions/ApiManagementServiceResource/properties/sku",
path: "definitions.ApiManagementServiceResource.properties.sku",
location:
"file:///tmp/resolved/specification/apimanagement/resource-manager/Microsoft.ApiManagement/preview/2019-12-01-preview/apimdeployment.json:3143:9",
},
type: "Error",
docUrl:
"https://github.com/Azure/openapi-diff/tree/master/docs/rules/1034.md",
mode: "Addition",
},
];
const expected = [
{
id: "1034",
code: "Added Required Property",
message: "override message",
old: {
ref:
"file:///home/vsts/work/1/c93b354fd9c14905bb574a8834c4d69b/specification/apimanagement/resource-manager/Microsoft.ApiManagement/preview/2019-12-01-preview/apimdeployment.json#/definitions/ApiManagementServiceResource/properties/sku",
path: "definitions.ApiManagementServiceResource.properties.sku",
location:
"file:///home/vsts/work/1/c93b354fd9c14905bb574a8834c4d69b/specification/apimanagement/resource-manager/Microsoft.ApiManagement/preview/2019-12-01-preview/apimdeployment.json:1268:9",
},
new: {
ref:
"file:///tmp/resolved/specification/apimanagement/resource-manager/Microsoft.ApiManagement/preview/2019-12-01-preview/apimdeployment.json#/definitions/ApiManagementServiceResource/properties/sku",
path: "definitions.ApiManagementServiceResource.properties.sku",
location:
"file:///tmp/resolved/specification/apimanagement/resource-manager/Microsoft.ApiManagement/preview/2019-12-01-preview/apimdeployment.json:3143:9",
},
type: "info",
docUrl:
"https://github.com/Azure/openapi-diff/tree/master/docs/rules/1034.md",
mode: "Addition",
},
];
const result = ruleManager.handleCrossApiVersion(messages);
assert.deepEqual(result, expected);
}
@test testSameApiVersion() {
process.chdir("./src/tests/Resource/breakingChangeRule");
process.env.BREAKING_CHANGE_RULE_CONFIG_PATH =
"./breakingChangeRules-1.yaml";
const messages = [
{
id: "1034",
code: "AddedRequiredProperty",
message:
"The new version has new required property 'capacity' that was not found in the old version.",
type: "Warning",
new: {},
old: {},
docUrl:
"https://github.com/Azure/openapi-diff/tree/master/docs/rules/1034.md",
mode: "Addition",
},
{
id: "1005",
code: "RemovedPath",
message:
"The new version is missing a path that was found in the old version. Was path '/providers/Microsoft.Devices/operations' removed or restructured?",
type: "Waring",
new: {},
old: {},
docUrl:
"https://github.com/Azure/openapi-diff/tree/master/docs/rules/1005.md",
mode: "Removal",
},
];
const expected = [
{
id: "1034",
code: "Added Required Property",
message: "override message",
type: "error",
new: {},
old: {},
docUrl:
"https://github.com/Azure/openapi-diff/tree/master/docs/rules/1034.md",
mode: "Addition",
},
{
id: "1005",
code: "Removed path",
message: "override message",
type: "error",
new: {},
old: {},
docUrl:
"https://github.com/Azure/openapi-diff/tree/master/docs/rules/1005.md",
mode: "Removal",
},
];
const result = ruleManager.handleSameApiVersion(messages);
assert.deepEqual(result, expected);
}
@test TestLintTrace() {
lintTracer.add(
"specification/apimanagement/resource-manager/readme.md",
"package-2020-08",
true
);
lintTracer.add(
"specification/apimanagement/resource-manager/readme.md",
"package-2020-08",
false
);
lintTracer.add(
"specification/apimanagement/resource-manager/readme.md",
"",
false
);
const resultFile = "pipe.log";
if (fs.existsSync(resultFile)) {
fs.unlinkSync(resultFile);
}
lintTracer.save();
const lintTraceInfo = JSON.parse(fs.readFileSync(resultFile).toString());
assert.notEqual(undefined, lintTraceInfo);
}
after() {
process.chdir(this.cwd);
}
}

Просмотреть файл

@ -11,7 +11,7 @@ import * as asyncIt from "@ts-common/async-iterator";
import _ from 'lodash';
import { LintingResultMessage } from '../momentOfTruthUtils';
import { ReadmeParser } from "../readmeUtils"
import { LintMsgTransformer } from "../unifiedPipelineHelper";
import { MsgTransformer } from "../unifiedPipelineHelper";
const sinon = require("sinon");
let cwd = process.cwd();
@ -30,7 +30,7 @@ class LintingRpaasTest {
@test TestLintMsgTransformer() {
process.chdir("./src/tests/Resource/lintingRpaas");
const transformer = new LintMsgTransformer();
const transformer = new MsgTransformer();
const testMsg = [
({
type: "Error",

Просмотреть файл

@ -47,6 +47,10 @@ class MomentOfTruthPostProcessingTest {
let stub5 = sinon.stub(utils, "getTargetBranch").callsFake(() => "master");
await cleanUpDir("./output");
const pipeFile = "./pipe.log";
if (fs.existsSync(pipeFile)) {
fs.unlinkSync(pipeFile);
}
await lintDiff(utils, devOps);
await postProcessing();
@ -63,7 +67,7 @@ class MomentOfTruthPostProcessingTest {
const logFile = "./output/1001.json";
const result = JSON.parse(fs.readFileSync(logFile, { encoding: "utf8" }));
const resultFiles = result.files;
assert.deepEqual(Object.keys(resultFiles), [
assert.deepEqual(Object.keys(resultFiles), [
"specification/test-lint/readme.md",
]);
@ -77,15 +81,15 @@ assert.deepEqual(Object.keys(resultFiles), [
.after as Array<any>).map((error) => error.id).sort();
assert.deepEqual(errorIds, ["D5001","R2054", "R3023"]);
const pipeFile = "./pipe.log";
console.log("------------- read from pipe.log -----------------");
const chunck = fs.readFileSync(pipeFile, { encoding: "utf8" })
console.log(chunck);
const messages = chunck.split(/[\r\n]+/)
const chunk = fs.readFileSync(pipeFile, { encoding: "utf8" })
console.log(chunk);
const messages = chunk.split(/[\r\n]+/)
.filter(l => l) // filter out empty lines
.map(l => JSON.parse(l.trim()) as MessageLine)
.map(l => JSON.parse(l.trim()) as MessageLine).filter(m => m)
.map(l => Array.isArray(l) ? l : [l]);
const res: ResultMessageRecord[] = _.flatMap(messages, m => m).map(m => <ResultMessageRecord>m);
const res: ResultMessageRecord[] = _.flatMap(messages, m => m).map(m => <ResultMessageRecord>m).filter(m => m.type === "Result");
const resIds = res.map(m => m.id).sort();
console.log("------------- parse validation message from[pipe.log] ------------------");
console.log(JSON.stringify(res));

Просмотреть файл

@ -2,8 +2,13 @@
import { LintingResultMessage , Mutable , Issue, getFile, getLine,getDocUrl, composeLintResult} from "./momentOfTruthUtils"
import * as utils from "./utils";
import * as fs from "fs-extra";
import { OadMessage } from './breaking-change';
import * as format from "@azure/swagger-validation-common";
import { devOps } from '@azure/avocado';
import { PullRequestProperties } from '@azure/avocado/dist/dev-ops';
const packageJson = require("../package.json");
export class LintMsgTransformer {
export class MsgTransformer {
constructor() {}
lintMsgToUnifiedMsg(msg: LintingResultMessage[]) {
@ -23,6 +28,42 @@ export class LintMsgTransformer {
return JSON.stringify(result);
}
OadMsgToUnifiedMsg(messages: OadMessage[]) {
const pipelineResultData: format.ResultMessageRecord[] = messages.map(
(it) => ({
type: "Result",
level: it.type as format.MessageLevel,
message: it.message,
code: it.code,
id: it.id,
docUrl: it.docUrl,
time: new Date(),
extra: {
mode: it.mode,
},
paths: [
{
tag: "New",
path: utils.blobHref(
utils.getGithubStyleFilePath(
utils.getRelativeSwaggerPathToRepo(it.new.location || "")
)
),
},
{
tag: "Old",
path: utils.targetHref(
utils.getGithubStyleFilePath(
utils.getRelativeSwaggerPathToRepo(it.old.location || "")
)
),
},
],
})
);
return JSON.stringify(pipelineResultData);
}
rawErrorToUnifiedMsg(
errType: string,
errorMsg: string,
@ -41,15 +82,29 @@ export class LintMsgTransformer {
};
return JSON.stringify(result);
}
toMarkDownMsg(
errorMsg: string,
levelType = "Error"
) {
const result = {
type: "Markdown",
mode:"append",
level: levelType,
message: errorMsg,
time: new Date(),
} as format.MarkdownMessageRecord;
return JSON.stringify(result);
}
}
export class UnifiedPipeLineStore {
logFile = "pipe.log";
readme: string;
transformer: LintMsgTransformer;
transformer: MsgTransformer;
constructor(readme: string) {
this.transformer = new LintMsgTransformer();
this.transformer = new MsgTransformer();
this.readme = readme;
}
@ -103,4 +158,130 @@ export class UnifiedPipeLineStore {
)
);
}
public appendOadViolation(oadResult: OadMessage[]) {
this.appendMsg(this.transformer.OadMsgToUnifiedMsg(oadResult));
}
public appendMarkDown(markDown: string) {
this.appendMsg(this.transformer.toMarkDownMsg(markDown));
}
}
class AbstractToolTrace {
genMarkDown() {
return ""
}
save() {
const markDown = this.genMarkDown();
if (markDown) {
return new UnifiedPipeLineStore("").appendMarkDown(markDown);
}
}
}
// record lint invoking trace
class LintTrace extends AbstractToolTrace {
private traces = {
source: new Map<string, string[]>(),
target: new Map<string, string[]>(),
};
// isFromTargetBranch indicates whether it's from target branch
add(readmeRelatedPath: string, tag: string, isFromTargetBranch: boolean) {
const targetMap = isFromTargetBranch
? this.traces.target
: this.traces.source;
if (targetMap.has(readmeRelatedPath)) {
const tags = targetMap.get(readmeRelatedPath);
if (tags) {
tags.push(tag);
}
} else {
targetMap.set(readmeRelatedPath, [tag]);
}
}
genMarkDown() {
const classicLintVersion = process.env["CLASSIC_LINT_VERSION"]
? process.env["CLASSIC_LINT_VERSION"]
: "1.0.14";
const lintVersion = process.env["LINT_VERSION"]
? process.env["LINT_VERSION"]
: "1.0.4";
let content = "<br><ul>";
let impactedTags = 0;
for (const [beforeAfter, readmeTags] of Object.entries(this.traces)) {
content += `<li>`;
content += `Linted configuring files (Based on ${
beforeAfter === "source" ? "source" : "target"
} branch, openapi-validator <a href="https://www.npmjs.com/package/@microsoft.azure/openapi-validator/v/${lintVersion}" target="_blank"> v${lintVersion} </a>, classic-openapi-validator <a href="https://www.npmjs.com/package/@microsoft.azure/classic-openapi-validator/v/${classicLintVersion}" target="_blank"> v${classicLintVersion} </a>)`;
content += `<ul> `;
for (const [readme, tags] of readmeTags.entries()) {
const url =
beforeAfter === "target"
? utils.targetHref(readme)
: utils.blobHref(readme);
const showReadme = readme
.split(/[/|\\]/)
.slice(-3)
.join("/");
for (const tag of tags) {
content += "<li>";
content += `<a href="${url}"target="_blank">${showReadme}</a> tag:<a href="${url}${
tag ? "#tag-" : ""
}${tag}" target="_blank">${tag ? tag : "default"}</a>`;
content += "</li>";
impactedTags ++
}
}
content += `</ul></li>`;
}
if (impactedTags === 0) {
return ""
}
content += "</ul>";
return content;
}
}
// record oad invoking trace
class OadTrace extends AbstractToolTrace {
private traces: { old: string; new: string }[] = [];
add(oldSwagger: string, newSwagger: string) {
this.traces.push({ old: oldSwagger, new: newSwagger });
return this;
}
genMarkDown() {
const oadVersion = packageJson.dependencies["@azure/oad"].replace(
/[\^~]/,
""
);
if (this.traces.length === 0 ) {
return ""
}
let content = `<br><ul><li>Compared Swaggers (Based on Oad <a href="https://www.npmjs.com/package/@azure/oad/v/${oadVersion}" target="_blank">v${oadVersion}</a>)<ul>`;
for (const value of this.traces.values()) {
content += "<li>";
content += `original: <a href="${utils.targetHref(
value.old
)}" target="_blank">${value.old
.split("/")
.slice(-3)
.join("/")} </a> <---> new: <a href="${utils.blobHref(
value.new
)} " target="_blank"> ${value.new.split("/").slice(-3).join("/")} </a>`;
content += "</li>";
}
content += `</ul></li></ul>`;
return content;
}
}
export const oadTracer = new OadTrace();
export const lintTracer = new LintTrace();