-
Notifications
You must be signed in to change notification settings - Fork 15
Expand file tree
/
Copy pathlintcommit.js
More file actions
178 lines (153 loc) · 4.78 KB
/
lintcommit.js
File metadata and controls
178 lines (153 loc) · 4.78 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
// Checks that a PR title conforms to conventional commits
// (https://www.conventionalcommits.org/).
//
// To run self-tests, run this script:
//
// node lintcommit.js test
import { readFileSync, appendFileSync } from "fs";
const types = new Set([
"build",
"chore",
"ci",
"config",
"deps",
"docs",
"feat",
"fix",
"perf",
"refactor",
"revert",
"style",
"test",
"types",
]);
const scopes = new Set(["sdk", "examples"]);
/**
* Checks that a pull request title, or commit message subject, follows the expected format:
*
* type(scope): message
*
* Returns undefined if `title` is valid, else an error message.
*/
function validateTitle(title) {
const parts = title.split(":");
const subject = parts.slice(1).join(":").trim();
if (title.startsWith("Merge")) {
return undefined;
}
if (parts.length < 2) {
return "missing colon (:) char";
}
const typeScope = parts[0];
const [type, scope] = typeScope.split(/\(([^)]+)\)$/);
if (/\s+/.test(type)) {
return `type contains whitespace: "${type}"`;
} else if (!types.has(type)) {
return `invalid type "${type}"`;
} else if (!scope && typeScope.includes("(")) {
return `must be formatted like type(scope):`;
} else if (scope && scope.length > 30) {
return "invalid scope (must be <=30 chars)";
} else if (scope && /[^- a-z0-9]+/.test(scope)) {
return `invalid scope (must be lowercase, ascii only): "${scope}"`;
} else if (scope && !scopes.has(scope)) {
return `invalid scope "${scope}" (valid scopes are ${Array.from(scopes).join(", ")})`;
} else if (subject.length === 0) {
return "empty subject";
} else if (subject.length > 50) {
return "invalid subject (must be <=50 chars)";
}
return undefined;
}
function run() {
const eventData = JSON.parse(
readFileSync(process.env.GITHUB_EVENT_PATH, "utf8"),
);
const pullRequest = eventData.pull_request;
// console.log(eventData)
if (!pullRequest) {
console.info("No pull request found in the context");
return;
}
const title = pullRequest.title;
const failReason = validateTitle(title);
const msg = failReason
? `
Invalid pull request title: \`${title}\`
* Problem: ${failReason}
* Expected format: \`type(scope): subject...\`
* type: one of (${Array.from(types).join(", ")})
* scope: optional, lowercase, <30 chars
* subject: must be <50 chars
* Hint: *close and re-open the PR* to re-trigger CI (after fixing the PR title).
`
: `Pull request title matches the expected format`;
if (process.env.GITHUB_STEP_SUMMARY) {
appendFileSync(process.env.GITHUB_STEP_SUMMARY, msg);
}
if (failReason) {
console.error(msg);
process.exit(1);
} else {
console.info(msg);
}
}
function _test() {
const tests = {
" foo(scope): bar": 'type contains whitespace: " foo"',
"build: update build process": undefined,
"chore: update dependencies": undefined,
"ci: configure CI/CD": undefined,
"config: update configuration files": undefined,
"deps: bump aws-sdk group with 5 updates": undefined,
"docs: update documentation": undefined,
"feat(sdk): add new feature": undefined,
"feat(sdk):": "empty subject",
"feat foo):": 'type contains whitespace: "feat foo)"',
"feat(foo)): sujet": 'invalid type "feat(foo))"',
"feat(foo: sujet": 'invalid type "feat(foo"',
"feat(Q Foo Bar): bar":
'invalid scope (must be lowercase, ascii only): "Q Foo Bar"',
"feat(sdk): bar": undefined,
"feat(sdk): x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x ":
"invalid subject (must be <=50 chars)",
"feat: foo": undefined,
"fix: foo": undefined,
"fix(sdk): resolve issue": undefined,
"foo (scope): bar": 'type contains whitespace: "foo "',
"invalid title": "missing colon (:) char",
"perf: optimize performance": undefined,
"refactor: improve code structure": undefined,
"revert: feat: add new feature": undefined,
"style: format code": undefined,
"test: add new tests": undefined,
"types: add type definitions": undefined,
"Merge staging into feature/lambda-get-started": undefined,
"feat(foo): fix the types":
'invalid scope "foo" (valid scopes are sdk, examples)',
};
let passed = 0;
let failed = 0;
for (const [title, expected] of Object.entries(tests)) {
const result = validateTitle(title);
if (result === expected) {
console.log(`✅ Test passed for "${title}"`);
passed++;
} else {
console.log(
`❌ Test failed for "${title}" (expected "${expected}", got "${result}")`,
);
failed++;
}
}
console.log(`\n${passed} tests passed, ${failed} tests failed`);
}
function main() {
const mode = process.argv[2];
if (mode === "test") {
_test();
} else {
run();
}
}
main();