Make the sync label logic reusable for typespec-azure repo (#3425)
To allow this https://github.com/Azure/typespec-azure/pull/903
This commit is contained in:
Родитель
410705935e
Коммит
5698be15fe
|
@ -15,8 +15,6 @@ configuration:
|
|||
filters:
|
||||
- isIssue
|
||||
- isOpen
|
||||
- hasLabel:
|
||||
label: No-Recent-Activity
|
||||
- hasLabel:
|
||||
label: needs-info
|
||||
- noActivitySince:
|
||||
|
|
|
@ -6,8 +6,6 @@
|
|||
**/obj/
|
||||
**/.vs/
|
||||
**/.docusaurus/
|
||||
common/temp/
|
||||
common/scripts/
|
||||
.chronus/changes/
|
||||
|
||||
# Pnpm lock file
|
||||
|
|
|
@ -297,17 +297,6 @@ TypeSpec repo use labels to help categorize and manage issues and PRs. The follo
|
|||
|
||||
### Labels reference
|
||||
|
||||
#### issue_kinds
|
||||
|
||||
Issue kinds
|
||||
|
||||
| Name | Color | Description |
|
||||
| --------- | ------- | ------------------------------------------ |
|
||||
| `bug` | #d93f0b | Something isn't working |
|
||||
| `feature` | #cccccc | New feature or request |
|
||||
| `docs` | #cccccc | Improvements or additions to documentation |
|
||||
| `epic` | #cccccc | |
|
||||
|
||||
#### area
|
||||
|
||||
Area of the codebase
|
||||
|
@ -332,6 +321,17 @@ Area of the codebase
|
|||
| `emitter:service:js` | #967200 | |
|
||||
| `eng` | #65bfff | |
|
||||
|
||||
#### issue_kinds
|
||||
|
||||
Issue kinds
|
||||
|
||||
| Name | Color | Description |
|
||||
| --------- | ------- | ------------------------------------------ |
|
||||
| `bug` | #d93f0b | Something isn't working |
|
||||
| `feature` | #cccccc | New feature or request |
|
||||
| `docs` | #cccccc | Improvements or additions to documentation |
|
||||
| `epic` | #cccccc | |
|
||||
|
||||
#### breaking-change
|
||||
|
||||
Labels around annotating issues and PR if they contain breaking change or deprecation
|
||||
|
|
|
@ -1,24 +0,0 @@
|
|||
import { type AreaLabels } from "./labels.js";
|
||||
|
||||
/**
|
||||
* Set the paths that each area applies to.
|
||||
*/
|
||||
export const AreaPaths: Record<keyof typeof AreaLabels, string[]> = {
|
||||
"compiler:core": ["packages/compiler/"],
|
||||
"compiler:emitter-framework": [],
|
||||
ide: ["packages/typespec-vscode/", "packages/typespec-vs/"],
|
||||
"lib:http": ["packages/http/"],
|
||||
"lib:openapi": ["packages/openapi/"],
|
||||
"lib:rest": ["packages/rest/"],
|
||||
"lib:versioning": ["packages/versioning/"],
|
||||
"meta:blog": ["blog/"],
|
||||
"meta:website": ["website/"],
|
||||
tspd: ["packages/tspd/"],
|
||||
"emitter:client:csharp": ["packages/http-client-csharp/"],
|
||||
"emitter:json-schema": ["packages/json-schema/"],
|
||||
"emitter:protobuf": ["packages/protobuf/"],
|
||||
"emitter:openapi3": ["packages/openapi3/"],
|
||||
"emitter:service:csharp": [],
|
||||
"emitter:service:js": [],
|
||||
eng: ["eng/", ".github/"],
|
||||
};
|
|
@ -1,4 +1,6 @@
|
|||
// cspell:ignore bfff
|
||||
import { repo } from "../scripts/common.js";
|
||||
import { defineConfig, defineLabels } from "../scripts/labels/config.js";
|
||||
|
||||
/**
|
||||
* Labels that are used to categorize issue for which area they belong to.
|
||||
|
@ -74,10 +76,10 @@ export const AreaLabels = defineLabels({
|
|||
},
|
||||
});
|
||||
|
||||
export default {
|
||||
export const CommonLabels = {
|
||||
issue_kinds: {
|
||||
description: "Issue kinds",
|
||||
labels: {
|
||||
labels: defineLabels({
|
||||
bug: {
|
||||
color: "d93f0b",
|
||||
description: "Something isn't working",
|
||||
|
@ -94,11 +96,7 @@ export default {
|
|||
color: "cccccc",
|
||||
description: "",
|
||||
},
|
||||
},
|
||||
},
|
||||
area: {
|
||||
description: "Area of the codebase",
|
||||
labels: AreaLabels,
|
||||
}),
|
||||
},
|
||||
"breaking-change": {
|
||||
description:
|
||||
|
@ -150,23 +148,52 @@ export default {
|
|||
},
|
||||
},
|
||||
},
|
||||
misc: {
|
||||
description: "Misc labels",
|
||||
labels: {
|
||||
"Client Emitter Migration": {
|
||||
color: "FD92F0",
|
||||
description: "",
|
||||
},
|
||||
"good first issue": {
|
||||
color: "7057ff",
|
||||
description: "Good for newcomers",
|
||||
};
|
||||
|
||||
/**
|
||||
* Set the paths that each area applies to.
|
||||
*/
|
||||
export const AreaPaths: Record<keyof typeof AreaLabels, string[]> = {
|
||||
"compiler:core": ["packages/compiler/"],
|
||||
"compiler:emitter-framework": [],
|
||||
ide: ["packages/typespec-vscode/", "packages/typespec-vs/"],
|
||||
"lib:http": ["packages/http/"],
|
||||
"lib:openapi": ["packages/openapi/"],
|
||||
"lib:rest": ["packages/rest/"],
|
||||
"lib:versioning": ["packages/versioning/"],
|
||||
"meta:blog": ["blog/"],
|
||||
"meta:website": ["website/"],
|
||||
tspd: ["packages/tspd/"],
|
||||
"emitter:client:csharp": ["packages/http-client-csharp/"],
|
||||
"emitter:json-schema": ["packages/json-schema/"],
|
||||
"emitter:protobuf": ["packages/protobuf/"],
|
||||
"emitter:openapi3": ["packages/openapi3/"],
|
||||
"emitter:service:csharp": [],
|
||||
"emitter:service:js": [],
|
||||
eng: ["eng/", ".github/"],
|
||||
};
|
||||
|
||||
export default defineConfig({
|
||||
repo,
|
||||
labels: {
|
||||
area: {
|
||||
description: "Area of the codebase",
|
||||
labels: AreaLabels,
|
||||
},
|
||||
...CommonLabels,
|
||||
misc: {
|
||||
description: "Misc labels",
|
||||
labels: {
|
||||
"Client Emitter Migration": {
|
||||
color: "FD92F0",
|
||||
description: "",
|
||||
},
|
||||
"good first issue": {
|
||||
color: "7057ff",
|
||||
description: "Good for newcomers",
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
} as const;
|
||||
|
||||
function defineLabels<const T extends string>(
|
||||
labels: Record<T, { color: string; description: string }>
|
||||
) {
|
||||
return labels;
|
||||
}
|
||||
areaPaths: AreaPaths,
|
||||
});
|
||||
|
|
|
@ -1,8 +1,6 @@
|
|||
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, syncFile } from "../common.js";
|
||||
import {
|
||||
PolicyServiceConfig,
|
||||
and,
|
||||
|
@ -16,14 +14,15 @@ import {
|
|||
or,
|
||||
payloadType,
|
||||
} from "./policy.js";
|
||||
import type { RepoConfig } from "./types.js";
|
||||
|
||||
const policyFolder = resolve(repoRoot, ".github", "policies");
|
||||
const policyFolder = resolve(process.cwd(), ".github", "policies");
|
||||
|
||||
export interface SyncLabelAutomationOptions extends CheckOptions {}
|
||||
|
||||
export async function syncLabelAutomation(options: SyncLabelAutomationOptions) {
|
||||
await syncPolicyFile(issueTriageConfig, options);
|
||||
await syncPolicyFile(prTriageConfig, options);
|
||||
export async function syncLabelAutomation(config: RepoConfig, options: SyncLabelAutomationOptions) {
|
||||
await syncPolicyFile(createIssueTriageConfig(config), options);
|
||||
await syncPolicyFile(createPrTriageConfig(config), options);
|
||||
}
|
||||
|
||||
async function syncPolicyFile(policy: PolicyServiceConfig, options: CheckOptions) {
|
||||
|
@ -33,96 +32,100 @@ async function syncPolicyFile(policy: PolicyServiceConfig, options: CheckOptions
|
|||
await syncFile(filename, content, options);
|
||||
}
|
||||
|
||||
const issueTriageConfig: PolicyServiceConfig = {
|
||||
id: "issues.triage",
|
||||
name: "New Issue Assign labels",
|
||||
description: "Assign labels to new issues",
|
||||
resource: "repository",
|
||||
disabled: false,
|
||||
configuration: {
|
||||
resourceManagementConfiguration: {
|
||||
eventResponderTasks: [
|
||||
eventResponderTask({
|
||||
description: "Adds `needs-area` label for new unassigned issues",
|
||||
if: [
|
||||
payloadType("Issues"),
|
||||
isAction("Opened"),
|
||||
not(and(["isAssignedToSomeone"])),
|
||||
not(or(Object.keys(AreaLabels).map((area) => hasLabel(area)))),
|
||||
],
|
||||
then: [
|
||||
{
|
||||
addLabel: {
|
||||
label: "needs-area",
|
||||
function createIssueTriageConfig(config: RepoConfig): PolicyServiceConfig {
|
||||
const areaLabels = config.labels.area.labels;
|
||||
return {
|
||||
id: "issues.triage",
|
||||
name: "New Issue Assign labels",
|
||||
description: "Assign labels to new issues",
|
||||
resource: "repository",
|
||||
disabled: false,
|
||||
configuration: {
|
||||
resourceManagementConfiguration: {
|
||||
eventResponderTasks: [
|
||||
eventResponderTask({
|
||||
description: "Adds `needs-area` label for new unassigned issues",
|
||||
if: [
|
||||
payloadType("Issues"),
|
||||
isAction("Opened"),
|
||||
not(and(["isAssignedToSomeone"])),
|
||||
not(or(Object.keys(areaLabels).map((area) => hasLabel(area)))),
|
||||
],
|
||||
then: [
|
||||
{
|
||||
addLabel: {
|
||||
label: "needs-area",
|
||||
},
|
||||
},
|
||||
},
|
||||
],
|
||||
}),
|
||||
eventResponderTask({
|
||||
description: "Remove `needs-area` label when an area label is added",
|
||||
if: [
|
||||
payloadType("Issues"),
|
||||
hasLabel("needs-area"),
|
||||
"isOpen",
|
||||
or(Object.keys(AreaLabels).map((area) => labelAdded(area))),
|
||||
],
|
||||
then: [
|
||||
{
|
||||
removeLabel: {
|
||||
label: "needs-area",
|
||||
},
|
||||
},
|
||||
],
|
||||
}),
|
||||
eventResponderTask({
|
||||
description: "Add `needs-area` back when all area labels are removed",
|
||||
if: [
|
||||
payloadType("Issues"),
|
||||
not(hasLabel("needs-area")),
|
||||
"isOpen",
|
||||
or(Object.keys(AreaLabels).map((area) => labelRemoved(area))),
|
||||
not(or(Object.keys(AreaLabels).map((area) => hasLabel(area)))),
|
||||
],
|
||||
then: [
|
||||
{
|
||||
addLabel: {
|
||||
label: "needs-area",
|
||||
},
|
||||
},
|
||||
],
|
||||
}),
|
||||
],
|
||||
},
|
||||
},
|
||||
};
|
||||
|
||||
const prTriageConfig: PolicyServiceConfig = {
|
||||
id: "prs.triage",
|
||||
name: "Assign area labels to PRs",
|
||||
description: "Assign area labels to PR depending on path modified.",
|
||||
resource: "repository",
|
||||
disabled: false,
|
||||
configuration: {
|
||||
resourceManagementConfiguration: {
|
||||
eventResponderTasks: [
|
||||
eventResponderTask({
|
||||
if: [payloadType("Pull_Request")],
|
||||
then: Object.entries(AreaPaths).flatMap(([label, files]) => {
|
||||
return files.map((file) => {
|
||||
return {
|
||||
if: [filesMatchPattern(`${file}.*`)],
|
||||
then: [
|
||||
{
|
||||
addLabel: {
|
||||
label,
|
||||
},
|
||||
},
|
||||
],
|
||||
};
|
||||
});
|
||||
],
|
||||
}),
|
||||
}),
|
||||
],
|
||||
eventResponderTask({
|
||||
description: "Remove `needs-area` label when an area label is added",
|
||||
if: [
|
||||
payloadType("Issues"),
|
||||
hasLabel("needs-area"),
|
||||
"isOpen",
|
||||
or(Object.keys(areaLabels).map((area) => labelAdded(area))),
|
||||
],
|
||||
then: [
|
||||
{
|
||||
removeLabel: {
|
||||
label: "needs-area",
|
||||
},
|
||||
},
|
||||
],
|
||||
}),
|
||||
eventResponderTask({
|
||||
description: "Add `needs-area` back when all area labels are removed",
|
||||
if: [
|
||||
payloadType("Issues"),
|
||||
not(hasLabel("needs-area")),
|
||||
"isOpen",
|
||||
or(Object.keys(areaLabels).map((area) => labelRemoved(area))),
|
||||
not(or(Object.keys(areaLabels).map((area) => hasLabel(area)))),
|
||||
],
|
||||
then: [
|
||||
{
|
||||
addLabel: {
|
||||
label: "needs-area",
|
||||
},
|
||||
},
|
||||
],
|
||||
}),
|
||||
],
|
||||
},
|
||||
},
|
||||
},
|
||||
};
|
||||
};
|
||||
}
|
||||
function createPrTriageConfig(config: RepoConfig): PolicyServiceConfig {
|
||||
return {
|
||||
id: "prs.triage",
|
||||
name: "Assign area labels to PRs",
|
||||
description: "Assign area labels to PR depending on path modified.",
|
||||
resource: "repository",
|
||||
disabled: false,
|
||||
configuration: {
|
||||
resourceManagementConfiguration: {
|
||||
eventResponderTasks: [
|
||||
eventResponderTask({
|
||||
if: [payloadType("Pull_Request")],
|
||||
then: Object.entries(config.areaPaths).flatMap(([label, files]) => {
|
||||
return files.map((file) => {
|
||||
return {
|
||||
if: [filesMatchPattern(`${file}.*`)],
|
||||
then: [
|
||||
{
|
||||
addLabel: {
|
||||
label,
|
||||
},
|
||||
},
|
||||
],
|
||||
};
|
||||
});
|
||||
}),
|
||||
}),
|
||||
],
|
||||
},
|
||||
},
|
||||
};
|
||||
}
|
||||
|
|
|
@ -0,0 +1,11 @@
|
|||
import { RepoConfig } from "./types.js";
|
||||
|
||||
export function defineConfig(config: RepoConfig) {
|
||||
return config;
|
||||
}
|
||||
|
||||
export function defineLabels<const T extends string>(
|
||||
labels: Record<T, { color: string; description: string }>
|
||||
) {
|
||||
return labels;
|
||||
}
|
|
@ -6,14 +6,13 @@ import { resolve } from "path";
|
|||
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 { RepoConfig } from "./types.js";
|
||||
|
||||
const Octokit = OctokitCore.plugin(paginateGraphQL).plugin(restEndpointMethods);
|
||||
type Octokit = InstanceType<typeof Octokit>;
|
||||
|
||||
const labelFileRelative = "eng/common/config/labels.ts";
|
||||
const contributingFile = resolve(repoRoot, "CONTRIBUTING.md");
|
||||
const contributingFile = resolve(process.cwd(), "CONTRIBUTING.md");
|
||||
const magicComment = {
|
||||
start: "<!-- LABEL GENERATED REF START -->",
|
||||
end: "<!-- LABEL GENERATED REF END -->",
|
||||
|
@ -25,12 +24,12 @@ export interface SyncLabelsOptions {
|
|||
readonly dryRun?: boolean;
|
||||
}
|
||||
|
||||
export async function syncLabelsDefinitions(options: SyncLabelsOptions = {}) {
|
||||
const labels = loadLabels();
|
||||
export async function syncLabelsDefinitions(config: RepoConfig, options: SyncLabelsOptions = {}) {
|
||||
const labels = loadLabels(config);
|
||||
logLabelConfig(labels);
|
||||
|
||||
if (options.github) {
|
||||
await syncGithubLabels(labels.labels, {
|
||||
await syncGithubLabels(config, labels.labels, {
|
||||
dryRun: options.dryRun,
|
||||
check: options.check,
|
||||
});
|
||||
|
@ -42,7 +41,7 @@ export async function syncLabelsDefinitions(options: SyncLabelsOptions = {}) {
|
|||
});
|
||||
}
|
||||
|
||||
interface LabelsConfig {
|
||||
interface LabelsResolvedConfig {
|
||||
readonly categories: LabelCategory[];
|
||||
readonly labels: Label[];
|
||||
}
|
||||
|
@ -64,8 +63,8 @@ interface ActionOptions {
|
|||
readonly check?: boolean;
|
||||
}
|
||||
|
||||
function loadLabels(): LabelsConfig {
|
||||
const data = rawLabels;
|
||||
function loadLabels(config: RepoConfig): LabelsResolvedConfig {
|
||||
const data = config.labels;
|
||||
const labels = [];
|
||||
const categories: LabelCategory[] = [];
|
||||
for (const [categoryName, { description, labels: labelMap }] of Object.entries(data)) {
|
||||
|
@ -80,7 +79,7 @@ function loadLabels(): LabelsConfig {
|
|||
return { labels, categories };
|
||||
}
|
||||
|
||||
function logLabelConfig(config: LabelsConfig) {
|
||||
function logLabelConfig(config: LabelsResolvedConfig) {
|
||||
console.log("Label config:");
|
||||
const max = config.labels.reduce((max, label) => Math.max(max, label.name.length), 0);
|
||||
for (const category of config.categories) {
|
||||
|
@ -110,7 +109,7 @@ function prettyLabel(label: Label, padEnd: number = 0) {
|
|||
return `${pc.cyan(label.name.padEnd(padEnd))} ${pc.blue(`#${label.color}`)} ${pc.gray(label.description)}`;
|
||||
}
|
||||
|
||||
async function syncGithubLabels(labels: Label[], options: ActionOptions = {}) {
|
||||
async function syncGithubLabels(config: RepoConfig, labels: Label[], options: ActionOptions = {}) {
|
||||
if (!options.dryRun && !process.env.GITHUB_TOKEN && !options.check) {
|
||||
throw new Error(
|
||||
"GITHUB_TOKEN environment variable is required when not running in dry-run mode or check mode."
|
||||
|
@ -120,8 +119,8 @@ async function syncGithubLabels(labels: Label[], options: ActionOptions = {}) {
|
|||
process.env.GITHUB_TOKEN ? { auth: `token ${process.env.GITHUB_TOKEN}` } : {}
|
||||
);
|
||||
|
||||
const existingLabels = await fetchAllLabels(octokit);
|
||||
logLabels("Existing github labels", existingLabels as any);
|
||||
const existingLabels = await fetchAllLabels(octokit, config.repo);
|
||||
logLabels("Existing github labels", existingLabels);
|
||||
const labelToUpdate: Label[] = [];
|
||||
const labelsToCreate: Label[] = [];
|
||||
const exitingLabelMap = new Map(existingLabels.map((label) => [label.name, label]));
|
||||
|
@ -148,9 +147,9 @@ async function syncGithubLabels(labels: Label[], options: ActionOptions = {}) {
|
|||
}
|
||||
} else {
|
||||
logAction("Applying changes", options);
|
||||
await updateLabels(octokit, labelToUpdate, options);
|
||||
await createLabels(octokit, labelsToCreate, options);
|
||||
await deleteLabels(octokit, labelsToDelete, options);
|
||||
await updateLabels(config, octokit, labelToUpdate, options);
|
||||
await createLabels(config, octokit, labelsToCreate, options);
|
||||
await deleteLabels(config, octokit, labelsToDelete, options);
|
||||
logAction("Done applying changes", options);
|
||||
}
|
||||
}
|
||||
|
@ -160,7 +159,7 @@ async function checkLabelsToDelete(labels: GithubLabel[]) {
|
|||
let hasError = false;
|
||||
for (const label of labels) {
|
||||
if (label.issues.totalCount > 0) {
|
||||
console.error(
|
||||
console.log(
|
||||
pc.red(
|
||||
`Label ${label.name} has ${label.issues.totalCount} issues assigned to it, make sure to rename the label manually first to not lose assignment.`
|
||||
)
|
||||
|
@ -171,7 +170,7 @@ async function checkLabelsToDelete(labels: GithubLabel[]) {
|
|||
if (hasError) {
|
||||
process.exit(1);
|
||||
} else {
|
||||
console.error(pc.green(`Labels looks good to delete.`));
|
||||
console.log(pc.green(`Labels looks good to delete.`));
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -181,10 +180,10 @@ interface GithubLabel {
|
|||
readonly description: string;
|
||||
readonly issues: { readonly totalCount: number };
|
||||
}
|
||||
async function fetchAllLabels(octokit: Octokit): Promise<GithubLabel[]> {
|
||||
async function fetchAllLabels(octokit: Octokit, repo: RepoConfig["repo"]): Promise<GithubLabel[]> {
|
||||
const { repository } = await octokit.graphql.paginate(
|
||||
`query paginate($cursor: String) {
|
||||
repository(owner: "Microsoft", name: "typespec") {
|
||||
`query paginate($cursor: String, $owner: String!, $repo: String!) {
|
||||
repository(owner: $owner, name: $repo) {
|
||||
labels(first: 100, after: $cursor) {
|
||||
nodes {
|
||||
color
|
||||
|
@ -200,7 +199,11 @@ async function fetchAllLabels(octokit: Octokit): Promise<GithubLabel[]> {
|
|||
}
|
||||
}
|
||||
}
|
||||
}`
|
||||
}`,
|
||||
{
|
||||
owner: repo.owner,
|
||||
repo: repo.repo,
|
||||
}
|
||||
);
|
||||
|
||||
return repository.labels.nodes;
|
||||
|
@ -217,30 +220,45 @@ async function doAction(action: () => Promise<unknown>, label: string, options:
|
|||
}
|
||||
logAction(label, options);
|
||||
}
|
||||
async function createLabels(octokit: Octokit, labels: Label[], options: ActionOptions) {
|
||||
async function createLabels(
|
||||
config: RepoConfig,
|
||||
octokit: Octokit,
|
||||
labels: Label[],
|
||||
options: ActionOptions
|
||||
) {
|
||||
for (const label of labels) {
|
||||
await doAction(
|
||||
() => octokit.rest.issues.createLabel({ ...repo, ...label }),
|
||||
() => octokit.rest.issues.createLabel({ ...config.repo, ...label }),
|
||||
`Created label ${label.name}, color: ${label.color}, description: ${label.description}`,
|
||||
options
|
||||
);
|
||||
}
|
||||
}
|
||||
async function updateLabels(octokit: Octokit, labels: Label[], options: ActionOptions) {
|
||||
async function updateLabels(
|
||||
config: RepoConfig,
|
||||
octokit: Octokit,
|
||||
labels: Label[],
|
||||
options: ActionOptions
|
||||
) {
|
||||
for (const label of labels) {
|
||||
await doAction(
|
||||
() => octokit.rest.issues.updateLabel({ ...repo, ...label }),
|
||||
() => octokit.rest.issues.updateLabel({ ...config.repo, ...label }),
|
||||
`Updated label ${label.name}, color: ${label.color}, description: ${label.description}`,
|
||||
options
|
||||
);
|
||||
}
|
||||
}
|
||||
async function deleteLabels(octokit: Octokit, labels: GithubLabel[], options: ActionOptions) {
|
||||
async function deleteLabels(
|
||||
config: RepoConfig,
|
||||
octokit: Octokit,
|
||||
labels: GithubLabel[],
|
||||
options: ActionOptions
|
||||
) {
|
||||
checkLabelsToDelete(labels);
|
||||
|
||||
for (const label of labels) {
|
||||
await doAction(
|
||||
() => octokit.rest.issues.deleteLabel({ ...repo, name: label.name }),
|
||||
() => octokit.rest.issues.deleteLabel({ ...config.repo, name: label.name }),
|
||||
`Deleted label ${label.name}`,
|
||||
options
|
||||
);
|
||||
|
@ -260,7 +278,7 @@ function validateLabel(label: Label) {
|
|||
}
|
||||
}
|
||||
|
||||
async function updateContributingFile(labels: LabelsConfig, options: ActionOptions) {
|
||||
async function updateContributingFile(labels: LabelsResolvedConfig, options: ActionOptions) {
|
||||
console.log("Updating contributing file", contributingFile);
|
||||
const content = await readFile(contributingFile, "utf8");
|
||||
const startIndex = content.indexOf(magicComment.start);
|
||||
|
@ -282,7 +300,7 @@ async function updateContributingFile(labels: LabelsConfig, options: ActionOptio
|
|||
if (formatted === content) {
|
||||
console.log(pc.green("CONTRIBUTING.md is up to date."));
|
||||
} else {
|
||||
console.error(
|
||||
console.log(
|
||||
pc.red(
|
||||
"CONTRIBUTING.md file label section is not up to date, run pnpm sync-labels to update it"
|
||||
)
|
||||
|
@ -298,7 +316,7 @@ async function updateContributingFile(labels: LabelsConfig, options: ActionOptio
|
|||
}
|
||||
}
|
||||
|
||||
function generateLabelsDoc(labels: LabelsConfig) {
|
||||
function generateLabelsDoc(labels: LabelsResolvedConfig) {
|
||||
return [
|
||||
"### Labels reference",
|
||||
...labels.categories.map((category) => {
|
||||
|
|
|
@ -1,10 +1,16 @@
|
|||
import { resolve } from "path";
|
||||
import { parseArgs } from "util";
|
||||
import { syncLabelAutomation } from "./automation.js";
|
||||
import { syncLabelsDefinitions } from "./definitions.js";
|
||||
import { RepoConfig } from "./types.js";
|
||||
|
||||
const options = parseArgs({
|
||||
args: process.argv.slice(2),
|
||||
options: {
|
||||
config: {
|
||||
type: "string",
|
||||
description: "The directory where the labels configuration is stored.",
|
||||
},
|
||||
"dry-run": {
|
||||
type: "boolean",
|
||||
description: "Do not make any changes, log what action would be taken.",
|
||||
|
@ -17,12 +23,23 @@ const options = parseArgs({
|
|||
},
|
||||
});
|
||||
|
||||
await syncLabelsDefinitions({
|
||||
if (!options.values["config"]) {
|
||||
throw new Error("--config is required");
|
||||
}
|
||||
|
||||
const config = await loadConfig(options.values["config"]);
|
||||
|
||||
await syncLabelsDefinitions(config, {
|
||||
check: options.values["check"],
|
||||
dryRun: options.values["dry-run"],
|
||||
github: options.values["github"],
|
||||
});
|
||||
|
||||
await syncLabelAutomation({
|
||||
await syncLabelAutomation(config, {
|
||||
check: options.values["check"],
|
||||
});
|
||||
|
||||
async function loadConfig(configFile: string): Promise<RepoConfig> {
|
||||
const module = await import(resolve(process.cwd(), configFile));
|
||||
return module.default;
|
||||
}
|
||||
|
|
|
@ -0,0 +1,18 @@
|
|||
export interface RepoConfig {
|
||||
repo: {
|
||||
owner: string;
|
||||
repo: string;
|
||||
};
|
||||
labels: LabelsConfig;
|
||||
areaPaths: Record<string, string[]>;
|
||||
}
|
||||
|
||||
export interface LabelsConfig {
|
||||
area: LabelCategory;
|
||||
[key: string]: LabelCategory;
|
||||
}
|
||||
|
||||
export interface LabelCategory {
|
||||
description: string;
|
||||
labels: Record<string, { color: string; description: string }>;
|
||||
}
|
|
@ -0,0 +1,7 @@
|
|||
{
|
||||
"extends": "../tsconfig.base.json",
|
||||
"compilerOptions": {
|
||||
"noEmit": true
|
||||
},
|
||||
"include": ["**/*"]
|
||||
}
|
|
@ -33,7 +33,7 @@
|
|||
"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",
|
||||
"sync-labels": "tsx ./eng/common/scripts/labels/sync-labels.ts"
|
||||
"sync-labels": "tsx ./eng/common/scripts/labels/sync-labels.ts --config ./eng/common/config/labels.ts"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@chronus/chronus": "^0.10.2",
|
||||
|
|
Загрузка…
Ссылка в новой задаче