Use @microsoft/vally library directly instead of vally-cli subprocess

Replace the npx-spawned vally-cli process with a direct call to the
@microsoft/vally core library in the external plugin quality gates scripts:

- Add @microsoft/vally as a devDependency in package.json
- Import runLint and LintConsoleReporter from @microsoft/vally
- Replace runVallyLintGate() process spawn with async API call:
  - runLint({ rootPath }) returns structured LintResults
  - LintConsoleReporter with a Writable capture stream collects
    text output without printing to stdout
- Make runExternalPluginQualityGates() async (propagated to
  runExternalPluginPrQualityGates() and both main entry points)
- Use Promise.all in runExternalPluginPrQualityGates() for parallel
  plugin checks
- Fix remaining skill_validator_status reference in pr-quality-gates
  summary string (now vally-lint=...) and YAML workflow table header
- Add 'npm install @microsoft/vally' step to both calling workflows

This removes a layer of indirection (Node -> npx -> CLI -> library)
and replaces it with a direct in-process library call, which is faster,
more reliable, and gives structured access to lint results.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
This commit is contained in:
Aaron Powell
2026-06-23 16:24:02 +10:00
parent ee39347184
commit dabd0ba0f1
6 changed files with 297 additions and 18 deletions
@@ -95,6 +95,9 @@ jobs:
- name: Install GitHub Copilot CLI
run: npm install -g @github/copilot
- name: Install @microsoft/vally
run: npm install @microsoft/vally
- name: Run external plugin PR quality gates
id: quality
env:
@@ -205,7 +208,7 @@ jobs:
const sourceUrl = String(entry?.source_tree_url || '');
const locator = String(entry?.source?.sha || entry?.source?.ref || 'repository');
const sourceCell = sourceUrl ? `[${locator}](${sourceUrl})` : locator;
return `| ${name} | ${quality.skill_validator_status || 'not_run'} | ${quality.smoke_status || 'not_run'} | ${quality.overall_status || 'not_run'} | ${sourceCell} |`;
return `| ${name} | ${quality.vally_lint_status || 'not_run'} | ${quality.smoke_status || 'not_run'} | ${quality.overall_status || 'not_run'} | ${sourceCell} |`;
})
: ['| _none_ | not_run | not_run | not_run | _n/a_ |'];
@@ -218,7 +221,7 @@ jobs:
'',
'### Per-plugin quality summary',
'',
'| Plugin | skill-validator | install smoke test | overall | source tree |',
'| Plugin | vally lint | install smoke test | overall | source tree |',
'|---|---|---|---|---|',
...rows,
'',
@@ -36,6 +36,9 @@ jobs:
- name: Install GitHub Copilot CLI
run: npm install -g @github/copilot
- name: Install @microsoft/vally
run: npm install @microsoft/vally
- name: Run external plugin quality gates
id: quality
env:
+6 -6
View File
@@ -66,27 +66,27 @@ function aggregateResultStatus(pluginResults) {
};
}
export function runExternalPluginPrQualityGates(plugins) {
export async function runExternalPluginPrQualityGates(plugins) {
if (!Array.isArray(plugins)) {
throw new Error("plugins must be an array");
}
const checkedPlugins = plugins.map((plugin) => {
const quality = runExternalPluginQualityGates(plugin);
const checkedPlugins = await Promise.all(plugins.map(async (plugin) => {
const quality = await runExternalPluginQualityGates(plugin);
return {
name: plugin?.name ?? "unknown",
source: plugin?.source ?? {},
source_tree_url: buildSourceTreeUrl(plugin),
quality,
};
});
}));
const aggregate = aggregateResultStatus(checkedPlugins);
const summary = checkedPlugins.length === 0
? "No changed external plugin entries were detected in plugins/external.json."
: checkedPlugins
.map((entry) =>
`- ${entry.name}: skill-validator=${entry.quality.skill_validator_status}, install-smoke=${entry.quality.smoke_status}, overall=${entry.quality.overall_status}`
`- ${entry.name}: vally-lint=${entry.quality.vally_lint_status}, install-smoke=${entry.quality.smoke_status}, overall=${entry.quality.overall_status}`
)
.join("\n");
@@ -120,6 +120,6 @@ if (import.meta.url === `file://${process.argv[1]}`) {
}
const plugins = JSON.parse(args["plugins-json"]);
const result = runExternalPluginPrQualityGates(plugins);
const result = await runExternalPluginPrQualityGates(plugins);
process.stdout.write(`${JSON.stringify(result)}\n`);
}
+20 -10
View File
@@ -3,7 +3,9 @@
import fs from "fs";
import os from "os";
import path from "path";
import { Writable } from "stream";
import { spawnSync } from "child_process";
import { runLint, LintConsoleReporter } from "@microsoft/vally";
const MAX_OUTPUT_LENGTH = 12000;
@@ -182,7 +184,7 @@ function buildVallyLintArgs(pluginRoot) {
return [pluginRoot];
}
function runVallyLintGate(pluginRoot) {
async function runVallyLintGate(pluginRoot) {
try {
const targets = buildVallyLintArgs(pluginRoot);
@@ -190,12 +192,20 @@ function runVallyLintGate(pluginRoot) {
let anyFailure = false;
for (const target of targets) {
const check = runCommand(
"npx",
["--yes", "@microsoft/vally-cli", "lint", target, "--verbose"],
);
combinedOutput += check.output + "\n";
if (check.exitCode !== 0) {
const chunks = [];
const captureStream = new Writable({
write(chunk, _encoding, callback) {
chunks.push(chunk.toString());
callback();
},
});
const result = await runLint({ rootPath: target });
const reporter = new LintConsoleReporter({ verbose: true, stream: captureStream });
await reporter.report(result);
combinedOutput += chunks.join("") + "\n";
if (!result.passed) {
anyFailure = true;
}
}
@@ -318,7 +328,7 @@ function toFailureClass(overallStatus) {
return "none";
}
export function runExternalPluginQualityGates(plugin) {
export async function runExternalPluginQualityGates(plugin) {
const workDir = fs.mkdtempSync(path.join(os.tmpdir(), "external-plugin-quality-"));
const result = {
overall_status: "not_run",
@@ -344,7 +354,7 @@ export function runExternalPluginQualityGates(plugin) {
return result;
}
const vallyResult = runVallyLintGate(pluginRoot);
const vallyResult = await runVallyLintGate(pluginRoot);
result.vally_lint_status = vallyResult.status;
result.vally_lint_output = vallyResult.output;
@@ -394,6 +404,6 @@ if (import.meta.url === `file://${process.argv[1]}`) {
}
const plugin = JSON.parse(args["plugin-json"]);
const result = runExternalPluginQualityGates(plugin);
const result = await runExternalPluginQualityGates(plugin);
process.stdout.write(`${JSON.stringify(result)}\n`);
}
+262
View File
@@ -14,6 +14,7 @@
"vfile-matter": "^5.0.1"
},
"devDependencies": {
"@microsoft/vally": "^0.6.0",
"all-contributors-cli": "^6.26.1"
}
},
@@ -27,6 +28,193 @@
"node": ">=6.9.0"
}
},
"node_modules/@github/copilot": {
"version": "1.0.64-3",
"resolved": "https://registry.npmjs.org/@github/copilot/-/copilot-1.0.64-3.tgz",
"integrity": "sha512-Q9nBMYEHX1bJLXzzJocQx2nZvORJ0E9gvK6ly/FCtmtA7ad96BWZvf4EHzkCNDsn56aI3zNaUSfKHUKcmIAzSg==",
"dev": true,
"license": "SEE LICENSE IN LICENSE.md",
"dependencies": {
"detect-libc": "^2.1.2"
},
"bin": {
"copilot": "npm-loader.js"
},
"optionalDependencies": {
"@github/copilot-darwin-arm64": "1.0.64-3",
"@github/copilot-darwin-x64": "1.0.64-3",
"@github/copilot-linux-arm64": "1.0.64-3",
"@github/copilot-linux-x64": "1.0.64-3",
"@github/copilot-linuxmusl-arm64": "1.0.64-3",
"@github/copilot-linuxmusl-x64": "1.0.64-3",
"@github/copilot-win32-arm64": "1.0.64-3",
"@github/copilot-win32-x64": "1.0.64-3"
}
},
"node_modules/@github/copilot-darwin-arm64": {
"version": "1.0.64-3",
"resolved": "https://registry.npmjs.org/@github/copilot-darwin-arm64/-/copilot-darwin-arm64-1.0.64-3.tgz",
"integrity": "sha512-wlV6mRoAd/wG2V08TG+BOJ0nyOjroya24FSyA5A49z7PnUUuQXYRpa/GljvI5j3PM8aUl0DyBkXuB/DcFU818g==",
"cpu": [
"arm64"
],
"dev": true,
"license": "SEE LICENSE IN LICENSE.md",
"optional": true,
"os": [
"darwin"
],
"bin": {
"copilot-darwin-arm64": "copilot"
}
},
"node_modules/@github/copilot-darwin-x64": {
"version": "1.0.64-3",
"resolved": "https://registry.npmjs.org/@github/copilot-darwin-x64/-/copilot-darwin-x64-1.0.64-3.tgz",
"integrity": "sha512-mk48PIESL2keeemX7tLRmWRDxKwl0q3cFI1ORD2QcrieNK7pSqI3eVbfoB7MqoUUI27yzIkl67xqgl8Qq28IUQ==",
"cpu": [
"x64"
],
"dev": true,
"license": "SEE LICENSE IN LICENSE.md",
"optional": true,
"os": [
"darwin"
],
"bin": {
"copilot-darwin-x64": "copilot"
}
},
"node_modules/@github/copilot-linux-arm64": {
"version": "1.0.64-3",
"resolved": "https://registry.npmjs.org/@github/copilot-linux-arm64/-/copilot-linux-arm64-1.0.64-3.tgz",
"integrity": "sha512-rCgtK3/rofQW5StSbeU0TwDUlOl2bvS2HGKyapVxow1Nvz3Q/TDB+eFRQc5ocBdv5tNSor+Caw2JGkRx5v508w==",
"cpu": [
"arm64"
],
"dev": true,
"license": "SEE LICENSE IN LICENSE.md",
"optional": true,
"os": [
"linux"
],
"bin": {
"copilot-linux-arm64": "copilot"
}
},
"node_modules/@github/copilot-linux-x64": {
"version": "1.0.64-3",
"resolved": "https://registry.npmjs.org/@github/copilot-linux-x64/-/copilot-linux-x64-1.0.64-3.tgz",
"integrity": "sha512-FAiBMw1h07mURSBLi3ztj5yzbP+uTbo9mhxOym1Xysut5LDpO2kYUzTYk2DlIyLGZhmH/HDOZE+b6U7lOUQy0g==",
"cpu": [
"x64"
],
"dev": true,
"license": "SEE LICENSE IN LICENSE.md",
"optional": true,
"os": [
"linux"
],
"bin": {
"copilot-linux-x64": "copilot"
}
},
"node_modules/@github/copilot-linuxmusl-arm64": {
"version": "1.0.64-3",
"resolved": "https://registry.npmjs.org/@github/copilot-linuxmusl-arm64/-/copilot-linuxmusl-arm64-1.0.64-3.tgz",
"integrity": "sha512-vn8P6grPf0y2mNskkdVbAz0i46b1sP9uSXv7z6kgycjprl0CdIYPDf3WEkG60vpyopfQna+iCqCLMWRnNyCk3g==",
"cpu": [
"arm64"
],
"dev": true,
"license": "SEE LICENSE IN LICENSE.md",
"optional": true,
"os": [
"linux"
],
"bin": {
"copilot-linuxmusl-arm64": "copilot"
}
},
"node_modules/@github/copilot-linuxmusl-x64": {
"version": "1.0.64-3",
"resolved": "https://registry.npmjs.org/@github/copilot-linuxmusl-x64/-/copilot-linuxmusl-x64-1.0.64-3.tgz",
"integrity": "sha512-atdHimNd6nzRRwHybXUY6/84bYzXeKbDOeYN/N/DsX23+AQOPSu5BD8MD8166I/5kNHui0XOmeTSydVNBUwcJw==",
"cpu": [
"x64"
],
"dev": true,
"license": "SEE LICENSE IN LICENSE.md",
"optional": true,
"os": [
"linux"
],
"bin": {
"copilot-linuxmusl-x64": "copilot"
}
},
"node_modules/@github/copilot-sdk": {
"version": "1.0.3",
"resolved": "https://registry.npmjs.org/@github/copilot-sdk/-/copilot-sdk-1.0.3.tgz",
"integrity": "sha512-ujnH2QVw3+xvjgo9cbpY0wik4fNxAmdMDSFnxGScDSvRuK2vUCL2xWW4V2ANc9pWwRHPBpEpMuNJMtmydmLCIQ==",
"dev": true,
"license": "MIT",
"dependencies": {
"@github/copilot": "^1.0.64-1",
"vscode-jsonrpc": "^8.2.1",
"zod": "^4.3.6"
},
"engines": {
"node": "^20.19.0 || >=22.12.0"
}
},
"node_modules/@github/copilot-win32-arm64": {
"version": "1.0.64-3",
"resolved": "https://registry.npmjs.org/@github/copilot-win32-arm64/-/copilot-win32-arm64-1.0.64-3.tgz",
"integrity": "sha512-jUTS9meoHEXQR8nMDOjwC0baqV273lYtLxY46W7TiOFszhsqhbhWxQMkNQBfT3GEfPp+40igzMPq3reaUTuvag==",
"cpu": [
"arm64"
],
"dev": true,
"license": "SEE LICENSE IN LICENSE.md",
"optional": true,
"os": [
"win32"
],
"bin": {
"copilot-win32-arm64": "copilot.exe"
}
},
"node_modules/@github/copilot-win32-x64": {
"version": "1.0.64-3",
"resolved": "https://registry.npmjs.org/@github/copilot-win32-x64/-/copilot-win32-x64-1.0.64-3.tgz",
"integrity": "sha512-gHUhS500Q91hjtH9fqKDblaIs0mO09G4ifpZ1woDPXbkdKe/W29uwB7g2fn0+KczNRyPxFSWlqjnOon4Fe8svA==",
"cpu": [
"x64"
],
"dev": true,
"license": "SEE LICENSE IN LICENSE.md",
"optional": true,
"os": [
"win32"
],
"bin": {
"copilot-win32-x64": "copilot.exe"
}
},
"node_modules/@microsoft/vally": {
"version": "0.6.0",
"resolved": "https://registry.npmjs.org/@microsoft/vally/-/vally-0.6.0.tgz",
"integrity": "sha512-b283YRDFZXUkKNKY3+1EfMBVbHrBLIs5jfUi7lIQ8N0Y10lVsNnNGkRbtTbd7tLYZajr6AhtnpIroc4RFzo1cQ==",
"dev": true,
"dependencies": {
"@github/copilot-sdk": "^1.0.0",
"js-tiktoken": "^1.0.21",
"picomatch": "^4.0.4",
"yaml": "^2.9.0",
"zod": "^4.4.3"
}
},
"node_modules/@types/unist": {
"version": "3.0.3",
"resolved": "https://registry.npmjs.org/@types/unist/-/unist-3.0.3.tgz",
@@ -116,6 +304,27 @@
"dev": true,
"license": "MIT"
},
"node_modules/base64-js": {
"version": "1.5.1",
"resolved": "https://registry.npmjs.org/base64-js/-/base64-js-1.5.1.tgz",
"integrity": "sha512-AKpaYlHn8t4SVbOHCy+b5+KKgvR4vrsD8vbvrbiQJps7fKDTkjkDry6ji0rUJjC0kzbNePLwzxq8iypo41qeWA==",
"dev": true,
"funding": [
{
"type": "github",
"url": "https://github.com/sponsors/feross"
},
{
"type": "patreon",
"url": "https://www.patreon.com/feross"
},
{
"type": "consulting",
"url": "https://feross.org/support"
}
],
"license": "MIT"
},
"node_modules/camelcase": {
"version": "5.3.1",
"resolved": "https://registry.npmjs.org/camelcase/-/camelcase-5.3.1.tgz",
@@ -215,6 +424,16 @@
"node": ">=0.10.0"
}
},
"node_modules/detect-libc": {
"version": "2.1.2",
"resolved": "https://registry.npmjs.org/detect-libc/-/detect-libc-2.1.2.tgz",
"integrity": "sha512-Btj2BOOO83o3WyH59e8MgXsxEQVcarkUOpEYrubB0urwnN10yQ364rsiByU11nZlqWYZm05i/of7io4mzihBtQ==",
"dev": true,
"license": "Apache-2.0",
"engines": {
"node": ">=8"
}
},
"node_modules/didyoumean": {
"version": "1.2.2",
"resolved": "https://registry.npmjs.org/didyoumean/-/didyoumean-1.2.2.tgz",
@@ -352,6 +571,16 @@
"node": ">=8"
}
},
"node_modules/js-tiktoken": {
"version": "1.0.21",
"resolved": "https://registry.npmjs.org/js-tiktoken/-/js-tiktoken-1.0.21.tgz",
"integrity": "sha512-biOj/6M5qdgx5TKjDnFT1ymSpM5tbd3ylwDtrQvFQSu0Z7bBYko2dF+W/aUkXUPuk6IVpRxk/3Q2sHOzGlS36g==",
"dev": true,
"license": "MIT",
"dependencies": {
"base64-js": "^1.5.1"
}
},
"node_modules/js-yaml": {
"version": "4.2.0",
"resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-4.2.0.tgz",
@@ -535,6 +764,19 @@
"node": ">=0.10"
}
},
"node_modules/picomatch": {
"version": "4.0.4",
"resolved": "https://registry.npmjs.org/picomatch/-/picomatch-4.0.4.tgz",
"integrity": "sha512-QP88BAKvMam/3NxH6vj2o21R6MjxZUAd6nlwAS/pnGvN9IVLocLHxGYIzFhg6fUQ+5th6P4dv4eW9jX3DSIj7A==",
"dev": true,
"license": "MIT",
"engines": {
"node": ">=12"
},
"funding": {
"url": "https://github.com/sponsors/jonschlinkert"
}
},
"node_modules/pify": {
"version": "5.0.0",
"resolved": "https://registry.npmjs.org/pify/-/pify-5.0.0.tgz",
@@ -783,6 +1025,16 @@
"url": "https://opencollective.com/unified"
}
},
"node_modules/vscode-jsonrpc": {
"version": "8.2.1",
"resolved": "https://registry.npmjs.org/vscode-jsonrpc/-/vscode-jsonrpc-8.2.1.tgz",
"integrity": "sha512-kdjOSJ2lLIn7r1rtrMbbNCHjyMPfRnowdKjBQ+mGq6NAW5QY2bEZC/khaC5OR8svbbjvLEaIXkOq45e2X9BIbQ==",
"dev": true,
"license": "MIT",
"engines": {
"node": ">=14.0.0"
}
},
"node_modules/webidl-conversions": {
"version": "3.0.1",
"resolved": "https://registry.npmjs.org/webidl-conversions/-/webidl-conversions-3.0.1.tgz",
@@ -881,6 +1133,16 @@
"engines": {
"node": ">=6"
}
},
"node_modules/zod": {
"version": "4.4.3",
"resolved": "https://registry.npmjs.org/zod/-/zod-4.4.3.tgz",
"integrity": "sha512-ytENFjIJFl2UwYglde2jchW2Hwm4GJFLDiSXWdTrJQBIN9Fcyp7n4DhxJEiWNAJMV1/BqWfW/kkg71UDcHJyTQ==",
"dev": true,
"license": "MIT",
"funding": {
"url": "https://github.com/sponsors/colinhacks"
}
}
}
}
+1
View File
@@ -38,6 +38,7 @@
"author": "GitHub",
"license": "MIT",
"devDependencies": {
"@microsoft/vally": "^0.6.0",
"all-contributors-cli": "^6.26.1"
},
"dependencies": {