diff --git a/.gitignore b/.gitignore index a2c6fd64847..d8eefeebf04 100644 --- a/.gitignore +++ b/.gitignore @@ -58,4 +58,6 @@ tsconfig.json !# synthesized by projen, but committed to git !/.github/workflows/release.yml !# synthesized by projen, but committed to git -!/.github/workflows/build.yml \ No newline at end of file +!/.github/workflows/build.yml +!# synthesized by projen, but committed to git +!/.mergify.yml \ No newline at end of file diff --git a/.mergify.yml b/.mergify.yml new file mode 100644 index 00000000000..ce5c5c286f4 --- /dev/null +++ b/.mergify.yml @@ -0,0 +1,14 @@ +# Generated by projen. To modify, edit .projenrc.js and run "npx projen". + +pull_request_rules: + - name: Automatic merge on approval and successful build + conditions: + - "#approved-reviews-by>=1" + - status-success=build + actions: + merge: + method: squash + commit_message: title+body + strict: smart + strict_method: merge + delete_head_branch: {} diff --git a/lib/jsii-project.ts b/lib/jsii-project.ts index 7b8cadde0ca..f3e1d35d9c1 100644 --- a/lib/jsii-project.ts +++ b/lib/jsii-project.ts @@ -5,6 +5,7 @@ import { GithubWorkflow } from './github-workflow'; import { Project } from './project'; import { PROJEN_VERSION } from './common'; import { Jest } from './jest'; +import { Mergify } from './mergify'; export interface JsiiProjectOptions extends CommonOptions { /** @@ -43,6 +44,12 @@ export interface JsiiProjectOptions extends CommonOptions { * @default true */ readonly jest?: boolean; + + /** + * Add mergify configuration + * @default true + */ + readonly mergify?: boolean; } export enum Stability { @@ -163,12 +170,38 @@ export class JsiiProject extends NodeProject { this.npmignore.comment('include .jsii manifest'); this.npmignore.include('.jsii'); - new JsiiBuildWorkflow(this, options.workflowOptions); + const buildWorkflow = new JsiiBuildWorkflow(this, options.workflowOptions); const jest = options.jest ?? true; if (jest) { new Jest(this); } + + const mergify = options.mergify ?? true; + if (mergify) { + const m = new Mergify(this); + m.addRule({ + name: 'Automatic merge on approval and successful build', + conditions: [ + '#approved-reviews-by>=1', + `status-success=${buildWorkflow.jobName}`, + ], + actions: { + merge: { + // squash all commits into a single commit when merging + method: 'squash', + + // use PR title+body as the commit message + commit_message: 'title+body', + + // update PR branch so it's up-to-date before merging + strict: 'smart', + strict_method: 'merge', + }, + delete_head_branch: { }, + }, + }); + } } } @@ -340,13 +373,18 @@ class JsiiReleaseWorkflow extends GithubWorkflow { } export class JsiiBuildWorkflow extends GithubWorkflow { + + public readonly jobName: string; + constructor(project: Project, options: WorkflowOptions = { }) { super(project, 'build', { name: 'Build' }); + this.jobName = 'build'; + this.on({ pull_request: { } }); this.addJobs({ - build: { + [this.jobName]: { 'runs-on': 'ubuntu-latest', container: { image: 'jsii/superchain', diff --git a/lib/json.ts b/lib/json.ts index 375f334578c..b002943dcf0 100644 --- a/lib/json.ts +++ b/lib/json.ts @@ -6,7 +6,7 @@ export interface JsonFileOptions extends FileBaseOptions { } export class JsonFile extends FileBase { - private readonly obj: object; + protected readonly obj: object; constructor(project: Project, filePath: string, options: JsonFileOptions) { super(project, filePath, options); diff --git a/lib/mergify.ts b/lib/mergify.ts new file mode 100644 index 00000000000..3b95192f6d2 --- /dev/null +++ b/lib/mergify.ts @@ -0,0 +1,32 @@ +import { Construct } from 'constructs'; +import { Project } from './project'; +import { YamlFile } from './yaml'; + +export interface MergifyRule { + readonly name: string; + readonly conditions: string[]; + readonly actions: { [action: string]: any }; +} + +export interface MergifyOptions { + readonly rules?: MergifyRule[]; +} + +export class Mergify extends Construct { + private readonly rules = new Array(); + + constructor(project: Project) { + super(project, 'mergify'); + + new YamlFile(project, '.mergify.yml', { + committed: true, // must be committed for mergify to be able to find it dah! + obj: { + pull_request_rules: this.rules, + }, + }); + } + + public addRule(rule: MergifyRule) { + this.rules.push(rule); + } +} \ No newline at end of file diff --git a/lib/yaml.ts b/lib/yaml.ts new file mode 100644 index 00000000000..5e03c33d106 --- /dev/null +++ b/lib/yaml.ts @@ -0,0 +1,24 @@ +import { JsonFile, JsonFileOptions } from './json'; +import * as YAML from 'yaml'; +import { Project } from './project'; +import { GENERATION_DISCLAIMER } from './common'; + +export interface YamlFileOptions extends JsonFileOptions { + +} + +export class YamlFile extends JsonFile { + constructor(project: Project, filePath: string, options: YamlFileOptions) { + super(project, filePath, options); + } + + protected get data() { + // sanitize object references by serializaing and deserializing to JSON + const sanitized = JSON.parse(JSON.stringify(this.obj)); + return [ + `# ${GENERATION_DISCLAIMER}`, + '', + YAML.stringify(sanitized, { indent: 2 }), + ].join('\n') + } +} \ No newline at end of file