Skip to content

Commit 43b5ebc

Browse files
feat(gulp): add githooks from igniteui-angular
1 parent a8e0487 commit 43b5ebc

10 files changed

Lines changed: 332 additions & 2 deletions

File tree

.hooks/prepare-commit-msg

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,19 @@
1+
#!/usr/bin/env node
2+
'use strict';
3+
4+
var validate = require('./scripts/validate'),
5+
fs = require('fs');
6+
7+
var options = {
8+
style: "default"
9+
};
10+
11+
var text = fs.readFileSync(process.argv[2]).toString();
12+
13+
var errors = [];
14+
errors = validate(text, options, errors);
15+
16+
if (errors.length > 0) {
17+
process.stdout.write(errors[0].toString());
18+
process.exitCode = 1;
19+
}

.hooks/scripts/common.js

Lines changed: 44 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,44 @@
1+
'use strict';
2+
3+
module.exports = {
4+
matchType: (types, line) => {
5+
return types.some(function(type) {
6+
if (line.startsWith(type)) {
7+
return true;
8+
}
9+
10+
return false;
11+
});
12+
},
13+
14+
errorFactory: (prefix, message, additionalParams) => {
15+
var result = '';
16+
if (prefix) {
17+
result = prefix;
18+
}
19+
20+
result += message;
21+
22+
if (additionalParams) {
23+
result += additionalParams;
24+
}
25+
26+
return result;
27+
},
28+
29+
errorMessages: {
30+
MESSAGE_SHOULD_START_WITH_TYPE: 'Message should start with one of the following types:\n',
31+
NEED_OPENING_PARENTHESIS: 'Need an opening parenthesis right after the type: (',
32+
TYPE_WAS_EMPTY: '<type> was empty, it must be one of these: \n',
33+
TYPE_WAS_INVALID: '<type> was invalid, It has to be one of these:\n',
34+
NEED_CLOSING_PARENTHESIS: 'Need a closing parenthesis after scope declaration: <scope>")"',
35+
SCOPE_WAS_EMPTY: "<scope> cannot be empty!",
36+
NEED_A_COLON: "Need a colon after the closing parenthesis: ):",
37+
NEED_SPACE_AND_SUBJECT: 'Need a space and <subject> after colon: ": <subject>"',
38+
SUBJECT_WAS_EMPTY: '<subject> must not be empty',
39+
WRONG_REVERT_STRUCT: 'If this is a revert of a previous commit, please write:\n',
40+
UNDEFINED_OF_COMMIT_MSG: 'Commit message is undefined, abort with error',
41+
MSG_IS_NOT_A_STRING: 'Commit message is not a string, abort with error',
42+
MSG_IS_EMPTY: 'Commit message is empty, abort with error'
43+
}
44+
}
Lines changed: 75 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,75 @@
1+
'use strict';
2+
3+
var matchType = require('../common').matchType,
4+
errorFactory = require('../common').errorFactory,
5+
errMessages = require('../common').errorMessages;
6+
7+
module.exports = (lines, options, errors) => {
8+
var scheme = '<type>(<scope>): <subject> <#issue|optional>';
9+
var prefix = `First line must be ${scheme}\n`;
10+
11+
var line = lines[0];
12+
if (line.startsWith('revert: ')) {
13+
line = line.replace(/^revert: /, '');
14+
prefix = `First line must be revert: ${scheme}\n`;
15+
} else if (line.startsWith('revert')) {
16+
errors.push(errorFactory(errMessages.WRONG_REVERT_STRUCT, 'revert: ' + scheme));
17+
18+
return;
19+
}
20+
21+
if (!matchType(options.types, line)) {
22+
errors.push(errorFactory(prefix, errMessages.MESSAGE_SHOULD_START_WITH_TYPE, options.types.join(', ')));
23+
24+
return;
25+
}
26+
27+
if (line.indexOf('(') === -1) {
28+
errors.push(errorFactory(prefix, errMessages.NEED_OPENING_PARENTHESIS));
29+
30+
return;
31+
}
32+
33+
var type = line.replace(/\(.*/, '');
34+
if (options.types.indexOf(type) === -1) {
35+
errors.push(errorFactory(prefix, errMessages.TYPE_WAS_INVALID, options.types.join(', ')));
36+
37+
return;
38+
}
39+
40+
if (line.indexOf(')') === -1) {
41+
errors.push(errorFactory(prefix, errMessages.NEED_CLOSING_PARENTHESIS));
42+
43+
return;
44+
}
45+
46+
var scope = line.slice(line.indexOf('(') + 1, line.indexOf(')'));
47+
if (scope.length === 0) {
48+
errors.push(errorFactory(prefix, errMessages.SCOPE_WAS_EMPTY));
49+
}
50+
51+
if (line.indexOf(type + '(' + scope + '):') === - 1) {
52+
errors.push(errorFactory(prefix, errMessages.NEED_A_COLON));
53+
54+
return;
55+
}
56+
57+
var subject = line.split(':')[1];
58+
if (subject === "" || !subject.startsWith(' ')) {
59+
errors.push(errorFactory(prefix, errMessages.NEED_SPACE_AND_SUBJECT));
60+
61+
return;
62+
}
63+
64+
subject = subject.trim();
65+
66+
if (subject.length === 0) {
67+
errors.push(errorFactory(errMessages.SUBJECT_WAS_EMPTY));
68+
69+
return;
70+
}
71+
72+
if (subject.length < options.subjectLimits) {
73+
errors.push(`<subject> should contains at least ${options.subjectLimits} characters`);
74+
}
75+
}
Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,23 @@
1+
'use strict';
2+
3+
var path = require('path');
4+
5+
var defaults = {
6+
default: {
7+
style: 'default',
8+
subjectLimits: 15,
9+
lineLimits: {
10+
firstLine: 80,
11+
otherLine: 80,
12+
},
13+
issuePattern: '(#)[0-9]+',
14+
typesWithMandatoryIssue: [],
15+
guidelinesUrl: 'https://bit.ly/angular-guidelines',
16+
types: [
17+
'feat', 'fix', 'docs', 'style', 'refactor', 'perf', 'test', 'chore', 'build', 'ci', 'revert'
18+
],
19+
},
20+
oldMessagePath: path.join('.git', 'COMMIT_EDITMSG_OLD')
21+
}
22+
23+
module.exports = defaults;
Lines changed: 33 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,33 @@
1+
'use strict';
2+
3+
var matchType = require('../common').matchType,
4+
errorFactory = require('../common').errorFactory;
5+
6+
module.exports = (lines, options, errors) => {
7+
var ticket = new RegExp(options.issuePattern);
8+
var whetherIssueIsMandatory = false,
9+
wheterMatchAnyIssueRef = false;
10+
11+
12+
if (matchType(options.typesWithMandatoryIssue, lines[0])) {
13+
whetherIssueIsMandatory = true;
14+
}
15+
16+
lines.forEach((line) => {
17+
line = line.trim();
18+
if (line === "") {
19+
return;
20+
}
21+
22+
if (ticket.test(line)) {
23+
wheterMatchAnyIssueRef = true;
24+
return;
25+
}
26+
});
27+
28+
if (whetherIssueIsMandatory && !wheterMatchAnyIssueRef) {
29+
errors.push(errorFactory(
30+
`The issue reference for (${options.typesWithMandatoryIssue.join(', ')}) types is mandatory!\n`,
31+
"Please add at least one related issue. E.g: Closes #31, Closes #45"));
32+
}
33+
}
Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,22 @@
1+
'use strict';
2+
3+
module.exports = (lines, options, errors) => {
4+
var limits = options.lineLimits;
5+
lines.forEach((line, index) => {
6+
line = line.trim();
7+
if (index === 0) {
8+
if (line.length === 0) {
9+
errors.push('First line of commit message must not be empty!');
10+
} else if (line.length > limits.firstLine) {
11+
errors.push(`First line of commit message must be no logner than ${limits.firstLine} characters!`);
12+
}
13+
} else if (index === 1 && line.length > 0) {
14+
errors.push('Second line must be always empty!');
15+
} else if (line.length > limits.otherLine) {
16+
errors.push(
17+
`Commit message line ${index + 1} is too long: ${line.length}, only ${limits.otherLine} are allowed.
18+
The line is: ${line.substring(0, 20)} [...]!`
19+
);
20+
}
21+
});
22+
}

.hooks/scripts/validate.js

Lines changed: 59 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,59 @@
1+
'use strict';
2+
3+
var defaultTemp = require('./templates/default'),
4+
commitValidator = require('./templateValidators/default-style-validator'),
5+
lineLimits = require('./utils/line-limits'),
6+
issuesValidator = require('./utils/issue-validator'),
7+
semver = require('semver'),
8+
errMessages = require('./common').errorMessages,
9+
errorFactory = require('./common').errorFactory;
10+
11+
module.exports = (message, options, errors) => {
12+
message = message.trim();
13+
if (message === undefined) {
14+
errors.push(errMessages.UNDEFINED_OF_COMMIT_MSG);
15+
} else if (!(typeof message === 'string' || message instanceof String)) {
16+
errors.push(errMessages.MSG_IS_NOT_A_STRING);
17+
} else if (message.length === 0) {
18+
errors.push(errMessages.MSG_IS_EMPTY);
19+
}
20+
21+
var lines = message.split('\n');
22+
if (semver.valid(lines[0])) {
23+
return [];
24+
}
25+
26+
if(lines[0].toLocaleLowerCase().startsWith("merge")) {
27+
return [];
28+
}
29+
30+
// Get the default template if the style property matches
31+
if (defaultTemp[options.style]) {
32+
options = defaultTemp[options.style];
33+
}
34+
35+
// Get the lineLimits options from the default
36+
// if the template is custom and there is no lineLimits declared.
37+
if (!options.lineLimits) {
38+
options.lineLimits = defaultTemp["default"].lineLimits;
39+
}
40+
41+
if (!options.lineLimits.firstLine) {
42+
options.lineLimits.firstLine = defaultTemp["default"].lineLimits.firstLine;
43+
}
44+
45+
if (!options.lineLimits.otherLine) {
46+
options.lineLimits.otherLine = defaultTemp["default"].lineLimits.otherLine;
47+
}
48+
49+
if(!options.type) {
50+
options.types = defaultTemp["default"].types;
51+
}
52+
53+
lineLimits(lines, options, errors);
54+
commitValidator(lines, options, errors);
55+
issuesValidator(lines, options, errors);
56+
57+
return errors;
58+
}
59+

gulpfile.js

Lines changed: 54 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -151,7 +151,7 @@ const processApp = (projectPath, dest, directoriesToExclude) => {
151151
directories.push(child);
152152
}
153153
});
154-
154+
155155
const jsonSamplesPath = path.join(__dirname, `${projectPath}/assets/samples`);
156156
const sharedJson = JSON.parse(fs.readFileSync(path.join(jsonSamplesPath, "/shared.json")));
157157
const submoduleAppDest = submodule + `/${dest}/`;
@@ -235,6 +235,58 @@ let repositoryfyAngularDemos;
235235
let repositoryfyAngularDemosLob;
236236
let repositoryfyAngularDemosCrm;
237237

238+
const copyGitHooks = async (cb) => {
239+
240+
if (process.env.AZURE_PIPELINES || process.env.TRAVIS || process.env.CI || !fs.existsSync('.git')) {
241+
return;
242+
}
243+
244+
const gitHooksDir = './.git/hooks/';
245+
const defaultCopyHookDir = gitHooksDir + 'scripts/';
246+
const dirs = [
247+
gitHooksDir,
248+
defaultCopyHookDir,
249+
defaultCopyHookDir + 'templates',
250+
defaultCopyHookDir + 'templateValidators',
251+
defaultCopyHookDir + 'utils'
252+
];
253+
254+
dirs.forEach((dir) => {
255+
if (!fs.existsSync(dir)) {
256+
fs.mkdir(dir, (err) => {
257+
if (err) {
258+
throw err;
259+
}
260+
});
261+
}
262+
});
263+
264+
const defaultHookDir = './.hooks/scripts/';
265+
266+
fs.copyFileSync(defaultHookDir + 'templates/default.js',
267+
defaultCopyHookDir + 'templates/default.js');
268+
269+
fs.copyFileSync(defaultHookDir + 'templateValidators/default-style-validator.js',
270+
defaultCopyHookDir + 'templateValidators/default-style-validator.js');
271+
272+
fs.copyFileSync(defaultHookDir + 'utils/issue-validator.js',
273+
defaultCopyHookDir + 'utils/issue-validator.js');
274+
275+
fs.copyFileSync(defaultHookDir + 'utils/line-limits.js',
276+
defaultCopyHookDir + 'utils/line-limits.js');
277+
278+
fs.copyFileSync(defaultHookDir + 'common.js',
279+
defaultCopyHookDir + 'common.js');
280+
281+
fs.copyFileSync(defaultHookDir + 'validate.js',
282+
defaultCopyHookDir + 'validate.js');
283+
284+
fs.copyFileSync('./.hooks/prepare-commit-msg',
285+
'./.git/hooks/prepare-commit-msg');
286+
287+
return await cb();
288+
};
289+
238290
const cleanupAngularDemos = (cb) => {
239291
fsExtra.removeSync(submodule + "/angular-demos");
240292
fsExtra.mkdirSync(submodule + "/angular-demos");
@@ -256,3 +308,4 @@ const cleanupAngularDemosCrm = (cb) => {
256308
exports.repositoryfyAngularDemos = repositoryfyAngularDemos = gulp.series(cleanupAngularDemos, processDemosWithScss);
257309
exports.repositoryfyAngularDemosLob = repositoryfyAngularDemosLob = gulp.series(cleanupAngularDemosLob, processDemosLobWithScss);
258310
exports.repositoryfyAngularDemosCrm = repositoryfyAngularDemosCrm = gulp.series(cleanupAngularDemosCrm, processDemosCrmWithScss);
311+
exports.copyGitHooks = copyGitHooks;

package-lock.json

Lines changed: 1 addition & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

package.json

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -37,7 +37,8 @@
3737
"repositoryfyAngularDemosCrm": "gulp repositoryfyAngularDemosCrm",
3838
"repositoryfyAngularDemosCrm:prod": "gulp repositoryfyAngularDemosCrm --configuration production",
3939
"build:stats": "ng build --stats-json",
40-
"analyze": "webpack-bundle-analyzer dist/app/stats.json"
40+
"analyze": "webpack-bundle-analyzer dist/app/stats.json",
41+
"postinstall": "gulp copyGitHooks"
4142
},
4243
"repository": {
4344
"type": "git",

0 commit comments

Comments
 (0)