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

Add ability to apply shared configs from another repo #141

Closed
wants to merge 2 commits into from
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
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
20 changes: 20 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -95,6 +95,25 @@ jobs:

_Note: This grants access to the `GITHUB_TOKEN` so the action can make calls to GitHub's rest API_

### (Optional) Apply any shared configurations

If you have lists of rules for labels & file patterns in a repository that stores shared configurations, you can apply them to multiple other repos rather than having to configure each repo independently. To do this, add a `shared-configurations` setting like this:

```
name: "Pull Request Labeler"
on:
- pull_request_target

jobs:
triage:
runs-on: ubuntu-latest
steps:
- uses: actions/labeler@main
with:
repo-token: "${{ secrets.GITHUB_TOKEN }}"
shared-configurations: '["my-org/my-repo/some-path/shared-preset.yml@branchname", "my-org/my-repo/some-path/another-preset.yml@branchname"]'
```

#### Inputs

Various inputs are defined in [`action.yml`](action.yml) to let you configure the labeler:
Expand All @@ -103,4 +122,5 @@ Various inputs are defined in [`action.yml`](action.yml) to let you configure th
| - | - | - |
| `repo-token` | Token to use to authorize label changes. Typically the GITHUB_TOKEN secret | N/A |
| `configuration-path` | The path to the label configuration file | `.github/labeler.yml` |
| `shared-configurations` | Github locations of shared configuration files | [] |
| `sync-labels` | Whether or not to remove labels when matching files are reverted or no longer changed by the PR | `false`
4 changes: 4 additions & 0 deletions action.yml
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,10 @@ inputs:
description: 'The path for the label configurations'
default: '.github/labeler.yml'
required: false
shared-configurations:
description: "Github locations of shared configuration files"
default: "[]"
required: false
sync-labels:
description: 'Whether or not to remove labels when matching files are reverted'
default: false
Expand Down
40 changes: 32 additions & 8 deletions dist/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -14534,11 +14534,15 @@ const core = __importStar(__webpack_require__(186));
const github = __importStar(__webpack_require__(438));
const yaml = __importStar(__webpack_require__(917));
const minimatch_1 = __webpack_require__(973);
const uniq = (arr) => [...new Set(arr)];
function run() {
return __awaiter(this, void 0, void 0, function* () {
try {
const token = core.getInput("repo-token", { required: true });
const configPath = core.getInput("configuration-path", { required: true });
const sharedConfigurations = JSON.parse(core.getInput("shared-configurations", {
required: true,
}));
const syncLabels = !!core.getInput("sync-labels", { required: false });
const prNumber = getPrNumber();
if (!prNumber) {
Expand All @@ -14553,7 +14557,27 @@ function run() {
});
core.debug(`fetching changed files for pr #${prNumber}`);
const changedFiles = yield getChangedFiles(client, prNumber);
const labelGlobs = yield getLabelGlobs(client, configPath);
const localLabelGlobs = yield getLabelGlobs(client, configPath);
const sharedConfigGlobs = yield Promise.all(sharedConfigurations.map((config) => {
const [, repoOwner, repoName, repoPath] = config.match(/([^/]+)\/([^/]+)\/?(.*)?@/);
const [, repoRef] = config.match("@(.*)");
return getLabelGlobs(client, repoPath, {
repoOwner,
repoName,
repoRef,
});
}));
const labelGlobEntries = Object.entries([localLabelGlobs, ...sharedConfigGlobs].reduce((acc, map) => {
const newAcc = Object.assign({}, acc);
const entries = [...map];
entries.forEach(([k, v]) => {
if (!newAcc[k])
newAcc[k] = [];
newAcc[k] = [...newAcc[k], ...v];
});
return newAcc;
}, {}));
const labelGlobs = new Map(labelGlobEntries);
const labels = [];
const labelsToRemove = [];
for (const [label, globs] of labelGlobs.entries()) {
Expand Down Expand Up @@ -14601,22 +14625,22 @@ function getChangedFiles(client, prNumber) {
return changedFiles;
});
}
function getLabelGlobs(client, configurationPath) {
function getLabelGlobs(client, configurationPath, remoteRepoDetails) {
return __awaiter(this, void 0, void 0, function* () {
const configurationContent = yield fetchContent(client, configurationPath);
const configurationContent = yield fetchContent(client, configurationPath, remoteRepoDetails);
// loads (hopefully) a `{[label:string]: string | StringOrMatchConfig[]}`, but is `any`:
const configObject = yaml.safeLoad(configurationContent);
// transform `any` => `Map<string,StringOrMatchConfig[]>` or throw if yaml is malformed:
return getLabelGlobMapFromObject(configObject);
});
}
function fetchContent(client, repoPath) {
function fetchContent(client, repoPath, { repoName = github.context.repo.repo, repoOwner = github.context.repo.owner, repoRef = github.context.sha, } = {}) {
return __awaiter(this, void 0, void 0, function* () {
const response = yield client.repos.getContents({
owner: github.context.repo.owner,
repo: github.context.repo.repo,
owner: repoOwner,
repo: repoName,
path: repoPath,
ref: github.context.sha
ref: repoRef,
});
return Buffer.from(response.data.content, response.data.encoding).toString();
});
Expand Down Expand Up @@ -29691,7 +29715,7 @@ module.exports = new Schema({
/***/ 954:
/***/ (function(module) {

module.exports = {"_args":[["@octokit/rest@16.43.1","/Users/dakale/dev/GitHub/actions/labeler"]],"_from":"@octokit/rest@16.43.1","_id":"@octokit/rest@16.43.1","_inBundle":false,"_integrity":"sha512-gfFKwRT/wFxq5qlNjnW2dh+qh74XgTQ2B179UX5K1HYCluioWj8Ndbgqw2PVqa1NnVJkGHp2ovMpVn/DImlmkw==","_location":"/@octokit/rest","_phantomChildren":{"@octokit/types":"2.14.0","deprecation":"2.3.1","once":"1.4.0","os-name":"3.1.0"},"_requested":{"type":"version","registry":true,"raw":"@octokit/rest@16.43.1","name":"@octokit/rest","escapedName":"@octokit%2frest","scope":"@octokit","rawSpec":"16.43.1","saveSpec":null,"fetchSpec":"16.43.1"},"_requiredBy":["/@actions/github"],"_resolved":"https://registry.npmjs.org/@octokit/rest/-/rest-16.43.1.tgz","_spec":"16.43.1","_where":"/Users/dakale/dev/GitHub/actions/labeler","author":{"name":"Gregor Martynus","url":"https://github.com/gr2m"},"bugs":{"url":"https://github.com/octokit/rest.js/issues"},"bundlesize":[{"path":"./dist/octokit-rest.min.js.gz","maxSize":"33 kB"}],"contributors":[{"name":"Mike de Boer","email":"info@mikedeboer.nl"},{"name":"Fabian Jakobs","email":"fabian@c9.io"},{"name":"Joe Gallo","email":"joe@brassafrax.com"},{"name":"Gregor Martynus","url":"https://github.com/gr2m"}],"dependencies":{"@octokit/auth-token":"^2.4.0","@octokit/plugin-paginate-rest":"^1.1.1","@octokit/plugin-request-log":"^1.0.0","@octokit/plugin-rest-endpoint-methods":"2.4.0","@octokit/request":"^5.2.0","@octokit/request-error":"^1.0.2","atob-lite":"^2.0.0","before-after-hook":"^2.0.0","btoa-lite":"^1.0.0","deprecation":"^2.0.0","lodash.get":"^4.4.2","lodash.set":"^4.3.2","lodash.uniq":"^4.5.0","octokit-pagination-methods":"^1.1.0","once":"^1.4.0","universal-user-agent":"^4.0.0"},"description":"GitHub REST API client for Node.js","devDependencies":{"@gimenete/type-writer":"^0.1.3","@octokit/auth":"^1.1.1","@octokit/fixtures-server":"^5.0.6","@octokit/graphql":"^4.2.0","@types/node":"^13.1.0","bundlesize":"^0.18.0","chai":"^4.1.2","compression-webpack-plugin":"^3.1.0","cypress":"^3.0.0","glob":"^7.1.2","http-proxy-agent":"^4.0.0","lodash.camelcase":"^4.3.0","lodash.merge":"^4.6.1","lodash.upperfirst":"^4.3.1","lolex":"^5.1.2","mkdirp":"^1.0.0","mocha":"^7.0.1","mustache":"^4.0.0","nock":"^11.3.3","npm-run-all":"^4.1.2","nyc":"^15.0.0","prettier":"^1.14.2","proxy":"^1.0.0","semantic-release":"^17.0.0","sinon":"^8.0.0","sinon-chai":"^3.0.0","sort-keys":"^4.0.0","string-to-arraybuffer":"^1.0.0","string-to-jsdoc-comment":"^1.0.0","typescript":"^3.3.1","webpack":"^4.0.0","webpack-bundle-analyzer":"^3.0.0","webpack-cli":"^3.0.0"},"files":["index.js","index.d.ts","lib","plugins"],"homepage":"https://github.com/octokit/rest.js#readme","keywords":["octokit","github","rest","api-client"],"license":"MIT","name":"@octokit/rest","nyc":{"ignore":["test"]},"publishConfig":{"access":"public"},"release":{"publish":["@semantic-release/npm",{"path":"@semantic-release/github","assets":["dist/*","!dist/*.map.gz"]}]},"repository":{"type":"git","url":"git+https://github.com/octokit/rest.js.git"},"scripts":{"build":"npm-run-all build:*","build:browser":"npm-run-all build:browser:*","build:browser:development":"webpack --mode development --entry . --output-library=Octokit --output=./dist/octokit-rest.js --profile --json > dist/bundle-stats.json","build:browser:production":"webpack --mode production --entry . --plugin=compression-webpack-plugin --output-library=Octokit --output-path=./dist --output-filename=octokit-rest.min.js --devtool source-map","build:ts":"npm run -s update-endpoints:typescript","coverage":"nyc report --reporter=html && open coverage/index.html","generate-bundle-report":"webpack-bundle-analyzer dist/bundle-stats.json --mode=static --no-open --report dist/bundle-report.html","lint":"prettier --check '{lib,plugins,scripts,test}/**/*.{js,json,ts}' 'docs/*.{js,json}' 'docs/src/**/*' index.js README.md package.json","lint:fix":"prettier --write '{lib,plugins,scripts,test}/**/*.{js,json,ts}' 'docs/*.{js,json}' 'docs/src/**/*' index.js README.md package.json","postvalidate:ts":"tsc --noEmit --target es6 test/typescript-validate.ts","prebuild:browser":"mkdirp dist/","pretest":"npm run -s lint","prevalidate:ts":"npm run -s build:ts","start-fixtures-server":"octokit-fixtures-server","test":"nyc mocha test/mocha-node-setup.js \"test/*/**/*-test.js\"","test:browser":"cypress run --browser chrome","update-endpoints":"npm-run-all update-endpoints:*","update-endpoints:fetch-json":"node scripts/update-endpoints/fetch-json","update-endpoints:typescript":"node scripts/update-endpoints/typescript","validate:ts":"tsc --target es6 --noImplicitAny index.d.ts"},"types":"index.d.ts","version":"16.43.1"};
module.exports = {"_args":[["@octokit/rest@16.43.1","/Users/jonathannarwold/repos/labeler-action"]],"_from":"@octokit/rest@16.43.1","_id":"@octokit/rest@16.43.1","_inBundle":false,"_integrity":"sha512-gfFKwRT/wFxq5qlNjnW2dh+qh74XgTQ2B179UX5K1HYCluioWj8Ndbgqw2PVqa1NnVJkGHp2ovMpVn/DImlmkw==","_location":"/@octokit/rest","_phantomChildren":{"@octokit/types":"2.14.0","deprecation":"2.3.1","once":"1.4.0","os-name":"3.1.0"},"_requested":{"type":"version","registry":true,"raw":"@octokit/rest@16.43.1","name":"@octokit/rest","escapedName":"@octokit%2frest","scope":"@octokit","rawSpec":"16.43.1","saveSpec":null,"fetchSpec":"16.43.1"},"_requiredBy":["/@actions/github"],"_resolved":"https://registry.npmjs.org/@octokit/rest/-/rest-16.43.1.tgz","_spec":"16.43.1","_where":"/Users/jonathannarwold/repos/labeler-action","author":{"name":"Gregor Martynus","url":"https://github.com/gr2m"},"bugs":{"url":"https://github.com/octokit/rest.js/issues"},"bundlesize":[{"path":"./dist/octokit-rest.min.js.gz","maxSize":"33 kB"}],"contributors":[{"name":"Mike de Boer","email":"info@mikedeboer.nl"},{"name":"Fabian Jakobs","email":"fabian@c9.io"},{"name":"Joe Gallo","email":"joe@brassafrax.com"},{"name":"Gregor Martynus","url":"https://github.com/gr2m"}],"dependencies":{"@octokit/auth-token":"^2.4.0","@octokit/plugin-paginate-rest":"^1.1.1","@octokit/plugin-request-log":"^1.0.0","@octokit/plugin-rest-endpoint-methods":"2.4.0","@octokit/request":"^5.2.0","@octokit/request-error":"^1.0.2","atob-lite":"^2.0.0","before-after-hook":"^2.0.0","btoa-lite":"^1.0.0","deprecation":"^2.0.0","lodash.get":"^4.4.2","lodash.set":"^4.3.2","lodash.uniq":"^4.5.0","octokit-pagination-methods":"^1.1.0","once":"^1.4.0","universal-user-agent":"^4.0.0"},"description":"GitHub REST API client for Node.js","devDependencies":{"@gimenete/type-writer":"^0.1.3","@octokit/auth":"^1.1.1","@octokit/fixtures-server":"^5.0.6","@octokit/graphql":"^4.2.0","@types/node":"^13.1.0","bundlesize":"^0.18.0","chai":"^4.1.2","compression-webpack-plugin":"^3.1.0","cypress":"^3.0.0","glob":"^7.1.2","http-proxy-agent":"^4.0.0","lodash.camelcase":"^4.3.0","lodash.merge":"^4.6.1","lodash.upperfirst":"^4.3.1","lolex":"^5.1.2","mkdirp":"^1.0.0","mocha":"^7.0.1","mustache":"^4.0.0","nock":"^11.3.3","npm-run-all":"^4.1.2","nyc":"^15.0.0","prettier":"^1.14.2","proxy":"^1.0.0","semantic-release":"^17.0.0","sinon":"^8.0.0","sinon-chai":"^3.0.0","sort-keys":"^4.0.0","string-to-arraybuffer":"^1.0.0","string-to-jsdoc-comment":"^1.0.0","typescript":"^3.3.1","webpack":"^4.0.0","webpack-bundle-analyzer":"^3.0.0","webpack-cli":"^3.0.0"},"files":["index.js","index.d.ts","lib","plugins"],"homepage":"https://github.com/octokit/rest.js#readme","keywords":["octokit","github","rest","api-client"],"license":"MIT","name":"@octokit/rest","nyc":{"ignore":["test"]},"publishConfig":{"access":"public"},"release":{"publish":["@semantic-release/npm",{"path":"@semantic-release/github","assets":["dist/*","!dist/*.map.gz"]}]},"repository":{"type":"git","url":"git+https://github.com/octokit/rest.js.git"},"scripts":{"build":"npm-run-all build:*","build:browser":"npm-run-all build:browser:*","build:browser:development":"webpack --mode development --entry . --output-library=Octokit --output=./dist/octokit-rest.js --profile --json > dist/bundle-stats.json","build:browser:production":"webpack --mode production --entry . --plugin=compression-webpack-plugin --output-library=Octokit --output-path=./dist --output-filename=octokit-rest.min.js --devtool source-map","build:ts":"npm run -s update-endpoints:typescript","coverage":"nyc report --reporter=html && open coverage/index.html","generate-bundle-report":"webpack-bundle-analyzer dist/bundle-stats.json --mode=static --no-open --report dist/bundle-report.html","lint":"prettier --check '{lib,plugins,scripts,test}/**/*.{js,json,ts}' 'docs/*.{js,json}' 'docs/src/**/*' index.js README.md package.json","lint:fix":"prettier --write '{lib,plugins,scripts,test}/**/*.{js,json,ts}' 'docs/*.{js,json}' 'docs/src/**/*' index.js README.md package.json","postvalidate:ts":"tsc --noEmit --target es6 test/typescript-validate.ts","prebuild:browser":"mkdirp dist/","pretest":"npm run -s lint","prevalidate:ts":"npm run -s build:ts","start-fixtures-server":"octokit-fixtures-server","test":"nyc mocha test/mocha-node-setup.js \"test/*/**/*-test.js\"","test:browser":"cypress run --browser chrome","update-endpoints":"npm-run-all update-endpoints:*","update-endpoints:fetch-json":"node scripts/update-endpoints/fetch-json","update-endpoints:typescript":"node scripts/update-endpoints/typescript","validate:ts":"tsc --target es6 --noImplicitAny index.d.ts"},"types":"index.d.ts","version":"16.43.1"};

/***/ }),

Expand Down
70 changes: 61 additions & 9 deletions src/main.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,12 +8,25 @@ interface MatchConfig {
any?: string[];
}

type RemoteRepoDetails = {
repoName?: string;
repoOwner?: string;
repoRef?: string;
};

type StringOrMatchConfig = string | MatchConfig;

const uniq = (arr) => [...new Set(arr)];

async function run() {
try {
const token = core.getInput("repo-token", { required: true });
const configPath = core.getInput("configuration-path", { required: true });
const sharedConfigurations = JSON.parse(
core.getInput("shared-configurations", {
required: true,
})
);
const syncLabels = !!core.getInput("sync-labels", { required: false });

const prNumber = getPrNumber();
Expand All @@ -32,9 +45,41 @@ async function run() {

core.debug(`fetching changed files for pr #${prNumber}`);
const changedFiles: string[] = await getChangedFiles(client, prNumber);
const labelGlobs: Map<string, StringOrMatchConfig[]> = await getLabelGlobs(
client,
configPath
const localLabelGlobs: Map<
string,
StringOrMatchConfig[]
> = await getLabelGlobs(client, configPath);
const sharedConfigGlobs: Map<
string,
StringOrMatchConfig[]
>[] = await Promise.all(
sharedConfigurations.map((config) => {
const [, repoOwner, repoName, repoPath] = config.match(
/([^/]+)\/([^/]+)\/?(.*)?@/
);
const [, repoRef] = config.match("@(.*)");
return getLabelGlobs(client, repoPath, {
repoOwner,
repoName,
repoRef,
});
})
);

const labelGlobEntries: [string, StringOrMatchConfig[]][] = Object.entries(
[localLabelGlobs, ...sharedConfigGlobs].reduce((acc, map) => {
const newAcc = { ...acc };
const entries = [...map];
entries.forEach(([k, v]) => {
if (!newAcc[k]) newAcc[k] = [];
newAcc[k] = [...newAcc[k], ...v];
});
return newAcc;
}, {})
);

const labelGlobs: Map<string, StringOrMatchConfig[]> = new Map(
labelGlobEntries
);

const labels: string[] = [];
Expand Down Expand Up @@ -93,11 +138,13 @@ async function getChangedFiles(

async function getLabelGlobs(
client: github.GitHub,
configurationPath: string
configurationPath: string,
remoteRepoDetails?: RemoteRepoDetails
): Promise<Map<string, StringOrMatchConfig[]>> {
const configurationContent: string = await fetchContent(
client,
configurationPath
configurationPath,
remoteRepoDetails
);

// loads (hopefully) a `{[label:string]: string | StringOrMatchConfig[]}`, but is `any`:
Expand All @@ -109,13 +156,18 @@ async function getLabelGlobs(

async function fetchContent(
client: github.GitHub,
repoPath: string
repoPath: string,
{
repoName = github.context.repo.repo,
repoOwner = github.context.repo.owner,
repoRef = github.context.sha,
}: RemoteRepoDetails = {}
): Promise<string> {
const response: any = await client.repos.getContents({
owner: github.context.repo.owner,
repo: github.context.repo.repo,
owner: repoOwner,
repo: repoName,
path: repoPath,
ref: github.context.sha
ref: repoRef,
});

return Buffer.from(response.data.content, response.data.encoding).toString();
Expand Down