Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Switch breaking-changes and momentOfTruth scripts to TypeScript #5643

Merged
merged 14 commits into from
Apr 15, 2019
5 changes: 5 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -110,3 +110,8 @@ warnings.txt
!.vscode/tasks.json
!.vscode/launch.json
!.vscode/extensions.json

*.js
*.d.ts
*.js.map
*.d.ts.map
6 changes: 5 additions & 1 deletion .travis.yml
Original file line number Diff line number Diff line change
Expand Up @@ -36,22 +36,26 @@ script:
- >-
if [[ $MODE == 'semantic' ]]; then
npm install
npm run tsc
node scripts/semanticValidation.js
fi
- >-
if [[ $MODE == 'model' ]]; then
npm install
npm run tsc
node scripts/modelValidation.js
fi
- >-
if [[ $MODE == 'BreakingChange' ]]; then
scripts/install-dotnet.sh
npm install
node -- scripts/breaking-change.js
npm run tsc
node scripts/breaking-change.ts
fi
- >-
if [[ $MODE == 'lintdiff' ]]; then
scripts/install-dotnet.sh
npm install
npm run tsc
node scripts/momentOfTruth.js && node scripts/momentOfTruthPostProcessing.js
fi
8 changes: 4 additions & 4 deletions azure-pipelines.yml
Original file line number Diff line number Diff line change
Expand Up @@ -41,7 +41,7 @@ jobs:
displayName: 'npm install'
inputs:
verbose: false
- script: 'node scripts/semanticValidation.js'
- script: 'npm run tsc && node scripts/semanticValidation.js'
displayName: 'Semantic Validation'

- job: "ModelValidation"
Expand All @@ -52,7 +52,7 @@ jobs:
displayName: 'npm install'
inputs:
verbose: false
- script: 'node scripts/modelValidation.js'
- script: 'npm run tsc && node scripts/modelValidation.js'
displayName: 'Model Validation'

- job: "Avocado"
Expand All @@ -78,7 +78,7 @@ jobs:
displayName: 'npm install'
inputs:
verbose: false
- script: 'node scripts/breaking-change.js'
- script: 'npm run tsc && node scripts/breaking-change.js'
displayName: 'Breaking Changes'

- job: "LintDiff"
Expand All @@ -94,7 +94,7 @@ jobs:
verbose: false
- script: 'scripts/install-dotnet.sh'
displayName: 'install .Net'
- script: 'node scripts/momentOfTruth.js && node scripts/momentOfTruthPostProcessing.js'
- script: 'npm run tsc && node scripts/momentOfTruth.js && node scripts/momentOfTruthPostProcessing.js'
displayName: 'LintDiff'

- job: "SDK"
Expand Down
44 changes: 22 additions & 22 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -10,28 +10,28 @@
"description": "Tests for Azure REST API Specifications",
"license": "MIT",
"devDependencies": {
"@azure/avocado": "^0.3.0",
"@azure/oad": "^0.4.3",
"@microsoft.azure/async-io": "^1.0.21",
"@microsoft.azure/literate": "^1.0.21",
"@microsoft.azure/polyfill": "^1.0.17",
"@ts-common/commonmark-to-markdown": "^1.1.10",
"@ts-common/fs": "0.1.1",
"@types/js-yaml": "^3.12.0",
"fs-extra": "^3.0.1",
"glob": "^5.0.14",
"js-yaml": "^3.13.0",
"json-schema-ref-parser": "^3.1.2",
"@azure/avocado": "^0.3.3",
"@azure/oad": "^0.5.1",
"@microsoft.azure/async-io": "^2.0.21",
"@microsoft.azure/literate": "^1.0.25",
"@microsoft.azure/polyfill": "^1.0.19",
"@octokit/rest": "^16.24.1",
"@ts-common/commonmark-to-markdown": "^1.2.0",
"@ts-common/fs": "0.2.0",
"@types/fs-extra": "^5.0.5",
"@types/js-yaml": "^3.12.1",
"@types/request": "^2.48.1",
"fs-extra": "^7.0.1",
"glob": "^7.1.3",
"js-yaml": "^3.13.1",
"json-schema-ref-parser": "^6.1.0",
"mocha": "*",
"oav": "^0.16.1",
"request": "^2.61.0",
"request-promise-native": "^1.0.5",
"ts-node": "^8.0.3",
"typescript": "^3.2.4",
"z-schema": "^3.25.1"
},
"dependencies": {
"@octokit/rest": "^15.2.6"
"oav": "^0.18.1",
"request": "^2.88.0",
"request-promise-native": "^1.0.7",
"ts-node": "^8.1.0",
"typescript": "^3.4.3",
"z-schema": "^4.0.2"
},
"homepage": "https://github.com/azure/azure-rest-api-specs",
"repository": {
Expand All @@ -42,7 +42,7 @@
"url": "http://github.com/azure/azure-rest-api-specs/issues"
},
"scripts": {
"test": "mocha -t 500000 --reporter min",
"test": "tsc && mocha -t 500000 --reporter min",
"oav": "oav",
"tsc": "tsc",
"multiapi": "ts-node ./scripts/multiapi.ts"
Expand Down
62 changes: 37 additions & 25 deletions scripts/breaking-change.js → scripts/breaking-change.ts
Original file line number Diff line number Diff line change
@@ -1,16 +1,20 @@
// Copyright (c) Microsoft Corporation. All rights reserved.
// Licensed under the MIT License. See License in the project root for license information.

'use strict';
const utils = require('../test/util/utils'),
path = require('path'),
fs = require('fs-extra'),
os = require('os'),
exec = require('util').promisify(require('child_process').exec),
oad = require('@azure/oad');
import * as stringMap from '@ts-common/string-map'
import * as tsUtils from './ts-utils'
import * as utils from '../test/util/utils'
import * as path from 'path'
import * as fs from 'fs-extra'
import * as os from 'os'
import * as childProcess from 'child_process'
import * as oad from '@azure/oad'
import * as util from 'util'

const exec = util.promisify(childProcess.exec)

// This map is used to store the mapping between files resolved and stored location
var resolvedMapForNewSpecs = {};
var resolvedMapForNewSpecs: stringMap.MutableStringMap<string> = {};
let outputFolder = path.join(os.tmpdir(), "resolved");
// Used to enable running script outside TravisCI for debugging
let isRunningInTravisCI = process.env.TRAVIS === 'true';
Expand All @@ -20,7 +24,7 @@ const headerText = `
|-|------|----------|---------|
`;

function iconFor(type) {
function iconFor(type: unknown) {
if (type === 'Error') {
return ':x:';
} else if (type === 'Warning') {
Expand All @@ -32,26 +36,33 @@ function iconFor(type) {
}
}

function shortName(filePath) {
function shortName(filePath: string) {
return `${path.basename(path.dirname(filePath))}/&#8203;<strong>${path.basename(filePath)}</strong>`;
}

function tableLine(filePath, diff) {
type Diff = {
readonly type: unknown
readonly id: string
readonly code: unknown
readonly message: unknown
}

function tableLine(filePath: string, diff: 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) {
function blobHref(file: unknown) {
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 oldSpec Path to the old swagger specification file.
*
* @param {string} newSpec Path to the new swagger specification file.
* @param newSpec Path to the new swagger specification file.
*/
async function runOad(oldSpec, newSpec) {
async function runOad(oldSpec: string, newSpec: string) {
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.');
}
Expand All @@ -65,7 +76,7 @@ async function runOad(oldSpec, newSpec) {
console.log(`New Spec: "${newSpec}"`);
console.log(`>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>`);

let result = await oad.compare(oldSpec, newSpec, { consoleLogLevel: 'warn', json: true });
let result = await oad.compare(oldSpec, newSpec, { consoleLogLevel: 'warn' });
console.log(result);

if (!result) {
Expand All @@ -81,9 +92,9 @@ async function runOad(oldSpec, newSpec) {
/**
* Processes the given swagger and stores the resolved swagger on to disk
*
* @param {string} swaggerPath Path to the swagger specification file.
* @param swaggerPath Path to the swagger specification file.
*/
async function processViaAutoRest(swaggerPath) {
async function processViaAutoRest(swaggerPath: string) {
if (swaggerPath === null || swaggerPath === undefined || typeof swaggerPath.valueOf() !== 'string' || !swaggerPath.trim().length) {
throw new Error('swaggerPath is a required parameter of type "string" and it cannot be an empty string.');
}
Expand Down Expand Up @@ -115,10 +126,10 @@ async function runScript() {
console.log(swaggersToProcess);

console.log('Finding new swaggers...')
let newSwaggers = [];
let newSwaggers: unknown[] = [];
if (isRunningInTravisCI && swaggersToProcess.length > 0) {
newSwaggers = await utils.doOnBranch(utils.getTargetBranch(), async () => {
return swaggersToProcess.filter(s => !fs.existsSync(s))
return swaggersToProcess.filter((s: string) => !fs.existsSync(s))
});
}

Expand All @@ -133,7 +144,7 @@ async function runScript() {
console.dir(resolvedMapForNewSpecs);

let errors = 0, warnings = 0;
const diffFiles = {};
const diffFiles: stringMap.MutableStringMap<Diff[]> = {};
const newFiles = [];

for (const swagger of swaggersToProcess) {
Expand All @@ -144,8 +155,9 @@ async function runScript() {
continue;
}

if (resolvedMapForNewSpecs[swagger]) {
const diffs = await runOad(swagger, resolvedMapForNewSpecs[swagger]);
const resolved = resolvedMapForNewSpecs[swagger]
if (resolved) {
const diffs = await runOad(swagger, resolved);
if (diffs) {
diffFiles[swagger] = diffs;
for (const diff of diffs) {
Expand Down Expand Up @@ -189,7 +201,7 @@ async function runScript() {

diffFileNames.sort();
for (const swagger of diffFileNames) {
const diffs = diffFiles[swagger];
const diffs = tsUtils.asNonUndefined(diffFiles[swagger]);
diffs.sort((a, b) => {
if (a.type === b.type) {
return a.id.localeCompare(b.id);
Expand Down Expand Up @@ -227,7 +239,7 @@ async function runScript() {
}

// magic starts here
runScript().then(success => {
runScript().then(() => {
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 => {
Expand Down
50 changes: 30 additions & 20 deletions scripts/momentOfTruth.js → scripts/momentOfTruth.ts
Original file line number Diff line number Diff line change
@@ -1,22 +1,30 @@
// Copyright (c) Microsoft Corporation. All rights reserved.
// Licensed under the MIT License. See License.txt in the project root for license information.

'use strict';

const exec = require('child_process').exec,
path = require('path'),
utils = require('../test/util/utils'),
fs = require('fs');
import * as stringMap from '@ts-common/string-map'
import * as tsUtils from './ts-utils'
import { exec } from 'child_process'
import * as path from 'path'
import * as utils from '../test/util/utils'
import * as fs from 'fs'

let configsToProcess = utils.getConfigFilesChangedInPR();
let pullRequestNumber = utils.getPullRequestNumber();
let linterCmd = `npx autorest --validation --azure-validator --message-format=json `;
var filename = `${pullRequestNumber}.json`;
var logFilepath = path.join(getLogDir(), filename);
var finalResult = {};
finalResult["pullRequest"] = pullRequestNumber;
finalResult["repositoryUrl"] = utils.getRepoUrl();
finalResult["files"] = {};

type FinalResult = {
readonly pullRequest: unknown,
readonly repositoryUrl: unknown,
readonly files: stringMap.MutableStringMap<stringMap.MutableStringMap<unknown>>
}

var finalResult: FinalResult = {
pullRequest: pullRequestNumber,
repositoryUrl: utils.getRepoUrl(),
files: {}
}

// Creates and returns path to the logging directory
function getLogDir() {
Expand All @@ -39,12 +47,12 @@ function createLogFile() {
}

//appends the content to the log file
function writeContent(content) {
function writeContent(content: unknown) {
fs.writeFileSync(logFilepath, content);
}

// Executes linter on given swagger path and returns structured JSON of linter output
async function getLinterResult(swaggerPath) {
async function getLinterResult(swaggerPath: string|null|undefined) {
if (swaggerPath === null || swaggerPath === undefined || typeof swaggerPath.valueOf() !== 'string' || !swaggerPath.trim().length) {
throw new Error('swaggerPath is a required parameter of type "string" and it cannot be an empty string.');
}
Expand All @@ -56,7 +64,7 @@ async function getLinterResult(swaggerPath) {
let cmd = "npx autorest --reset && " + linterCmd + swaggerPath;
console.log(`Executing: ${cmd}`);
const { err, stdout, stderr } = await new Promise(res => exec(cmd, { encoding: 'utf8', maxBuffer: 1024 * 1024 * 64 },
(err, stdout, stderr) => res({ err: err, stdout: stdout, stderr: stderr })));
(err: unknown, stdout: unknown, stderr: unknown) => res({ err: err, stdout: stdout, stderr: stderr })));

if (err && stderr.indexOf("Process() cancelled due to exception") !== -1) {
console.error(`AutoRest exited with code ${err.code}`);
Expand Down Expand Up @@ -86,22 +94,24 @@ async function getLinterResult(swaggerPath) {
};

// Run linter tool
async function runTools(swagger, beforeOrAfter) {
async function runTools(swagger: string, beforeOrAfter: string) {
console.log(`Processing "${swagger}":`);
const linterErrors = await getLinterResult(swagger);
console.log(linterErrors);
await updateResult(swagger, linterErrors, beforeOrAfter);
};

// Updates final result json to be written to the output file
async function updateResult(spec, errors, beforeOrAfter) {
if (!finalResult['files'][spec]) {
finalResult['files'][spec] = {};
async function updateResult(spec: string, errors: unknown, beforeOrAfter: string) {
const files = finalResult['files']
if (!files[spec]) {
files[spec] = {};
}
if (!finalResult['files'][spec][beforeOrAfter]) {
finalResult['files'][spec][beforeOrAfter] = {};
const filesSpec = tsUtils.asNonUndefined(files[spec])
if (!filesSpec[beforeOrAfter]) {
filesSpec[beforeOrAfter] = {};
}
finalResult['files'][spec][beforeOrAfter] = errors;
filesSpec[beforeOrAfter] = errors;
}

//main function
Expand Down
4 changes: 4 additions & 0 deletions scripts/ts-utils.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
// Copyright (c) Microsoft Corporation. All rights reserved.
// Licensed under the MIT License. See License in the project root for license information.

export const asNonUndefined = <T>(v: T|undefined) => v as T
Loading