[TypeSpec] Add @azure-tools/typespec-validation tool
- Replaces bash script used in "specs - typespec - validation" pipeline - Also intended for spec authors to run on their dev machines
This commit is contained in:
Родитель
41a6298aba
Коммит
a862f4f217
|
@ -119,3 +119,7 @@ warnings.txt
|
|||
*.js.map
|
||||
*.d.ts.map
|
||||
*.bak
|
||||
|
||||
# Eng Tools
|
||||
eng/tools/TypeSpecValidation/dist
|
||||
!eng/tools/TypeSpecValidation/cmd/*.js
|
||||
|
|
|
@ -5,49 +5,6 @@ parameters:
|
|||
type: string
|
||||
|
||||
steps:
|
||||
- script: |
|
||||
RED='\033[0;31m'
|
||||
exit_code=0
|
||||
expected_npm_prefix=$(pwd)
|
||||
|
||||
# Log commands before running
|
||||
set -x
|
||||
|
||||
pushd ${{parameters.Folder}} || exit_code=1
|
||||
|
||||
actual_npm_prefix=$(npm prefix)
|
||||
|
||||
if [ "$expected_npm_prefix" != "$actual_npm_prefix" ]; then
|
||||
echo -e "\n${RED}ERROR: TypeSpec folders MUST NOT contain a package.json, and instead MUST rely on the package.json at repo root."
|
||||
echo -e "${RED}Expected npm prefix: $expected_npm_prefix\n${RED}Actual npm prefix: $actual_npm_prefix\n"
|
||||
exit_code=1
|
||||
fi
|
||||
|
||||
# Pass "--no" to ensure npx does not install anything (TypeSpec compiler should already be installed)
|
||||
|
||||
if test -f main.tsp; then
|
||||
npx --no tsp compile . --warn-as-error || exit_code=1
|
||||
fi
|
||||
if test -f client.tsp; then
|
||||
npx --no tsp compile client.tsp --no-emit --warn-as-error || exit_code=1
|
||||
fi
|
||||
|
||||
# Format parent folder to include shared files
|
||||
npx tsp format ../**/*.tsp
|
||||
|
||||
popd
|
||||
|
||||
# If any files are added, changed, or deleted, print diff and return error
|
||||
if [ -n "$(git status --porcelain)" ]; then
|
||||
git status
|
||||
git diff
|
||||
exit_code=1
|
||||
fi
|
||||
|
||||
# Revert any changes
|
||||
git restore .
|
||||
git clean -df
|
||||
|
||||
exit $exit_code
|
||||
- script: npx --no tsv ${{parameters.Folder}}
|
||||
displayName: ${{parameters.DisplayName}}
|
||||
condition: succeededOrFailed()
|
||||
|
|
|
@ -0,0 +1,5 @@
|
|||
#!/usr/bin/env node
|
||||
|
||||
import { main } from "../dist/TypeSpecValidation.js"
|
||||
|
||||
await main();
|
|
@ -0,0 +1,16 @@
|
|||
{
|
||||
"name": "@azure-tools/typespec-validation",
|
||||
"private": true,
|
||||
"version": "0.0.1",
|
||||
"type": "module",
|
||||
"main": "dist/TypeSpecValidation.js",
|
||||
"bin": {
|
||||
"tsv": "cmd/tsv.js"
|
||||
},
|
||||
"dependencies": {
|
||||
"simple-git": "^3.16.0"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@types/node": "^18.16.18"
|
||||
}
|
||||
}
|
|
@ -0,0 +1,88 @@
|
|||
import {exec} from "child_process";
|
||||
import {access} from "fs/promises"
|
||||
import {parseArgs, ParseArgsConfig} from 'node:util';
|
||||
import path from "path";
|
||||
import {simpleGit} from 'simple-git';
|
||||
|
||||
async function runCmd(cmd:string, cwd:string) {
|
||||
console.log(`run command:${cmd}`)
|
||||
const { err, stdout, stderr } = await new Promise((res) =>
|
||||
exec(
|
||||
cmd,
|
||||
{ encoding: "utf8", maxBuffer: 1024 * 1024 * 64, cwd: cwd},
|
||||
(err: unknown, stdout: unknown, stderr: unknown) =>
|
||||
res({ err: err, stdout: stdout, stderr: stderr })
|
||||
)
|
||||
) as any;
|
||||
let resultString = stderr + stdout;
|
||||
console.log("Stdout output:")
|
||||
console.log(stdout)
|
||||
if (stderr) {
|
||||
console.log("Stderr output:")
|
||||
console.log(stderr)
|
||||
}
|
||||
if (stderr || err) {
|
||||
throw new Error(err);
|
||||
}
|
||||
return resultString as string;
|
||||
}
|
||||
|
||||
async function checkFileExists(file:string) {
|
||||
return access(file)
|
||||
.then(() => true)
|
||||
.catch(() => false)
|
||||
}
|
||||
|
||||
export async function main() {
|
||||
const args = process.argv.slice(2);
|
||||
const options = {
|
||||
folder: {
|
||||
type: 'string',
|
||||
short: 'f',
|
||||
},
|
||||
};
|
||||
const parsedArgs = parseArgs({ args, options, allowPositionals: true} as ParseArgsConfig);
|
||||
const folder = parsedArgs.positionals[0];
|
||||
console.log("Running TypeSpecValidation on folder:", folder);
|
||||
|
||||
// Verify all specs are using root level pacakge.json
|
||||
let expected_npm_prefix = process.cwd()
|
||||
const actual_npm_prefix = (await runCmd(`npm prefix`, folder)).trim()
|
||||
if (expected_npm_prefix !== actual_npm_prefix) {
|
||||
console.log("ERROR: TypeSpec folders MUST NOT contain a package.json, and instead MUST rely on the package.json at repo root.")
|
||||
throw new Error ("Expected npm prefix: " + expected_npm_prefix +"\nActual npm prefix: " + actual_npm_prefix)
|
||||
}
|
||||
|
||||
// Spec compilation check
|
||||
if (await checkFileExists(path.join(folder, "main.tsp"))) {
|
||||
await runCmd(
|
||||
`npx --no tsp compile . --warn-as-error`,
|
||||
folder
|
||||
);
|
||||
}
|
||||
if (await checkFileExists(path.join(folder, "client.tsp"))) {
|
||||
await runCmd(
|
||||
`npx --no tsp compile client.tsp --no-emit --warn-as-error`,
|
||||
folder
|
||||
);
|
||||
}
|
||||
|
||||
// Format parent folder to include shared files
|
||||
await runCmd(
|
||||
`npx tsp format ../**/*.tsp`,
|
||||
folder
|
||||
);
|
||||
|
||||
// Verify generated swagger file is in sync with one on disk
|
||||
const git = simpleGit();
|
||||
let gitStatusIsClean = await (await git.status(['--porcelain'])).isClean()
|
||||
if (!gitStatusIsClean) {
|
||||
let gitStatus = await git.status()
|
||||
let gitDiff = await git.diff()
|
||||
console.log("git status")
|
||||
console.log(gitStatus)
|
||||
console.log("git diff")
|
||||
console.log(gitDiff)
|
||||
throw new Error("Generated swagger file does not match swagger file on disk")
|
||||
}
|
||||
}
|
|
@ -0,0 +1,8 @@
|
|||
{
|
||||
"extends": "../../../tsconfig.json",
|
||||
"compilerOptions": {
|
||||
"target": "ES6",
|
||||
"module": "Node16",
|
||||
"outDir": "./dist"
|
||||
}
|
||||
}
|
|
@ -5,6 +5,7 @@
|
|||
"packages": {
|
||||
"": {
|
||||
"name": "azure-rest-api-specs",
|
||||
"hasInstallScript": true,
|
||||
"devDependencies": {
|
||||
"@azure-tools/cadl-apiview": "0.3.5",
|
||||
"@azure-tools/cadl-autorest": "0.26.0",
|
||||
|
@ -25,9 +26,30 @@
|
|||
"@typespec/rest": "0.45.0",
|
||||
"@typespec/versioning": "0.45.0",
|
||||
"prettier": "^2.8.8",
|
||||
"typescript": "~5.0.4"
|
||||
"typescript": "~5.0.4",
|
||||
"typespec-validation": "file:eng/tools/TypeSpecValidation"
|
||||
}
|
||||
},
|
||||
"eng/tools/TypeSpecValidation": {
|
||||
"name": "@azure-tools/typespec-validation",
|
||||
"version": "0.0.1",
|
||||
"dev": true,
|
||||
"dependencies": {
|
||||
"simple-git": "^3.16.0"
|
||||
},
|
||||
"bin": {
|
||||
"tsv": "cmd/tsv.js"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@types/node": "^18.16.18"
|
||||
}
|
||||
},
|
||||
"eng/tools/TypeSpecValidation/node_modules/@types/node": {
|
||||
"version": "18.16.18",
|
||||
"resolved": "https://registry.npmjs.org/@types/node/-/node-18.16.18.tgz",
|
||||
"integrity": "sha512-/aNaQZD0+iSBAGnvvN2Cx92HqE5sZCPZtx2TsK+4nvV23fFe09jVDvpArXr2j9DnYlzuU9WuoykDDc6wqvpNcw==",
|
||||
"dev": true
|
||||
},
|
||||
"node_modules/@azure-tools/cadl-apiview": {
|
||||
"version": "0.3.5",
|
||||
"resolved": "https://registry.npmjs.org/@azure-tools/cadl-apiview/-/cadl-apiview-0.3.5.tgz",
|
||||
|
@ -570,6 +592,21 @@
|
|||
"node": ">=16.0.0"
|
||||
}
|
||||
},
|
||||
"node_modules/@kwsites/file-exists": {
|
||||
"version": "1.1.1",
|
||||
"resolved": "https://registry.npmjs.org/@kwsites/file-exists/-/file-exists-1.1.1.tgz",
|
||||
"integrity": "sha512-m9/5YGR18lIwxSFDwfE3oA7bWuq9kdau6ugN4H2rJeyhFQZcG9AgSHkQtSD15a8WvTgfz9aikZMrKPHvbpqFiw==",
|
||||
"dev": true,
|
||||
"dependencies": {
|
||||
"debug": "^4.1.1"
|
||||
}
|
||||
},
|
||||
"node_modules/@kwsites/promise-deferred": {
|
||||
"version": "1.1.1",
|
||||
"resolved": "https://registry.npmjs.org/@kwsites/promise-deferred/-/promise-deferred-1.1.1.tgz",
|
||||
"integrity": "sha512-GaHYm+c0O9MjZRu0ongGBRbinu8gVAMd2UZjji6jVmqKtZluZnptXGWhz1E8j8D2HJ3f/yMxKAUC0b+57wncIw==",
|
||||
"dev": true
|
||||
},
|
||||
"node_modules/@nodelib/fs.scandir": {
|
||||
"version": "2.1.5",
|
||||
"resolved": "https://registry.npmjs.org/@nodelib/fs.scandir/-/fs.scandir-2.1.5.tgz",
|
||||
|
@ -1392,6 +1429,23 @@
|
|||
"node": ">= 12"
|
||||
}
|
||||
},
|
||||
"node_modules/debug": {
|
||||
"version": "4.3.4",
|
||||
"resolved": "https://registry.npmjs.org/debug/-/debug-4.3.4.tgz",
|
||||
"integrity": "sha512-PRWFHuSU3eDtQJPvnNY7Jcket1j0t5OuOsFzPPzsekD52Zl8qUfFIPEiswXqIvHWGVHOgX+7G/vCNNhehwxfkQ==",
|
||||
"dev": true,
|
||||
"dependencies": {
|
||||
"ms": "2.1.2"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=6.0"
|
||||
},
|
||||
"peerDependenciesMeta": {
|
||||
"supports-color": {
|
||||
"optional": true
|
||||
}
|
||||
}
|
||||
},
|
||||
"node_modules/decamelize": {
|
||||
"version": "1.2.0",
|
||||
"resolved": "https://registry.npmjs.org/decamelize/-/decamelize-1.2.0.tgz",
|
||||
|
@ -1935,6 +1989,12 @@
|
|||
"node": ">=10"
|
||||
}
|
||||
},
|
||||
"node_modules/ms": {
|
||||
"version": "2.1.2",
|
||||
"resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz",
|
||||
"integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==",
|
||||
"dev": true
|
||||
},
|
||||
"node_modules/mustache": {
|
||||
"version": "4.2.0",
|
||||
"resolved": "https://registry.npmjs.org/mustache/-/mustache-4.2.0.tgz",
|
||||
|
@ -2267,9 +2327,9 @@
|
|||
}
|
||||
},
|
||||
"node_modules/semver": {
|
||||
"version": "7.5.2",
|
||||
"resolved": "https://registry.npmjs.org/semver/-/semver-7.5.2.tgz",
|
||||
"integrity": "sha512-SoftuTROv/cRjCze/scjGyiDtcUyxw1rgYQSZY7XTmtR5hX+dm76iDbTH8TkLPHCQmlbQVSSbNZCPM2hb0knnQ==",
|
||||
"version": "7.5.3",
|
||||
"resolved": "https://registry.npmjs.org/semver/-/semver-7.5.3.tgz",
|
||||
"integrity": "sha512-QBlUtyVk/5EeHbi7X0fw6liDZc7BBmEaSYn01fMU1OUYbf6GPsbTtd8WmnqbI20SeycoHSeiybkE/q1Q+qlThQ==",
|
||||
"dev": true,
|
||||
"dependencies": {
|
||||
"lru-cache": "^6.0.0"
|
||||
|
@ -2304,6 +2364,21 @@
|
|||
"integrity": "sha512-vFwSUfQvqybiICwZY5+DAWIPLKsWO31Q91JSKl3UYv+K5c2QRPzn0qzec6QPu1Qc9eHYItiP3NdJqNVqetYAww==",
|
||||
"dev": true
|
||||
},
|
||||
"node_modules/simple-git": {
|
||||
"version": "3.19.1",
|
||||
"resolved": "https://registry.npmjs.org/simple-git/-/simple-git-3.19.1.tgz",
|
||||
"integrity": "sha512-Ck+rcjVaE1HotraRAS8u/+xgTvToTuoMkT9/l9lvuP5jftwnYUp6DwuJzsKErHgfyRk8IB8pqGHWEbM3tLgV1w==",
|
||||
"dev": true,
|
||||
"dependencies": {
|
||||
"@kwsites/file-exists": "^1.1.1",
|
||||
"@kwsites/promise-deferred": "^1.1.1",
|
||||
"debug": "^4.3.4"
|
||||
},
|
||||
"funding": {
|
||||
"type": "github",
|
||||
"url": "https://github.com/steveukx/git-js?sponsor=1"
|
||||
}
|
||||
},
|
||||
"node_modules/sisteransi": {
|
||||
"version": "1.0.5",
|
||||
"resolved": "https://registry.npmjs.org/sisteransi/-/sisteransi-1.0.5.tgz",
|
||||
|
@ -2401,9 +2476,9 @@
|
|||
"dev": true
|
||||
},
|
||||
"node_modules/tslib": {
|
||||
"version": "2.5.3",
|
||||
"resolved": "https://registry.npmjs.org/tslib/-/tslib-2.5.3.tgz",
|
||||
"integrity": "sha512-mSxlJJwl3BMEQCUNnxXBU9jP4JBktcEGhURcPR6VQVlnP0FdDEsIaz0C35dXNGLyRfrATNofF0F5p2KPxQgB+w==",
|
||||
"version": "2.6.0",
|
||||
"resolved": "https://registry.npmjs.org/tslib/-/tslib-2.6.0.tgz",
|
||||
"integrity": "sha512-7At1WUettjcSRHXCyYtTselblcHl9PJFFVKiCAy/bY97+BPZXSQ2wbq0P9s8tK2G7dFQfNnlJnPAiArVBVBsfA==",
|
||||
"dev": true
|
||||
},
|
||||
"node_modules/typescript": {
|
||||
|
@ -2419,6 +2494,10 @@
|
|||
"node": ">=12.20"
|
||||
}
|
||||
},
|
||||
"node_modules/typespec-validation": {
|
||||
"resolved": "eng/tools/TypeSpecValidation",
|
||||
"link": true
|
||||
},
|
||||
"node_modules/upper-case": {
|
||||
"version": "2.0.2",
|
||||
"resolved": "https://registry.npmjs.org/upper-case/-/upper-case-2.0.2.tgz",
|
||||
|
|
|
@ -20,7 +20,11 @@
|
|||
"@azure/avocado": "^0.8.4",
|
||||
"@types/prettier": "^2.7.2",
|
||||
"prettier": "^2.8.8",
|
||||
"typescript": "~5.0.4"
|
||||
"typescript": "~5.0.4",
|
||||
"typespec-validation": "file:eng/tools/TypeSpecValidation"
|
||||
},
|
||||
"scripts": {
|
||||
"postinstall": "npx --no tsc -- -p eng/tools/TypeSpecValidation"
|
||||
},
|
||||
"private": true
|
||||
}
|
||||
|
|
Загрузка…
Ссылка в новой задаче