forked from Azure/azure-rest-api-specs
-
Notifications
You must be signed in to change notification settings - Fork 0
/
Copy pathbreaking-change.js
240 lines (202 loc) · 8.24 KB
/
breaking-change.js
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
// Copyright (c) Microsoft Corporation. All rights reserved.
// Licensed under the MIT License. See License in the project root for license information.
'use strict';
var utils = require('../test/util/utils'),
path = require('path'),
fs = require('fs'),
os = require('os'),
execSync = require('child_process').execSync,
oad = require('oad');
// This map is used to store the mapping between files resolved and stored location
var resolvedMapForNewSpecs = {};
let outputFolder = path.join(os.tmpdir(), "resolved");
// Used to enable running script outside TravisCI for debugging
let isRunningInTravisCI = process.env.TRAVIS === 'true';
const headerText = `
| | Rule | Location | Message |
|-|------|----------|---------|
`;
function iconFor(type) {
if (type === 'Error') {
return ':x:';
} else if (type === 'Warning') {
return ':warning:';
} else if (type === 'Info') {
return ':speech_balloon:';
} else {
return '';
}
}
function shortName(filePath) {
return `${path.basename(path.dirname(filePath))}/​<strong>${path.basename(filePath)}</strong>`;
}
function tableLine(filePath, diff) {
return `|${iconFor(diff['type'])}|[${diff['type']} ${diff['id']} - ${diff['code']}](https://github.com/Azure/openapi-diff/blob/master/docs/rules/${diff['id']}.md)|[${shortName(filePath)}](${blobHref(filePath)} "${filePath}")|${diff['message']}|\n`;
}
function blobHref(file) {
return `https://github.com/${process.env.TRAVIS_PULL_REQUEST_SLUG}/blob/${process.env.TRAVIS_PULL_REQUEST_SHA}/${file}`;
}
/**
* Compares old and new specifications for breaking change detection.
*
* @param {string} oldSpec Path to the old swagger specification file.
*
* @param {string} newSpec Path to the new swagger specification file.
*/
async function runOad(oldSpec, newSpec) {
if (oldSpec === null || oldSpec === undefined || typeof oldSpec.valueOf() !== 'string' || !oldSpec.trim().length) {
throw new Error('oldSpec is a required parameter of type "string" and it cannot be an empty string.');
}
if (newSpec === null || newSpec === undefined || typeof newSpec.valueOf() !== 'string' || !newSpec.trim().length) {
throw new Error('newSpec is a required parameter of type "string" and it cannot be an empty string.');
}
console.log(`>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>`);
console.log(`Old Spec: "${oldSpec}"`);
console.log(`New Spec: "${newSpec}"`);
console.log(`>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>`);
const result = await oad.compare(oldSpec, newSpec, { consoleLogLevel: 'warn', json: true });
console.log(result);
if (!result) {
return;
}
return JSON.parse(result);
}
/**
* Processes the given swagger and stores the resolved swagger on to disk
*
* @param {string} swaggerPath Path to the swagger specification file.
*/
function processViaAutoRest(swaggerPath) {
if (swaggerPath === null || swaggerPath === undefined || typeof swaggerPath.valueOf() !== 'string' || !swaggerPath.trim().length) {
return Promise.reject(new Error('swaggerPath is a required parameter of type "string" and it cannot be an empty string.'));
}
console.log(`Processing via AutoRest...`);
let outputFileNameWithExt = path.basename(swaggerPath);
let outputFileNameWithoutExt = path.basename(swaggerPath, '.json');
let autoRestCmd = `autorest --input-file=${swaggerPath} --output-artifact=swagger-document.json --output-file=${outputFileNameWithoutExt} --output-folder=${outputFolder}`;
console.log(`Executing : ${autoRestCmd}`);
try {
let result = execSync(`${autoRestCmd}`, { encoding: 'utf8', maxBuffer: 1024 * 1024 * 64 });
resolvedMapForNewSpecs[outputFileNameWithExt] = path.join(outputFolder, outputFileNameWithExt);
} catch (err) {
// Do not update map in case of errors.
}
return Promise.resolve();
}
//main function
async function runScript() {
// See whether script is in Travis CI context
console.log(`isRunningInTravisCI: ${isRunningInTravisCI}`);
// Create directory to store the processed & resolved swaggers
if (!fs.existsSync(outputFolder)) {
fs.mkdirSync(outputFolder);
}
let targetBranch = utils.getTargetBranch();
let swaggersToProcess = utils.getFilesChangedInPR();
console.log('Processing swaggers:');
console.log(swaggersToProcess);
for (const swagger of swaggersToProcess) {
await processViaAutoRest(swagger);
}
let newSwaggers = [];
if (isRunningInTravisCI && swaggersToProcess.length > 0) {
newSwaggers = await utils.doOnBranch(utils.getTargetBranch(), async () => {
return swaggersToProcess.filter(s => !fs.existsSync(s))
});
}
console.log(`Resolved map for the new specification is:`);
console.dir(resolvedMapForNewSpecs);
let errors = 0, warnings = 0;
const diffFiles = {};
const newFiles = [];
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)) {
console.log(`File: "${swagger}" looks to be newly added in this PR.`);
newFiles.push(swagger);
continue;
}
let outputFileNameWithExt = path.basename(swagger);
console.log(outputFileNameWithExt);
if (resolvedMapForNewSpecs[outputFileNameWithExt]) {
const diff = await runOad(swagger, resolvedMapForNewSpecs[outputFileNameWithExt]);
if (diff) {
if (!diffFiles[swagger]) {
diffFiles[swagger] = [];
}
diffFiles[swagger].push(diff);
if (diff['type'] === 'Error') {
if (errors === 0) {
console.log(`There are potential breaking changes in this PR. Please review before moving forward. Thanks!`);
process.exitCode = 1;
}
errors += 1;
} else if (diff['type'] === 'Warning') {
warnings += 1;
}
}
}
}
if (isRunningInTravisCI) {
let summary = '';
if (errors > 0) {
summary += '**There are potential breaking changes in this PR. Please review before moving forward. Thanks!**\n\n';
}
summary += `Compared to the target branch (**${targetBranch}**), this pull request introduces:\n\n`;
summary += ` ${errors > 0 ? iconFor('Error') : ':white_check_mark:'} **${errors}** new error${errors !== 1 ? 's' : ''}\n\n`;
summary += ` ${warnings > 0 ? iconFor('Warning') : ':white_check_mark:'} **${warnings}** new warning${warnings !== 1 ? 's' : ''}\n\n`;
let message = '';
if (newFiles.length > 0) {
message += '### The following files look to be newly added in this PR:\n';
newFiles.sort();
for (const swagger of newFiles) {
message += `* [${swagger}](${blobHref(swagger)})\n`;
}
message += '<br><br>\n';
}
const diffFileNames = Object.keys(diffFiles);
if (diffFileNames.length > 0) {
message += '### OpenAPI diff results\n';
message += headerText;
diffFileNames.sort();
for (const swagger of diffFileNames) {
const diffs = diffFiles[swagger];
diffs.sort((a, b) => {
if (a.type === b.type) {
return a.id.localeCompare(b.id);
} else if (a.type === "Error") {
return 1;
} else if (b.type === "Error") {
return -1;
} else if (a.type === "Warning") {
return 1;
} else {
return -1;
}
});
for (const diff of diffs) {
message += tableLine(swagger, diff);
}
}
} else {
message += '**There were no files containing new errors or warnings.**\n';
}
message += '\n<br><br>\nThanks for using breaking change tool to review.\nIf you encounter any issue(s), please open issue(s) at https://github.com/Azure/openapi-diff/issues.';
const output = {
title: `${errors === 0 ? 'No' : errors} potential breaking change${errors !== 1 ? 's' : ''}`,
summary,
text: message
};
console.log('---output');
console.log(JSON.stringify(output));
console.log('---');
}
}
// magic starts here
runScript().then(success => {
console.log(`Thanks for using breaking change tool to review.`);
console.log(`If you encounter any issue(s), please open issue(s) at https://github.com/Azure/openapi-diff/issues .`);
}).catch(err => {
console.log(err);
process.exitCode = 1;
})