Feature/commit validation
MODIFY
CONTRIBUTING.md
CONTRIBUTING.md
@@ -107,13 +107,44 @@ If you are adding a new lint rule, or modifying some core code, you might need t
./rome run scripts/generate-all-files
```
+## Commit messages
+
+Internally, the Rome team adheres as closely as possible to the [conventional commit specification](https://www.conventionalcommits.org/en/v1.0.0-beta.2/).
+Following this convention encourages commit best-practices and facilitates commit-powered features like change log generation.
+
+
+The following commit prefixes are supported:
+- `feat:`, a new feature
+- `fix:`, a bugfix
+- `docs:`, a documentation update
+- `test`, a test update
+- `chore:`, project housekeeping
+- `perf:`, project performance
+- `refactor:`, refactor of the code without change in functionality
+
+ Below are examples of well-formatted commits:
+
+```
+feat(compiler): implement parsing for new type of files
+fix: fix nasty unhandled error
+docs: fix link to website page
+test(lint): add more cases to handle invalid rules
+```
+
+### Commit verification
+
+Commits will be verified as part of CI **only on the main branch**. This means that CI will **not** verify commits in pull requests, since these often contain many work-in-progress commits that may not be well-formed, and because the `main` branch uses squash-based merging.
+
+### Creating pull requests
+
+When creating a new pull request, it's preferable to use a conventional commit-formatted title, as this title will be used as the default commit message on the squashed commit after merging.
## Scripts
Here are some other scripts that you might find useful.
### `lint-create-rule`
-This is used to generate new lint rules and boilperlate.
+This is used to generate new lint rules and boilerplate.
```bash
./rome run scripts/lint-create-rule [category]/[ruleName]

MODIFY
scripts/_utils.ts
scripts/_utils.ts
@@ -25,6 +25,17 @@ export const ROOT = findRoot();
export const INTERNAL = ROOT.append("internal");
export const PUBLIC_PACKAGES = ROOT.append("public-packages");
+export const GIT_PLACEHOLDERS = {
+ authorEmail: "%aE",
+ authorName: "%aN",
+ body: "%b",
+ commit: "%H",
+ date: "%ad",
+ rawBody: "%B",
+ refNames: "%d",
+ subject: "%s",
+};
+
// This is only necessary because we have poor support for __dirname in the bundler
function findRoot(): AbsoluteFilePath {
let pickNext = false;

MODIFY
scripts/changelog.ts
scripts/changelog.ts
import {markup} from "@internal/markup";
-import {parseCommit} from "@internal/commit-parser";
import {readFileTextMeta} from "@internal/fs";
-import {AbsoluteFilePath, createUIDPath} from "@internal/path";
-import {PUBLIC_PACKAGES, ROOT, reporter, writeFile} from "./_utils";
+import {AbsoluteFilePath} from "@internal/path";
+import {
+ GIT_PLACEHOLDERS,
+ PUBLIC_PACKAGES,
+ ROOT,
+ reporter,
+ writeFile,
+} from "./_utils";
import {dedent} from "@internal/string-utils";
import {json} from "@internal/codec-config";
import child = require("child_process");
import https = require("https");
import http = require("http");
-
-const PROPERTY_DELIM = "<--ROME-PROPERTY-->";
-const LINE_DELIM = "<--ROME-LINE-->";
-const GIT_PLACEHOLDERS = {
- authorEmail: "%aE",
- authorName: "%aN",
- body: "%b",
- commit: "%H",
- date: "%ad",
- rawBody: "%B",
- refNames: "%d",
- subject: "%s",
-};
-
-interface Commit {
- authorEmail: string;
- authorName: string;
- body: string;
- commit: string;
- date: string;
- rawBody: string;
- refNames?: string;
- subject: string;
- meta?: {
- breaking: boolean;
- commitType: string;
- custom: boolean;
- scope: string;
- };
-}
+import {Commit, parseCommitLog} from "./vcs/utils";
const ReleaseType = {
major: "major",
@@ -215,67 +191,6 @@ async function isNewVersion(version: string): Promise<boolean> {
return res.statusCode === 404;
}
-/**
- * Parse a raw git log into an array of commit objects
- *
- * @param config - Map of result key names to git pretty placeholders
- * @param opts - Specify a starting and an ending commit
- * @returns - List of commit objects
- */
-function parseCommitLog(
- config: Record<string, string>,
- opts?: {
- from: string;
- to: string;
- },
-): Commit[] {
- const keys = Object.keys(config);
-
- const cmd = keys.reduce(
- (full, key) => `${full}${full ? `${PROPERTY_DELIM}` : ""}${config[key]}`,
- "",
- );
-
- const log = child.spawnSync(
- "git",
- [
- "log",
- `--pretty='format:${cmd}${LINE_DELIM}'`,
- opts ? `${opts.from}..${opts.to}` : ".",
- ],
- ).stdout.toString();
-
- const commits = log.split(LINE_DELIM).map((rawCommit) => {
- const values = rawCommit.trim().split(PROPERTY_DELIM);
- return keys.reduce<Commit>(
- (commit, key, index) => {
- const newCommit = {
- ...commit,
- [key]: values[index],
- };
-
- if (config[key] === "%B") {
- const ast = parseCommit({
- input: values[index],
- path: createUIDPath(`commit/${index}`),
- });
- newCommit.meta = {
- breaking: ast.breaking,
- commitType: ast.commitType,
- custom: ast.custom,
- scope: ast.scope,
- };
- }
-
- return newCommit;
- },
- {} as Commit,
- );
- });
-
- return commits;
-}
-
/**
* Raise an error using the reporter and exit the process
*

ADD
scripts/vcs/constants.ts
+export const PERMITTED_COMMIT_TYPES = ["feat", "chore", "fix", "docs", "test"];

ADD
scripts/vcs/utils.ts
+import {parseCommit} from "@internal/commit-parser";
+import child = require("child_process");
+import {createUIDPath} from "@internal/path";
+
+const PROPERTY_DELIM = "<--ROME-PROPERTY-->";
+const LINE_DELIM = "<--ROME-LINE-->";
+
+export interface Commit {
+ authorEmail: string;
+ authorName: string;
+ body: string;
+ commit: string;
+ date: string;
+ rawBody: string;
+ refNames?: string;
+ subject: string;
+ meta?: {
+ breaking: boolean;
+ commitType: string;
+ custom: boolean;
+ scope: string;
+ };
+}
+
+/**
+ * Parse a raw git log into an array of commit objects
+ *
+ * @param config - Map of result key names to git pretty placeholders
+ * @param opts - Specify a starting and an ending commit
+ * @returns - List of commit objects
+ */
+export function parseCommitLog(
+ config: Record<string, string>,
+ opts?: {
+ from: string;
+ to: string;
+ },
+): Commit[] {
+ const keys = Object.keys(config);
+
+ const cmd = keys.reduce(
+ (full, key) => `${full}${full ? `${PROPERTY_DELIM}` : ""}${config[key]}`,
+ "",
+ );
+
+ const log = child.spawnSync(
+ "git",
+ [
+ "log",
+ `--pretty='format:${cmd}${LINE_DELIM}'`,
+ opts ? `${opts.from}..${opts.to}` : ".",
+ ],
+ ).stdout.toString();
+
+ return log.split(LINE_DELIM).reduce(
+ (totalCommits, rawCommit) => {
+ const values = rawCommit.trim().split(PROPERTY_DELIM);
+ if (values.length <= 1) {
+ return totalCommits;
+ }
+
+ totalCommits.push(
+ keys.reduce<Commit>(
+ (commit, key, index) => {
+ const newCommit = {
+ ...commit,
+ [key]: values[index],
+ };
+
+ if (config[key] === "%B") {
+ const ast = parseCommit({
+ input: values[index],
+ path: createUIDPath(`commit/${index}`),
+ });
+ newCommit.meta = {
+ breaking: ast.breaking,
+ commitType: ast.commitType,
+ custom: ast.custom,
+ scope: ast.scope,
+ };
+ }
+
+ return newCommit;
+ },
+ {} as Commit,
+ ),
+ );
+
+ return totalCommits;
+ },
+ [] as Commit[],
+ );
+}

ADD
scripts/vcs/verify-commits.ts
+import {GIT_PLACEHOLDERS, reporter} from "../_utils";
+import {markup} from "@internal/markup";
+import {PERMITTED_COMMIT_TYPES} from "./constants";
+import {parseCommitLog} from "./utils";
+
+export async function main(): Promise<number> {
+ const commits = parseCommitLog(
+ GIT_PLACEHOLDERS,
+ {
+ from: "main",
+ to: "HEAD",
+ },
+ );
+
+ const invalidCommits = commits.filter((commit) => {
+ if (!commit.meta) {
+ return true;
+ }
+
+ return !PERMITTED_COMMIT_TYPES.includes(commit.meta.commitType);
+ });
+
+ if (invalidCommits.length === 0) {
+ reporter.success(markup`All the commits follow the correct pattern.`);
+ return 0;
+ }
+ reporter.warn(
+ markup`One or more commits do not follow the correct pattern, here's the list:`,
+ );
+ reporter.list(
+ invalidCommits.map((commit) => {
+ return markup`Commit ${commit.commit}, with message <emphasis>${commit.rawBody}</emphasis>`;
+ }),
+ {
+ ordered: true,
+ },
+ );
+
+ reporter.info(
+ markup`You can check <filelink target="../CONTRIBUTING.md">the internal guidelines (CONTRIBUTING.md)</filelink> and read about how to write a correct commit message.`,
+ );
+ reporter.info(
+ markup`You can run <emphasis>git rebase -i HEAD~${commits.length}</emphasis> and reword the incorrect commits.`,
+ );
+
+ return 1;
+}

Feature/commit validation (#1011)

Co-authored-by: Paul Bouchon <mail@bitpshr.net>