Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
31 commits
Select commit Hold shift + click to select a range
24ddfcc
remove dep @azure/avocado
mikeharder Oct 14, 2025
695ead4
add avocado to eng/tools/tsconfig.json
mikeharder Oct 14, 2025
ecf6801
add package.json
mikeharder Oct 14, 2025
4e7655d
add tsconfig.json
mikeharder Oct 14, 2025
2d63d09
copy source files from Azure/avocado
mikeharder Oct 14, 2025
6cec1a2
update src for new prettier config
mikeharder Oct 14, 2025
b295add
update tsconfig.json for prettier
mikeharder Oct 14, 2025
461ac30
add eslint.config.js
mikeharder Oct 14, 2025
e0bfbcd
remove unused suppression
mikeharder Oct 14, 2025
fce511e
copy folder src/test from Azure/avocado
mikeharder Oct 14, 2025
2f2a72f
add cmd script
mikeharder Oct 14, 2025
e163d9c
add missing test
mikeharder Oct 14, 2025
89fa7ca
add missing tests
mikeharder Oct 14, 2025
1ee3182
rename tests
mikeharder Oct 14, 2025
7fa41b0
format
mikeharder Oct 14, 2025
7d8ee4f
format
mikeharder Oct 14, 2025
71d723d
add workflow avocado-test
mikeharder Oct 14, 2025
3e1491d
delete tmp folder
mikeharder Oct 14, 2025
7b512ec
rename job
mikeharder Oct 14, 2025
e0e00c4
add avocado to eng/tools/package.json
mikeharder Oct 14, 2025
7877124
npm i
mikeharder Oct 14, 2025
d77b696
move test outside src
mikeharder Oct 14, 2025
babeea9
update test code for paths
mikeharder Oct 14, 2025
75a0144
remove avocado-tmp
mikeharder Oct 14, 2025
32a4284
move test specs to "fixtures" folder
mikeharder Oct 14, 2025
38bb256
delete avocado-tmp
mikeharder Oct 14, 2025
7acaae5
fix paths in cmd script
mikeharder Oct 14, 2025
94a8ebc
increase test timeout
mikeharder Oct 14, 2025
087bbdb
replace dependency yargs with built-in util.parseArgs
mikeharder Oct 15, 2025
790ae88
[getInputFilesFromReadme] Return no files, instead of throwing, on in…
mikeharder Oct 15, 2025
649c893
Merge branch 'main' into migrate-avocado
mikeharder Oct 16, 2025
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
28 changes: 28 additions & 0 deletions .github/workflows/avocado-test.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
name: Avocado - Test

on:
push:
branches:
- main
- typespec-next
pull_request:
paths:
- package-lock.json
- package.json
- tsconfig.json
- .github/workflows/_reusable-eng-tools-test.yaml
- .github/workflows/avocado-test.yaml
- eng/tools/package.json
- eng/tools/tsconfig.json
- eng/tools/avocado/**
workflow_dispatch:

permissions:
contents: read

jobs:
avocado:
uses: ./.github/workflows/_reusable-eng-tools-test.yaml
with:
package: avocado
lint: true
71 changes: 71 additions & 0 deletions eng/tools/avocado/cmd/avocado.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,71 @@
#!/usr/bin/env node

import { parseArgs } from "util";
import { run } from "../dist/src/cli.js";
import { avocado, UnifiedPipelineReport } from "../dist/src/index.js";

// Define help text
const showHelp = () => {
console.log(`Usage: avocado [options]

Options:
-f, --file output detail result to log file
-d, --dir run avocado under directory
--excludePaths array contains path patterns to be ignored [array]
--includePaths array contains path patterns to be included. If this option is
not set, all files will be included. If this option is set,
only files that match at least one pattern will be included
[array]
-h, --help Show help [boolean]`);
};

// Define parseArgs configuration
const options = {
file: {
type: "string",
short: "f",
},
dir: {
type: "string",
short: "d",
},
excludePaths: {
type: "string",
multiple: true,
},
includePaths: {
type: "string",
multiple: true,
},
help: {
type: "boolean",
short: "h",
},
};

// Parse arguments
let parsedArgs;
try {
parsedArgs = parseArgs({
options,
allowPositionals: true,
});
} catch (error) {
console.error(`Error: ${error.message}`);
showHelp();
process.exit(1);
}

const { values: argv } = parsedArgs;

// Handle help
if (argv.help) {
showHelp();
process.exit(0);
}

run(avocado, UnifiedPipelineReport(argv.file), {
cwd: process.cwd(),
env: process.env,
args: { dir: argv.dir, excludePaths: argv.excludePaths, includePaths: argv.includePaths },
});
18 changes: 18 additions & 0 deletions eng/tools/avocado/eslint.config.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
import eslint from "@eslint/js";
import { defineConfig } from "eslint/config";
import globals from "globals";
import tseslint from "typescript-eslint";

/** @type {import('eslint').Linter.Config[]} */
export default defineConfig(
{ ignores: ["dist/**"] },
eslint.configs.recommended,
tseslint.configs.recommended,
{
languageOptions: { globals: globals.node },
rules: {
// 17 errors
"@typescript-eslint/no-explicit-any": "off",
},
},
);
50 changes: 50 additions & 0 deletions eng/tools/avocado/package.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,50 @@
{
"name": "@azure-tools/avocado",
"private": true,
"type": "module",
"main": "dist/src/index.js",
"bin": {
"avocado": "cmd/avocado.js"
},
"scripts": {
"build": "tsc --build",
"format": "prettier . --ignore-path ../.prettierignore --write",
"format:check": "prettier . --ignore-path ../.prettierignore --check",
"format:check:ci": "prettier . --ignore-path ../.prettierignore --check --log-level debug",
"lint": "cross-env DEBUG=eslint:eslint eslint",
"test": "vitest",
"test:ci": "vitest run --coverage --reporter=verbose"
},
"engines": {
"node": ">=20.0.0"
},
"dependencies": {
"@azure/openapi-markdown": "0.9.4",
"@azure/swagger-validation-common": "0.1.2",
"@ts-common/async-iterator": "^1.1.0",
"@ts-common/commonmark-to-markdown": "^2.0.2",
"@ts-common/fs": "^1.1.0",
"@ts-common/iterator": "^1.1.2",
"@ts-common/json": "^1.1.0",
"@ts-common/json-parser": "^1.1.0",
"@ts-common/string-map": "^1.1.1",
"commonmark": "0.31.2",
"glob": "^11.0.3",
"js-yaml": "^4.1.0",
"jsonpath-plus": "^10.0.0",
"node-object-hash": "^3.1.1"
},
"devDependencies": {
"@eslint/js": "^9.37.0",
"@types/js-yaml": "^4.0.9",
"@types/node": "^20.0.0",
"@vitest/coverage-v8": "^3.2.4",
"cross-env": "^10.1.0",
"eslint": "^9.37.0",
"prettier": "~3.6.2",
"prettier-plugin-organize-imports": "^4.3.0",
"typescript": "~5.9.3",
"typescript-eslint": "^8.46.0",
"vitest": "^3.2.4"
}
}
23 changes: 23 additions & 0 deletions eng/tools/avocado/src/child-process.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
// Copyright (c) Microsoft Corporation. All rights reserved.
// Licensed under the MIT License. See LICENSE in the project root for license information.

import * as childProcess from "child_process";
import * as util from "util";

const nodeJsExec = util.promisify(childProcess.exec);

export type ExecResult = {
/**
* Standard Output
*/
readonly stdout: string;
/**
* Standard Error
*/
readonly stderr: string;
};

export const exec = (
command: string,
options: childProcess.ExecOptionsWithStringEncoding,
): Promise<ExecResult> => nodeJsExec(command, { maxBuffer: Infinity, ...options });
83 changes: 83 additions & 0 deletions eng/tools/avocado/src/cli.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,83 @@
// Copyright (c) Microsoft Corporation. All rights reserved.
// Licensed under the MIT License. See LICENSE in the project root for license information.

import * as stringMap from "@ts-common/string-map";
import { IErrorBase } from "./errors.js";

export type Report = {
/**
* This is a callback function to report validation tools result.
*/
readonly logResult: (error: any) => void;
/**
* This is a callback function to report validation tools exception.
*/
readonly logError: (error: any) => void;
/**
* This is a callback function to report an info.
*/
readonly logInfo: (info: any) => void;
};

export type Config = {
/**
* Current working directory.
*/
readonly cwd: string;
/**
* Environment variables.
*/
readonly env: stringMap.StringMap<string>;
/**
* Arguments
*/
readonly args?: stringMap.StringMap<any>;
};

export const defaultConfig = () => ({
cwd: process.cwd(),
env: process.env,
args: {},
});

export const isAzurePipelineEnv = (): boolean =>
process.env.SYSTEM_PULLREQUEST_TARGETBRANCH !== undefined;

/**
* The function executes the given `tool` and prints errors to `stderr`.
*
* @param tool is a function which returns errors as `AsyncIterable`.
*/
export const run = async <T extends IErrorBase>(
tool: (config: Config) => AsyncIterable<T>,
report: Report = { logResult: console.log, logError: console.error, logInfo: console.log },
config: Config = defaultConfig(),
): Promise<void> => {
try {
const errors = tool(config);
let errorsNumber = 0;
for await (const e of errors) {
errorsNumber += e.level !== "Warning" && e.level !== "Info" ? 1 : 0;
report.logResult(e);
}
report.logInfo(`errors: ${errorsNumber}`);
if (errorsNumber > 0) {
if (isAzurePipelineEnv()) {
console.log("##vso[task.setVariable variable=ValidationResult]failure");
}
process.exitCode = 1;
} else {
if (isAzurePipelineEnv()) {
console.log("##vso[task.setVariable variable=ValidationResult]success");
}
process.exitCode = 0;
}
} catch (e) {
report.logInfo(`INTERNAL ERROR`);
if (isAzurePipelineEnv()) {
console.log("##vso[task.setVariable variable=ValidationResult]failure");
}
report.logError(e);
process.exitCode = 1;
}
};
Loading