Skip to content

Commit b81bb20

Browse files
committed
feat(ng-dev): create a caretaker config subcommand for updated repo properties
Create a config subcommand to allow for `get` and `set` actions against a defined list of custom properties on angular repositories. These will be used to set the merge mode of the repository being configured.
1 parent c283d75 commit b81bb20

File tree

9 files changed

+266
-2
lines changed

9 files changed

+266
-2
lines changed

ng-dev/caretaker/BUILD.bazel

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,7 @@ ts_project(
1616
"//ng-dev:node_modules/typed-graphqlify",
1717
"//ng-dev:node_modules/yaml",
1818
"//ng-dev:node_modules/yargs",
19+
"//ng-dev/caretaker/config",
1920
"//ng-dev/release/versioning",
2021
"//ng-dev/utils",
2122
"//ng-dev/utils:g3_sync_config",

ng-dev/caretaker/cli.ts

Lines changed: 12 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -11,15 +11,25 @@ import {Argv} from 'yargs';
1111
import {assertValidCaretakerConfig, assertValidGithubConfig, getConfig} from '../utils/config.js';
1212
import {CheckModule} from './check/cli.js';
1313
import {HandoffModule} from './handoff/cli.js';
14+
import {RepositoryConfigSetModule} from './config/set/cli.js';
15+
import {RepositoryConfigGetModule} from './config/get/cli.js';
16+
import {StartReleaseModule} from './start-release/cli.js';
1417

1518
/** Build the parser for the caretaker commands. */
1619
export function buildCaretakerParser(argv: Argv) {
17-
return argv.middleware(caretakerCommandCanRun, false).command(CheckModule).command(HandoffModule);
20+
return argv
21+
.middleware(caretakerCommandCanRun, false)
22+
.command('config <set|get>', 'Manage the repository configuration', (yargs: Argv) =>
23+
yargs.command(RepositoryConfigSetModule).command(RepositoryConfigGetModule),
24+
)
25+
.command(StartReleaseModule)
26+
.command(CheckModule)
27+
.command(HandoffModule);
1828
}
1929

2030
function caretakerCommandCanRun() {
2131
try {
22-
getConfig([assertValidCaretakerConfig, assertValidGithubConfig]);
32+
getConfig([assertValidGithubConfig, assertValidCaretakerConfig]);
2333
} catch {
2434
info('The `caretaker` command is not enabled in this repository.');
2535
info(` To enable it, provide a caretaker config in the repository's .ng-dev/ directory`);
Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+
load("//tools:defaults.bzl", "ts_project")
2+
3+
ts_project(
4+
name = "config",
5+
srcs = glob(["**/*.ts"]),
6+
visibility = ["//ng-dev/caretaker:__subpackages__"],
7+
deps = [
8+
"//ng-dev:node_modules/@types/yargs",
9+
"//ng-dev:node_modules/yargs",
10+
"//ng-dev/utils",
11+
],
12+
)
Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,14 @@
1+
/**
2+
* @license
3+
* Copyright Google LLC
4+
*
5+
* Use of this source code is governed by an MIT-style license that can be
6+
* found in the LICENSE file at https://angular.io/license
7+
*/
8+
9+
/** The available configuration options to be set by the caretaker. */
10+
export const RepositoryConfigOptions = {
11+
'merge-mode': {
12+
options: ['team-only', 'lockdown', 'release', 'caretaker-only'],
13+
},
14+
};

ng-dev/caretaker/config/get/cli.ts

Lines changed: 45 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,45 @@
1+
/**
2+
* @license
3+
* Copyright Google LLC
4+
*
5+
* Use of this source code is governed by an MIT-style license that can be
6+
* found in the LICENSE file at https://angular.io/license
7+
*/
8+
9+
import {Arguments, Argv, CommandModule} from 'yargs';
10+
11+
import {addGithubTokenOption} from '../../../utils/git/github-yargs';
12+
import {RepositoryConfigOptions} from '../config-options';
13+
import {bold, Log} from '../../../utils/logging';
14+
import {getRepoConfigValue, getRepoConfigValueDefinition} from './index';
15+
16+
interface GetOptions {
17+
key: string;
18+
}
19+
20+
function getBuilder(argv: Argv) {
21+
return addGithubTokenOption(argv).positional('key', {
22+
type: 'string',
23+
demandOption: true,
24+
choices: Object.keys(RepositoryConfigOptions),
25+
});
26+
}
27+
28+
async function getHandler({key}: Arguments<GetOptions>) {
29+
const [definition, value] = await Promise.all([
30+
getRepoConfigValueDefinition(key),
31+
getRepoConfigValue(key),
32+
]);
33+
Log.info(`Configuration: ${bold(key)}`);
34+
Log.info(` Options: ${definition.allowed_values?.join(', ')}`);
35+
Log.info(` Default: ${definition.default_value}`);
36+
Log.info();
37+
Log.info(` Current Value: ${bold(value)}`);
38+
}
39+
40+
export const RepositoryConfigGetModule: CommandModule<{}, GetOptions> = {
41+
handler: getHandler,
42+
builder: getBuilder,
43+
command: 'get <key>',
44+
describe: 'Get the current value of a provided repository configuration',
45+
};
Lines changed: 42 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,42 @@
1+
/**
2+
* @license
3+
* Copyright Google LLC
4+
*
5+
* Use of this source code is governed by an MIT-style license that can be
6+
* found in the LICENSE file at https://angular.io/license
7+
*/
8+
9+
import {AuthenticatedGitClient} from '../../../utils/git/authenticated-git-client';
10+
11+
export async function getRepoConfigValue(
12+
key: string,
13+
git: Promise<AuthenticatedGitClient> | AuthenticatedGitClient = AuthenticatedGitClient.get(),
14+
) {
15+
git = await git;
16+
17+
const {data: properties} = await git.github.repos.customPropertiesForReposGetRepositoryValues({
18+
owner: git.remoteConfig.owner,
19+
repo: git.remoteConfig.name,
20+
});
21+
22+
const property = properties.find(({property_name}) => property_name === key);
23+
if (property === undefined) {
24+
throw Error(`No repository configuration value with the key: ${key}`);
25+
}
26+
27+
return property.value;
28+
}
29+
30+
export async function getRepoConfigValueDefinition(
31+
key: string,
32+
git: Promise<AuthenticatedGitClient> | AuthenticatedGitClient = AuthenticatedGitClient.get(),
33+
) {
34+
git = await git;
35+
36+
return git.github.orgs
37+
.customPropertiesForReposGetOrganizationDefinition({
38+
custom_property_name: key,
39+
org: git.remoteConfig.owner,
40+
})
41+
.then(({data}) => data);
42+
}

ng-dev/caretaker/config/set/cli.ts

Lines changed: 57 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,57 @@
1+
/**
2+
* @license
3+
* Copyright Google LLC
4+
*
5+
* Use of this source code is governed by an MIT-style license that can be
6+
* found in the LICENSE file at https://angular.io/license
7+
*/
8+
9+
import {Arguments, Argv, CommandModule} from 'yargs';
10+
import {Log, yellow, red, green} from '../../../utils/logging';
11+
import {addGithubTokenOption} from '../../../utils/git/github-yargs';
12+
import {RepositoryConfigOptions} from '../config-options';
13+
import {setRepoConfigValue} from './index';
14+
15+
interface SetOptions {
16+
key: string;
17+
value: string;
18+
}
19+
20+
function setBuilder(argv: Argv) {
21+
return addGithubTokenOption(argv)
22+
.positional('key', {
23+
type: 'string',
24+
demandOption: true,
25+
choices: Object.keys(RepositoryConfigOptions),
26+
})
27+
.positional('value', {
28+
type: 'string',
29+
demandOption: true,
30+
});
31+
}
32+
33+
async function setHandler({key, value}: Arguments<SetOptions>) {
34+
try {
35+
const updated = await setRepoConfigValue(key, value);
36+
if (updated === false) {
37+
Log.info(`${yellow('⚠')} No update required as ${key} was already set to ${value}`);
38+
return;
39+
}
40+
Log.info(`${green('✔')} Successfully Updated ${key} to ${value}`);
41+
} catch (err) {
42+
Log.info(`${red('✘')} Failed to update ${key} value`);
43+
if (err instanceof Error) {
44+
Log.info(err.message);
45+
Log.debug(err.stack);
46+
return;
47+
}
48+
Log.info(err);
49+
}
50+
}
51+
52+
export const RepositoryConfigSetModule: CommandModule<{}, SetOptions> = {
53+
handler: setHandler,
54+
builder: setBuilder,
55+
command: 'set <key> <value>',
56+
describe: 'Set the current value of a provided repository configuration',
57+
};
Lines changed: 49 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,49 @@
1+
/**
2+
* @license
3+
* Copyright Google LLC
4+
*
5+
* Use of this source code is governed by an MIT-style license that can be
6+
* found in the LICENSE file at https://angular.io/license
7+
*/
8+
9+
import {AuthenticatedGitClient} from '../../../utils/git/authenticated-git-client';
10+
import {Log} from '../../../utils/logging';
11+
import {getRepoConfigValue, getRepoConfigValueDefinition} from '../get/index';
12+
13+
export async function setRepoConfigValue(
14+
key: string,
15+
value: string,
16+
git: Promise<AuthenticatedGitClient> | AuthenticatedGitClient = AuthenticatedGitClient.get(),
17+
) {
18+
git = await git;
19+
const currentValue = await getRepoConfigValue(key, git);
20+
if (currentValue === value) {
21+
Log.debug(
22+
'Skipping update of repository configuration value as it is already set to the provided value',
23+
);
24+
return false;
25+
}
26+
const {value_type, allowed_values} = await getRepoConfigValueDefinition(key, git);
27+
28+
if (value_type !== 'single_select') {
29+
throw Error(
30+
`Unable to update ${key} as its type is ${value_type}, currently the only supported ` +
31+
`configuration type is single_select`,
32+
);
33+
}
34+
35+
if (!allowed_values!.includes(value)) {
36+
throw Error(
37+
`Unable to update ${key}. The value provided must use one of: ${allowed_values!.join(', ')}\n` +
38+
`But "${value}" was provided as the value`,
39+
);
40+
}
41+
42+
await git.github.repos.customPropertiesForReposCreateOrUpdateRepositoryValues({
43+
owner: git.remoteConfig.owner,
44+
repo: git.remoteConfig.name,
45+
properties: [{value, property_name: key}],
46+
});
47+
48+
return true;
49+
}
Lines changed: 34 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,34 @@
1+
/**
2+
* @license
3+
* Copyright Google LLC
4+
*
5+
* Use of this source code is governed by an MIT-style license that can be
6+
* found in the LICENSE file at https://angular.io/license
7+
*/
8+
9+
import {CommandModule} from 'yargs';
10+
import {green, Log, red} from '../../utils/logging';
11+
import {addGithubTokenOption} from '../../utils/git/github-yargs';
12+
import {setRepoConfigValue} from '../config/set/index';
13+
14+
async function startReleaseHandler() {
15+
try {
16+
await setRepoConfigValue('merge-mode', 'release');
17+
Log.info(`${green('✔')} Repository is set for release`);
18+
} catch (err) {
19+
Log.info(`${red('✘')} Failed to setup of repository for release`);
20+
if (err instanceof Error) {
21+
Log.info(err.message);
22+
Log.debug(err.stack);
23+
return;
24+
}
25+
Log.info(err);
26+
}
27+
}
28+
29+
export const StartReleaseModule: CommandModule<{}, {}> = {
30+
builder: addGithubTokenOption,
31+
handler: startReleaseHandler,
32+
command: 'start-release',
33+
describe: 'Set the repository into the correct merge mode for releasing',
34+
};

0 commit comments

Comments
 (0)