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

Analyze change ts #3415

Open
wants to merge 18 commits into
base: main
Choose a base branch
from
Open
23 changes: 23 additions & 0 deletions eng/common/config/ci-triggers.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
import { AreaPaths } from "./areas.js";

/**
* Path that should trigger every CI build.
*/
const all = ["eng/common/", "vitest.config.ts"];

export const CIRules = {
CSharp: [...AreaPaths["emitter:client:csharp"], ".editorconfig", ...all],
Core: [
"*",
timotheeguerin marked this conversation as resolved.
Show resolved Hide resolved
"**/*",
"!.prettierignore",
"!.prettierrc.json",
"!cspell.yaml",
"!esling.config.json",
...ignore(AreaPaths["emitter:client:csharp"]),
],
};

function ignore(paths: string[]) {
return paths.map((x) => `!${x}`);
}
16 changes: 9 additions & 7 deletions eng/common/pipelines/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -22,18 +22,20 @@ extends:
- job: InitJob
displayName: Initialize
steps:
- script: |
corepack enable
corepack prepare pnpm@latest-8 --activate
timotheeguerin marked this conversation as resolved.
Show resolved Hide resolved
displayName: Install pnpm

- script: pnpm install
displayName: Install JavaScript Dependencies

- script: node $(Build.SourcesDirectory)/eng/common/scripts/resolve-target-branch.js
Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

That should be able to be combined directly in the next script?

displayName: Resolve target branch

- task: PowerShell@2
- script: pnpm tsx ./eng/common/scripts/dispatch-area-triggers.ts --target-branch $(TARGET_BRANCH)
displayName: "Analyze PR changes"
name: InitStep
inputs:
pwsh: true
filePath: $(Build.SourcesDirectory)/eng/common/scripts/Analyze-Changes.ps1
arguments: >
-TargetBranch $(TARGET_BRANCH)
workingDirectory: $(Build.SourcesDirectory)

# Run csharp stages if RunCSharp == true
- template: /packages/http-client-csharp/eng/pipeline/templates/ci-stages.yml
Expand Down
33 changes: 33 additions & 0 deletions eng/common/scripts/dispatch-area-triggers.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
import { parseArgs } from "util";
import { setOutputVariable } from "./utils/ado.js";
import { repoRoot } from "./utils/common.js";
import { findAreasChanged } from "./utils/find-area-changed.js";
import { listChangedFilesSince } from "./utils/git.js";

const args = parseArgs({
args: process.argv.slice(2),
options: {
"target-branch": { type: "string" },
},
});

const targetBranch = args.values["target-branch"];
if (!targetBranch) {
console.error("--target-branch is required");
process.exit(1);
}

console.log("Checking for changes in current branch compared to $TargetBranch");

const files = await listChangedFilesSince(`origin/${targetBranch}`, { repositoryPath: repoRoot });

console.log("##[group]Files changed in this pr");
console.log(files.map((x) => ` - ${x}`).join("\n"));
console.log("##[endgroup]");

const areaChanged = findAreasChanged(files);

for (const area of areaChanged) {
console.log(`Setting output variable Run${area} to true`);
setOutputVariable(`Run${area}`, "true");
}
2 changes: 1 addition & 1 deletion eng/common/scripts/labels/automation.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ import { resolve } from "path";
import { stringify } from "yaml";
import { AreaPaths } from "../../config/areas.js";
import { AreaLabels } from "../../config/labels.js";
import { CheckOptions, repoRoot, syncFile } from "../common.js";
import { CheckOptions, repoRoot, syncFile } from "../utils/common.js";
import {
PolicyServiceConfig,
and,
Expand Down
2 changes: 1 addition & 1 deletion eng/common/scripts/labels/definitions.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@ import pc from "picocolors";
import { format, resolveConfig } from "prettier";
import { inspect } from "util";
import rawLabels from "../../config/labels.js";
import { repo, repoRoot } from "../common.js";
import { repo, repoRoot } from "../utils/common.js";

const Octokit = OctokitCore.plugin(paginateGraphQL).plugin(restEndpointMethods);
type Octokit = InstanceType<typeof Octokit>;
Expand Down
3 changes: 3 additions & 0 deletions eng/common/scripts/utils/ado.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
export function setOutputVariable(name: string, value: string) {
process.stdout.write(`##vso[task.setvariable variable=${name};isOutput=true]${value}\n`);
}
Original file line number Diff line number Diff line change
Expand Up @@ -2,12 +2,13 @@ import { readFile, writeFile } from "fs/promises";
import { dirname, resolve } from "path";
import pc from "picocolors";
import { fileURLToPath } from "url";

export const repo = {
owner: "microsoft",
repo: "typespec",
};

export const repoRoot = resolve(dirname(fileURLToPath(import.meta.url)), "../../..");
export const repoRoot = resolve(dirname(fileURLToPath(import.meta.url)), "../../../..");

export interface CheckOptions {
readonly check?: boolean;
Expand Down
42 changes: 42 additions & 0 deletions eng/common/scripts/utils/exec-async.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
import { spawn, type SpawnOptions } from "child_process";

export interface ExecResult {
readonly code: number | null;
readonly stdall: Buffer;
readonly stdout: Buffer;
readonly stderr: Buffer;
}
export function execAsync(
cmd: string,
args: string[],
opts: SpawnOptions = {}
): Promise<ExecResult> {
return new Promise((resolve, reject) => {
const child = spawn(cmd, args, opts);
let stdall = Buffer.from("");
let stdout = Buffer.from("");
let stderr = Buffer.from("");

if (child.stdout) {
child.stdout.on("data", (data) => {
stdout = Buffer.concat([stdout, data]);
stdall = Buffer.concat([stdall, data]);
});
}

if (child.stderr) {
child.stderr.on("data", (data) => {
stderr = Buffer.concat([stderr, data]);
stdall = Buffer.concat([stdall, data]);
});
}

child.on("error", (err) => {
reject(err);
});

child.on("close", (code) => {
resolve({ code, stdout, stderr, stdall });
});
});
}
49 changes: 49 additions & 0 deletions eng/common/scripts/utils/find-area-changed.test.ts
Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Those are a 1:1 conversion of the test existing, I'll update if you merge the other PR with the new ones

Original file line number Diff line number Diff line change
@@ -0,0 +1,49 @@
import { describe, expect, it } from "vitest";
import { findAreasChanged } from "./find-area-changed.js";

describe("paths that should trigger CSharp CI", () => {
it.each(["packages/http-client-csharp/src/constants.ts"])("%s", (path) => {
const areas = findAreasChanged([path]);
expect(areas).toEqual(["CSharp"]);
});
});

describe("paths that should trigger Core CI", () => {
it.each([
"packages/compiler/package.json",
"packages/http/package.json",
"packages/openapi3/package.json",
])("%s", (path) => {
const areas = findAreasChanged([path]);
expect(areas).toEqual(["Core"]);
});
});

it("Should return a combination of core and isolated packages", () => {
const areas = findAreasChanged([
"packages/http-client-csharp/src/constants.ts",
"packages/compiler/package.json",
]);
expect(areas).toEqual(["CSharp", "Core"]);
});

it("Should return CSharp and Core if .editorconfig is changed", () => {
const areas = findAreasChanged([".editorconfig"]);
expect(areas).toEqual(["CSharp", "Core"]);
});

it("Should not return Core for .prettierignore, .prettierrc.json, cspell.yaml, esling.config.json", () => {
const areas = findAreasChanged([
".prettierignore",
".prettierrc.json",
"cspell.yaml",
"esling.config.json",
"packages/http-client-csharp/emitter/src/constants.ts",
]);
expect(areas).toEqual(["CSharp"]);
});

it("should return Core for random files at the root", () => {
const areas = findAreasChanged(["some.file", "file/in/deep/directory"]);
expect(areas).toEqual(["Core"]);
});
28 changes: 28 additions & 0 deletions eng/common/scripts/utils/find-area-changed.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
import micromatch from "micromatch";
import pc from "picocolors";
import { CIRules } from "../../config/ci-triggers.js";

export function findAreasChanged(files: string[]): (keyof typeof CIRules)[] {
const result: (keyof typeof CIRules)[] = [];
for (const [name, patterns] of Object.entries(CIRules)) {
const expandedPatterns = patterns.map(expandFolder);
console.log(`Checking trigger ${name}, with patterns:`, expandedPatterns);
const match = micromatch(files, expandedPatterns, { dot: true });

if (match.length > 0) {
result.push(name as any);
console.log(`Changes matched for trigger ${pc.cyan(name)}`, files);
} else {
console.log(`No changes matched for trigger ${pc.cyan(name)}`);
}
}

return result;
}

function expandFolder(maybeFolder: string) {
if (maybeFolder.endsWith("/")) {
return `${maybeFolder}**/*`;
}
return maybeFolder;
}
36 changes: 36 additions & 0 deletions eng/common/scripts/utils/git.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
import { execAsync, type ExecResult } from "./exec-async.js";

export async function listChangedFilesSince(
ref: string,
{ repositoryPath }: { repositoryPath: string }
) {
return splitStdoutLines(await execGit(["diff", "--name-only", `${ref}...`], { repositoryPath }));
}

async function execGit(
args: string[],
{ repositoryPath }: { repositoryPath: string }
): Promise<ExecResult> {
const result = await execAsync("git", args, { cwd: repositoryPath });

if (result.code !== 0) {
throw new GitError(args, result.stderr.toString());
}
return result;
}

export class GitError extends Error {
args: string[];

constructor(args: string[], stderr: string) {
super(`GitError running: git ${args.join(" ")}\n${stderr}`);
this.args = args;
}
}

function splitStdoutLines(result: ExecResult): string[] {
return result.stdout
.toString()
.split("\n")
.filter((a) => a);
}
7 changes: 7 additions & 0 deletions eng/tsconfig.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
{
"extends": "../tsconfig.base.json",
"compilerOptions": {
"noEmit": true
},
"include": ["**/*"]
}
4 changes: 4 additions & 0 deletions eng/vitest.config.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
import { defineConfig, mergeConfig } from "vitest/config";
import { defaultTypeSpecVitestConfig } from "../vitest.workspace.js";

export default mergeConfig(defaultTypeSpecVitestConfig, defineConfig({}));
3 changes: 3 additions & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,7 @@
"regen-samples": "pnpm -r run regen-samples",
"test:ci": "pnpm -r --aggregate-output --reporter=append-only --sequential test:ci",
"test:e2e": "pnpm -r run test:e2e",
"test:eng": "vitest --project eng",
"test": "pnpm -r --aggregate-output --reporter=append-only run test",
"update-latest-docs": "pnpm -r run update-latest-docs",
"watch": "tsc --build ./tsconfig.ws.json --watch",
Expand All @@ -43,6 +44,7 @@
"@octokit/plugin-paginate-graphql": "^5.2.2",
"@octokit/plugin-rest-endpoint-methods": "^13.2.1",
"@pnpm/find-workspace-packages": "^6.0.9",
"@types/micromatch": "^4.0.7",
"@types/node": "~18.11.19",
"@typescript-eslint/parser": "^7.9.0",
"@typescript-eslint/utils": "^7.9.0",
Expand All @@ -53,6 +55,7 @@
"eslint-plugin-import": "^2.29.1",
"eslint-plugin-unicorn": "^53.0.0",
"eslint-plugin-vitest": "^0.5.4",
"micromatch": "^4.0.5",
"picocolors": "~1.0.1",
"prettier": "~3.2.5",
"prettier-plugin-organize-imports": "~3.2.4",
Expand Down
6 changes: 6 additions & 0 deletions pnpm-lock.yaml

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

6 changes: 5 additions & 1 deletion vitest.workspace.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,8 @@
export default ["packages/*/vitest.config.ts", "packages/*/vitest.config.mts"];
export default [
"packages/*/vitest.config.ts",
"packages/*/vitest.config.mts",
"eng/vitest.config.ts",
];

/**
* Default Config For all TypeSpec projects using vitest.
Expand Down