@toptal/davinci-ci 1.14.6-alpha-feature-comm-833-reusing-graphql-fragments-bc6ed661.22 → 1.14.6-alpha-feature-comm-833-reusing-graphql-fragments-7830a568.23
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +1000 -0
- package/LICENSE.MD +4 -0
- package/README.md +83 -0
- package/bin/davinci-ci.js +8 -0
- package/package.json +9 -7
- package/src/commands/danger.js +67 -0
- package/src/configs/danger/conventional-commits/dangerfile.js +17 -0
- package/src/configs/danger/conventional-commits/plugins/conventional-commits/index.js +52 -0
- package/src/configs/danger/conventional-commits/plugins/conventional-pr-title/index.js +53 -0
- package/src/configs/danger/conventional-commits/plugins/index.js +7 -0
- package/src/configs/danger/plugins/empty-assignee.js +24 -0
- package/src/configs/danger/toptal/config.js +30 -0
- package/src/configs/danger/toptal/dangerfile.js +17 -0
- package/src/configs/danger/toptal/plugins/index.js +7 -0
- package/src/configs/danger/toptal/plugins/toptal-commits/index.js +73 -0
- package/src/configs/danger/toptal/plugins/toptal-commits/toptal-commits.test.js +128 -0
- package/src/configs/danger/toptal/plugins/toptal-pr-title/index.js +62 -0
- package/src/configs/danger/toptal/plugins/toptal-pr-title/toptal-pr-title.test.js +68 -0
- package/src/configs/docker/Dockerfile +30 -0
- package/src/configs/docker/Dockerfile.gha-deploy +33 -0
- package/src/configs/docker/Dockerfile.release +28 -0
- package/src/configs/docker/Dockerfile.storybook +20 -0
- package/src/configs/docker/env-runtime.entrypoint.sh +47 -0
- package/src/configs/docker/nginx-vhost-storybook.conf +20 -0
- package/src/configs/docker/nginx-vhost.conf +18 -0
- package/src/configs/jobs/build-image/Jenkinsfile +142 -0
- package/src/configs/jobs/build-image/config.xml +119 -0
- package/src/configs/jobs/build-release-image/Jenkinsfile +154 -0
- package/src/configs/jobs/build-release-image/config.xml +102 -0
- package/src/configs/jobs/consumer-contracts-verify/Jenkinsfile +117 -0
- package/src/configs/jobs/consumer-contracts-verify/config.xml +114 -0
- package/src/configs/jobs/deploy/config.xml +107 -0
- package/src/configs/jobs/deploy-helm-run/Jenkinsfile +159 -0
- package/src/configs/jobs/deploy-helm-run/config.xml +107 -0
- package/src/configs/jobs/deploy-helm-run-trigger/Jenkinsfile +200 -0
- package/src/configs/jobs/master-main/Jenkinsfile +460 -0
- package/src/configs/jobs/master-main/config.xml +119 -0
- package/src/configs/jobs/pr-tests/Jenkinsfile +407 -0
- package/src/configs/jobs/pr-tests/config.xml +134 -0
- package/src/configs/jobs/publish-alpha-package/Jenkinsfile +189 -0
- package/src/configs/jobs/publish-alpha-package/config.xml +136 -0
- package/src/configs/jobs/temploy-helm-run/Jenkinsfile +141 -0
- package/src/configs/jobs/temploy-helm-run/config.xml +97 -0
- package/src/index.js +17 -0
package/LICENSE.MD
ADDED
package/README.md
ADDED
|
@@ -0,0 +1,83 @@
|
|
|
1
|
+
# `@toptal/davinci-ci`
|
|
2
|
+
|
|
3
|
+
Holds Jenkins jobs definitions and Docker files supporting frontend applications.
|
|
4
|
+
|
|
5
|
+
## Demo
|
|
6
|
+
|
|
7
|
+
Watch [this video](https://drive.google.com/file/d/1C5ApkATYCXZ8kTn2LeG5FkM4AveLxAd8/view?usp=sharing) for a demo of how to set up a davinci-ci Jenkins jobs for a project or check the [How to start a new application](https://toptal-core.atlassian.net/wiki/spaces/ENG/pages/1120831396/How+to+start+a+new+application) guide.
|
|
8
|
+
|
|
9
|
+
## Installation
|
|
10
|
+
|
|
11
|
+
Use it by installing `yarn add @toptal/davinci-ci` in your project.
|
|
12
|
+
|
|
13
|
+
## Usage
|
|
14
|
+
|
|
15
|
+
### Commands
|
|
16
|
+
|
|
17
|
+
- `davinci-ci danger` - checks if the PR title and and commit messages comply with [Toptal's standards](https://toptal-core.atlassian.net/wiki/spaces/ENG/pages/210665897/Commit+Message+Quality)
|
|
18
|
+
|
|
19
|
+
- `davinci-ci danger --conventionalCommits` - checks if the PR title and and commit messages comply with [ConventionalCommits](https://www.conventionalcommits.org/en/v1.0.0/)
|
|
20
|
+
|
|
21
|
+
To check commit message quality locally just add `--local` argument
|
|
22
|
+
|
|
23
|
+
`davinci-ci danger --local`
|
|
24
|
+
|
|
25
|
+
Using husky, you can check the commit message before pushing files changes.
|
|
26
|
+
|
|
27
|
+
```js
|
|
28
|
+
"husky": {
|
|
29
|
+
"hooks": {
|
|
30
|
+
"pre-push": "yarn davinci-ci danger --local"
|
|
31
|
+
}
|
|
32
|
+
},
|
|
33
|
+
```
|
|
34
|
+
|
|
35
|
+
### Configuration
|
|
36
|
+
|
|
37
|
+
You can specify which scenarios you want to be performed both for PR and master branch updates by creating a davinci.yaml file in the root of your project with the following structure.
|
|
38
|
+
|
|
39
|
+
(by default each config option is set to `false`)
|
|
40
|
+
|
|
41
|
+
```yaml
|
|
42
|
+
pr:
|
|
43
|
+
contract_testing: false
|
|
44
|
+
|
|
45
|
+
master:
|
|
46
|
+
publish_as_package: false
|
|
47
|
+
deploy_staging: false
|
|
48
|
+
deploy_storybook_staging: false
|
|
49
|
+
deploy: false
|
|
50
|
+
conventional_commits: false # danger check will require each commit to follow convential commits
|
|
51
|
+
require_assignee: false # danger check will require each PR to have assignee
|
|
52
|
+
require_assignee_whitelist: ['dependabot-preview[bot]', 'dependabot[bot]']
|
|
53
|
+
|
|
54
|
+
# Customize Slack channel where the notifications will get posted
|
|
55
|
+
slack_channel: 'custom-slack-channel'
|
|
56
|
+
is_progressive_web_app: true
|
|
57
|
+
```
|
|
58
|
+
|
|
59
|
+
### Commands you can use in GitHub PRs
|
|
60
|
+
|
|
61
|
+
`@toptal-bot run tests` - to re-run `pr-tests` job
|
|
62
|
+
|
|
63
|
+
`@toptal-bot run package:alpha-release` - to run `publish-alpha-package` job, which is publishing alpha release of the package to npm (temploy for npm package)
|
|
64
|
+
|
|
65
|
+
### How to setup cypress in CI
|
|
66
|
+
|
|
67
|
+
In order to enable cypress tests on CI jobs (both pr-tests and master-main) enable it on your project `davinci.yaml` file, like in the example below:
|
|
68
|
+
|
|
69
|
+
```yaml
|
|
70
|
+
pr:
|
|
71
|
+
cypress: true
|
|
72
|
+
master:
|
|
73
|
+
cypress: true
|
|
74
|
+
```
|
|
75
|
+
|
|
76
|
+
If you have it set as false or if this config is not present in your project `davinci.yaml` file, cypress step will be skipped.
|
|
77
|
+
|
|
78
|
+
### IDE Tooling
|
|
79
|
+
|
|
80
|
+
This package can be used directly in your IDE through these extensions
|
|
81
|
+
|
|
82
|
+
- [IntelliJ Suite (WebStorm, RubyMine, IDEA)](https://github.com/zaguiini/intellij-davinci-plugin)
|
|
83
|
+
- [VSCode](https://github.com/borisyordanov/vscode-supercharge-react)
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@toptal/davinci-ci",
|
|
3
|
-
"version": "1.14.6-alpha-feature-comm-833-reusing-graphql-fragments-
|
|
3
|
+
"version": "1.14.6-alpha-feature-comm-833-reusing-graphql-fragments-7830a568.23+7830a568",
|
|
4
4
|
"description": "Continuos integrations tools for frontend projects",
|
|
5
5
|
"publishConfig": {
|
|
6
6
|
"access": "public"
|
|
@@ -16,25 +16,27 @@
|
|
|
16
16
|
"bin": {
|
|
17
17
|
"davinci-ci": "./bin/davinci-ci.js"
|
|
18
18
|
},
|
|
19
|
-
"main": "
|
|
19
|
+
"main": "src/index.js",
|
|
20
|
+
"files": [
|
|
21
|
+
"src",
|
|
22
|
+
"CHANGELOG.md"
|
|
23
|
+
],
|
|
20
24
|
"repository": {
|
|
21
25
|
"type": "git",
|
|
22
26
|
"url": "git+https://github.com/toptal/davinci.git"
|
|
23
27
|
},
|
|
24
28
|
"scripts": {
|
|
25
|
-
"build:package": "../../bin/build-package.js",
|
|
26
|
-
"prepublishOnly": "../../bin/prepublish.js",
|
|
27
29
|
"test": "echo \"Error: run tests from root\" && exit 1"
|
|
28
30
|
},
|
|
29
31
|
"bugs": {
|
|
30
32
|
"url": "https://github.com/toptal/davinci/issues"
|
|
31
33
|
},
|
|
32
34
|
"dependencies": {
|
|
33
|
-
"@commitlint/cli": "^17.0
|
|
35
|
+
"@commitlint/cli": "^17.4.0",
|
|
34
36
|
"@commitlint/config-conventional": "^17.1.0",
|
|
35
|
-
"@toptal/davinci-cli-shared": "1.5.2-alpha-feature-comm-833-reusing-graphql-fragments-
|
|
37
|
+
"@toptal/davinci-cli-shared": "1.5.2-alpha-feature-comm-833-reusing-graphql-fragments-7830a568.23+7830a568",
|
|
36
38
|
"danger": "^11.0.7",
|
|
37
39
|
"markdown-table": "^2.0.0"
|
|
38
40
|
},
|
|
39
|
-
"gitHead": "
|
|
41
|
+
"gitHead": "7830a568c3d58e9114ca7d7d4c164bb7a60f3596"
|
|
40
42
|
}
|
|
@@ -0,0 +1,67 @@
|
|
|
1
|
+
const {
|
|
2
|
+
davinciProjectConfig,
|
|
3
|
+
runSync,
|
|
4
|
+
print,
|
|
5
|
+
convertToCLIParameters,
|
|
6
|
+
files,
|
|
7
|
+
} = require('@toptal/davinci-cli-shared')
|
|
8
|
+
|
|
9
|
+
const commandOptions = [
|
|
10
|
+
{
|
|
11
|
+
label: 'check only, without fixing the code',
|
|
12
|
+
name: '--check',
|
|
13
|
+
},
|
|
14
|
+
{
|
|
15
|
+
label: 'checks commit message quality locally',
|
|
16
|
+
name: '--local',
|
|
17
|
+
},
|
|
18
|
+
{
|
|
19
|
+
label: 'specify custom danger file',
|
|
20
|
+
name: '--dangerfile <dangerfile>',
|
|
21
|
+
},
|
|
22
|
+
]
|
|
23
|
+
|
|
24
|
+
const dangerCommand = ({ options }) => {
|
|
25
|
+
print.green('Running danger check...')
|
|
26
|
+
|
|
27
|
+
const { local, dangerfile, ...rest } = options
|
|
28
|
+
|
|
29
|
+
const conventionalCommitsDangerfilePath = files.getPackageFilePath(
|
|
30
|
+
'@toptal/davinci-ci',
|
|
31
|
+
'src/configs/danger/conventional-commits/dangerfile.js'
|
|
32
|
+
)
|
|
33
|
+
const toptalCommitsDangerfilePath = files.getPackageFilePath(
|
|
34
|
+
'@toptal/davinci-ci',
|
|
35
|
+
'src/configs/danger/toptal/dangerfile.js'
|
|
36
|
+
)
|
|
37
|
+
|
|
38
|
+
let dangerfilePath = dangerfile
|
|
39
|
+
|
|
40
|
+
if (!dangerfilePath) {
|
|
41
|
+
dangerfilePath = davinciProjectConfig.master.conventionalCommits
|
|
42
|
+
? conventionalCommitsDangerfilePath
|
|
43
|
+
: toptalCommitsDangerfilePath
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
runSync(
|
|
47
|
+
'yarn',
|
|
48
|
+
[
|
|
49
|
+
'danger',
|
|
50
|
+
local ? 'local' : 'ci',
|
|
51
|
+
local ? '--failOnErrors' : undefined,
|
|
52
|
+
'--dangerfile',
|
|
53
|
+
`${dangerfilePath}`,
|
|
54
|
+
...convertToCLIParameters(rest),
|
|
55
|
+
].filter(Boolean)
|
|
56
|
+
)
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
const dangerCommandCreator = {
|
|
60
|
+
action: dangerCommand,
|
|
61
|
+
allowUnknownOptions: true,
|
|
62
|
+
command: 'danger',
|
|
63
|
+
description: 'Run danger check',
|
|
64
|
+
options: commandOptions,
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
module.exports = dangerCommandCreator
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
const { schedule } = require('danger')
|
|
2
|
+
const { davinciProjectConfig } = require('@toptal/davinci-cli-shared')
|
|
3
|
+
|
|
4
|
+
const { conventionalCommits } = require('./plugins/conventional-commits')
|
|
5
|
+
const { conventionalPRTitle } = require('./plugins/conventional-pr-title')
|
|
6
|
+
const { checkAssigneeExist } = require('../plugins/empty-assignee')
|
|
7
|
+
|
|
8
|
+
schedule(conventionalCommits)
|
|
9
|
+
schedule(conventionalPRTitle)
|
|
10
|
+
|
|
11
|
+
const {
|
|
12
|
+
master: { requireAssignee },
|
|
13
|
+
} = davinciProjectConfig
|
|
14
|
+
|
|
15
|
+
if (requireAssignee) {
|
|
16
|
+
schedule(checkAssigneeExist)
|
|
17
|
+
}
|
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
/* globals danger, fail */
|
|
2
|
+
/* eslint-disable import/no-extraneous-dependencies */
|
|
3
|
+
const { default: load } = require('@commitlint/load')
|
|
4
|
+
const { default: lint } = require('@commitlint/lint')
|
|
5
|
+
/* eslint-enable */
|
|
6
|
+
const table = require('markdown-table')
|
|
7
|
+
|
|
8
|
+
const { WHITELISTED_USERS } = require('../../../toptal/config')
|
|
9
|
+
|
|
10
|
+
const conventionalCommits = async () => {
|
|
11
|
+
if (
|
|
12
|
+
danger.github &&
|
|
13
|
+
WHITELISTED_USERS.includes(danger.github.pr.user.login)
|
|
14
|
+
) {
|
|
15
|
+
return
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
const { commits } = danger.git
|
|
19
|
+
const { rules, parserPreset } = await load({
|
|
20
|
+
extends: ['@commitlint/config-conventional'],
|
|
21
|
+
})
|
|
22
|
+
|
|
23
|
+
commits.forEach(async commit => {
|
|
24
|
+
const result = await lint(
|
|
25
|
+
commit.message,
|
|
26
|
+
rules,
|
|
27
|
+
parserPreset ? { parserOpts: parserPreset.parserOpts } : {}
|
|
28
|
+
)
|
|
29
|
+
|
|
30
|
+
if (!result.valid) {
|
|
31
|
+
if (result.errors.length > 0) {
|
|
32
|
+
let message = `The commit - ${commit.sha.trim()} doesn't conform the conventional commit guidelines. \n\n**Errors**:\n\n`
|
|
33
|
+
|
|
34
|
+
message += table([
|
|
35
|
+
['Message'],
|
|
36
|
+
...result.errors.map(error => [error.message]),
|
|
37
|
+
])
|
|
38
|
+
|
|
39
|
+
message +=
|
|
40
|
+
'\n\n💡 For a guidance on how to fix this problem please refer to [https://www.conventionalcommits.org](https://www.conventionalcommits.org)'
|
|
41
|
+
message +=
|
|
42
|
+
'\n\n💡 Or you can check our [documentation](https://github.com/toptal/picasso/blob/master/docs/contribution/github-workflow.md#general-commit-message-pattern)'
|
|
43
|
+
|
|
44
|
+
fail(message)
|
|
45
|
+
}
|
|
46
|
+
}
|
|
47
|
+
})
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
module.exports = {
|
|
51
|
+
conventionalCommits,
|
|
52
|
+
}
|
|
@@ -0,0 +1,53 @@
|
|
|
1
|
+
/// <reference types="danger" />
|
|
2
|
+
/* globals danger, fail */
|
|
3
|
+
/* eslint-disable import/no-extraneous-dependencies */
|
|
4
|
+
const { default: load } = require('@commitlint/load')
|
|
5
|
+
const { default: lint } = require('@commitlint/lint')
|
|
6
|
+
/* eslint-enable */
|
|
7
|
+
const table = require('markdown-table')
|
|
8
|
+
|
|
9
|
+
const { WHITELISTED_USERS } = require('../../../toptal/config')
|
|
10
|
+
|
|
11
|
+
const conventionalPRTitle = async () => {
|
|
12
|
+
if (!danger.github) {
|
|
13
|
+
return
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
if (WHITELISTED_USERS.includes(danger.github.pr.user.login)) {
|
|
17
|
+
return
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
const { rules, parserPreset } = await load({
|
|
21
|
+
extends: ['@commitlint/config-conventional'],
|
|
22
|
+
})
|
|
23
|
+
const { title } = danger.github.pr
|
|
24
|
+
|
|
25
|
+
const result = await lint(
|
|
26
|
+
title,
|
|
27
|
+
rules,
|
|
28
|
+
parserPreset ? { parserOpts: parserPreset.parserOpts } : {}
|
|
29
|
+
)
|
|
30
|
+
|
|
31
|
+
if (!result.valid) {
|
|
32
|
+
if (result.errors.length > 0) {
|
|
33
|
+
let message =
|
|
34
|
+
"PR title doesn't conform the conventional commit guidelines. \n\n**Errors**:\n\n"
|
|
35
|
+
|
|
36
|
+
message += table([
|
|
37
|
+
['Message'],
|
|
38
|
+
...result.errors.map(error => [error.message]),
|
|
39
|
+
])
|
|
40
|
+
|
|
41
|
+
message +=
|
|
42
|
+
'\n\n💡 For a guidance on how to fix this problem please refer to [https://www.conventionalcommits.org](https://www.conventionalcommits.org)'
|
|
43
|
+
message +=
|
|
44
|
+
'\n\n💡 Or you can check our [documentation](https://github.com/toptal/picasso/blob/master/docs/contribution/github-workflow.md#general-commit-message-pattern)'
|
|
45
|
+
|
|
46
|
+
fail(message)
|
|
47
|
+
}
|
|
48
|
+
}
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
module.exports = {
|
|
52
|
+
conventionalPRTitle,
|
|
53
|
+
}
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
/// <reference types="danger" />
|
|
2
|
+
/* globals danger, fail */
|
|
3
|
+
const { davinciProjectConfig } = require('@toptal/davinci-cli-shared')
|
|
4
|
+
|
|
5
|
+
const {
|
|
6
|
+
master: { requireAssigneeWhiteList = [] },
|
|
7
|
+
} = davinciProjectConfig
|
|
8
|
+
|
|
9
|
+
const WHITELISTED_USERS = requireAssigneeWhiteList
|
|
10
|
+
const checkAssigneeExist = () => {
|
|
11
|
+
const isLocalRun = !danger.github
|
|
12
|
+
|
|
13
|
+
if (isLocalRun || WHITELISTED_USERS.includes(danger.github.pr.user.login)) {
|
|
14
|
+
return
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
if (!danger.github.pr.assignee || danger.github.pr.assignees.length === 0) {
|
|
18
|
+
fail('Please assign someone to this PR before merging.')
|
|
19
|
+
}
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
module.exports = {
|
|
23
|
+
checkAssigneeExist,
|
|
24
|
+
}
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
const WHITELISTED_USERS = [
|
|
2
|
+
'dependabot-preview[bot]',
|
|
3
|
+
'dependabot[bot]',
|
|
4
|
+
'toptal-devbot',
|
|
5
|
+
]
|
|
6
|
+
const VALID_PR_CODE_REGEX = /\[[A-Z]{1,5}-.*]\s/
|
|
7
|
+
// Valid commit titles:
|
|
8
|
+
// "Regular commit message"
|
|
9
|
+
// "Commit with sentence. But does not end with a full-stop"
|
|
10
|
+
// "[FOO-1234] Commit with prefix"
|
|
11
|
+
// "[FOO] Prefix numbers can be omitted"
|
|
12
|
+
// "[FOO][BAR] Prefixes can couple"
|
|
13
|
+
const VALID_COMMIT_TITLE_REGEX = /^((\[[A-Z]+(-\d+)?])+ )?[A-Z]\w[^\n]*[^.]$/
|
|
14
|
+
const MAX_COMMIT_LINE_LENGTH = 79
|
|
15
|
+
const ENGINEERING_DOCS_LINK =
|
|
16
|
+
'https://toptal-core.atlassian.net/wiki/spaces/ENG/pages/210665897/Commit+Message+Quality'
|
|
17
|
+
|
|
18
|
+
const DEFAULT_PR_TITLE_ERROR_MESSAGE = `The pull request title doesn't conform to Toptal's engineering practices. For guidance on how to fix this problem please refer [here](${ENGINEERING_DOCS_LINK})`
|
|
19
|
+
const MISSING_TICKET_CODE_ERROR_MESSAGE =
|
|
20
|
+
"The pull request title is missing a Jira issue code. Correct format '[ASD-123] Add a cool feature'. If you're working without a Jira issue then add a 'no-jira' label to your pull request."
|
|
21
|
+
|
|
22
|
+
module.exports = {
|
|
23
|
+
DEFAULT_PR_TITLE_ERROR_MESSAGE,
|
|
24
|
+
ENGINEERING_DOCS_LINK,
|
|
25
|
+
MAX_COMMIT_LINE_LENGTH,
|
|
26
|
+
MISSING_TICKET_CODE_ERROR_MESSAGE,
|
|
27
|
+
VALID_COMMIT_TITLE_REGEX,
|
|
28
|
+
VALID_PR_CODE_REGEX,
|
|
29
|
+
WHITELISTED_USERS,
|
|
30
|
+
}
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
const { schedule } = require('danger')
|
|
2
|
+
const { davinciProjectConfig } = require('@toptal/davinci-cli-shared')
|
|
3
|
+
|
|
4
|
+
const { toptalCommits } = require('./plugins/toptal-commits')
|
|
5
|
+
const { toptalPRTitle } = require('./plugins/toptal-pr-title')
|
|
6
|
+
const { checkAssigneeExist } = require('../plugins/empty-assignee')
|
|
7
|
+
|
|
8
|
+
schedule(toptalCommits)
|
|
9
|
+
schedule(toptalPRTitle)
|
|
10
|
+
|
|
11
|
+
const {
|
|
12
|
+
master: { requireAssignee },
|
|
13
|
+
} = davinciProjectConfig
|
|
14
|
+
|
|
15
|
+
if (requireAssignee) {
|
|
16
|
+
schedule(checkAssigneeExist)
|
|
17
|
+
}
|
|
@@ -0,0 +1,73 @@
|
|
|
1
|
+
/* globals danger, fail */
|
|
2
|
+
const {
|
|
3
|
+
MAX_COMMIT_LINE_LENGTH,
|
|
4
|
+
VALID_COMMIT_TITLE_REGEX,
|
|
5
|
+
WHITELISTED_USERS,
|
|
6
|
+
} = require('../../config')
|
|
7
|
+
|
|
8
|
+
const validateCommitMessage = message => {
|
|
9
|
+
const errors = []
|
|
10
|
+
const [title = '', body = ''] = message.split(/\n\n/)
|
|
11
|
+
const bodyLines = body.split(/\n/)
|
|
12
|
+
const isTitleFormatValid = VALID_COMMIT_TITLE_REGEX.test(title)
|
|
13
|
+
const isTitleLengthValid = title.length <= MAX_COMMIT_LINE_LENGTH
|
|
14
|
+
const isBodyLinesLengthValid = bodyLines.every(
|
|
15
|
+
line => line.length <= MAX_COMMIT_LINE_LENGTH
|
|
16
|
+
)
|
|
17
|
+
|
|
18
|
+
if (!isTitleFormatValid) {
|
|
19
|
+
errors.push(`- format of commit title is not correct ([read more](https://toptal-core.atlassian.net/wiki/spaces/ENG/pages/210665897/Commit+Message+Quality)):
|
|
20
|
+
- Title should start with capital letter
|
|
21
|
+
- Title should not end with a full-stop (i.e .)\n
|
|
22
|
+
Example of a valid commit title:
|
|
23
|
+
- Regular commit message
|
|
24
|
+
- [FOO-1234] Commit with prefix
|
|
25
|
+
- [FOO][BAR] Coupled prefixes`)
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
if (!isTitleLengthValid) {
|
|
29
|
+
errors.push(
|
|
30
|
+
`- title is too long (max ${MAX_COMMIT_LINE_LENGTH} characters)`
|
|
31
|
+
)
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
if (!isBodyLinesLengthValid) {
|
|
35
|
+
errors.push(
|
|
36
|
+
`- body lines must not exceed ${MAX_COMMIT_LINE_LENGTH} characters`
|
|
37
|
+
)
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
return errors
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
const toptalCommits = () => {
|
|
44
|
+
if (
|
|
45
|
+
danger.github &&
|
|
46
|
+
WHITELISTED_USERS.includes(danger.github.pr.user.login)
|
|
47
|
+
) {
|
|
48
|
+
return
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
const { commits } = danger.git
|
|
52
|
+
|
|
53
|
+
commits.forEach(commit => {
|
|
54
|
+
if (commit.author && WHITELISTED_USERS.includes(commit.author.name)) {
|
|
55
|
+
return
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
const validationErrors = validateCommitMessage(commit.message)
|
|
59
|
+
|
|
60
|
+
if (validationErrors.length) {
|
|
61
|
+
fail(
|
|
62
|
+
[
|
|
63
|
+
`Commit ${commit.sha.trim()} does not meet requirements of [Commit Message Quality](https://toptal-core.atlassian.net/wiki/spaces/ENG/pages/210665897/Commit+Message+Quality):`,
|
|
64
|
+
...validationErrors,
|
|
65
|
+
].join('\n')
|
|
66
|
+
)
|
|
67
|
+
}
|
|
68
|
+
})
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
module.exports = {
|
|
72
|
+
toptalCommits,
|
|
73
|
+
}
|
|
@@ -0,0 +1,128 @@
|
|
|
1
|
+
const { toptalCommits } = require('.')
|
|
2
|
+
|
|
3
|
+
const prepareCommitsWithMessages = (messages = []) => ({
|
|
4
|
+
commits: messages.map(message => ({
|
|
5
|
+
message,
|
|
6
|
+
sha: '123',
|
|
7
|
+
})),
|
|
8
|
+
})
|
|
9
|
+
|
|
10
|
+
describe('commit danger plugin', () => {
|
|
11
|
+
beforeEach(() => {
|
|
12
|
+
global.fail = jest.fn()
|
|
13
|
+
global.danger = {
|
|
14
|
+
github: {
|
|
15
|
+
pr: {
|
|
16
|
+
user: {
|
|
17
|
+
login: 'user',
|
|
18
|
+
},
|
|
19
|
+
},
|
|
20
|
+
},
|
|
21
|
+
}
|
|
22
|
+
})
|
|
23
|
+
|
|
24
|
+
afterEach(() => {
|
|
25
|
+
global.fail = undefined
|
|
26
|
+
global.danger = undefined
|
|
27
|
+
})
|
|
28
|
+
|
|
29
|
+
it('commit with title only', () => {
|
|
30
|
+
global.danger.git = prepareCommitsWithMessages(['Hello world'])
|
|
31
|
+
toptalCommits()
|
|
32
|
+
|
|
33
|
+
expect(global.fail).toHaveBeenCalledTimes(0)
|
|
34
|
+
})
|
|
35
|
+
|
|
36
|
+
it('commit with title and body', () => {
|
|
37
|
+
global.danger.git = prepareCommitsWithMessages([
|
|
38
|
+
'Hello world\n\nSome commit body here.',
|
|
39
|
+
])
|
|
40
|
+
toptalCommits()
|
|
41
|
+
|
|
42
|
+
expect(global.fail).toHaveBeenCalledTimes(0)
|
|
43
|
+
})
|
|
44
|
+
|
|
45
|
+
it('commit with multi-line body', () => {
|
|
46
|
+
global.danger.git = prepareCommitsWithMessages([
|
|
47
|
+
'Hello world\n\nFirst line of commit body\nSecond line of commit body',
|
|
48
|
+
])
|
|
49
|
+
toptalCommits()
|
|
50
|
+
|
|
51
|
+
expect(global.fail).toHaveBeenCalledTimes(0)
|
|
52
|
+
})
|
|
53
|
+
|
|
54
|
+
it('commit with title longer than allowed', () => {
|
|
55
|
+
global.danger.git = prepareCommitsWithMessages([
|
|
56
|
+
'This commit message is a teeny tiny bit longer than allowed length of 79 characters',
|
|
57
|
+
])
|
|
58
|
+
toptalCommits()
|
|
59
|
+
|
|
60
|
+
expect(global.fail).toHaveBeenCalledTimes(1)
|
|
61
|
+
})
|
|
62
|
+
|
|
63
|
+
it('commit with body lines longer than allowed', () => {
|
|
64
|
+
global.danger.git = prepareCommitsWithMessages([
|
|
65
|
+
'Short commit title\n\nBut this body line commit message is a bit longer than allowed length of 79 characters\nAnd this one is fine again.',
|
|
66
|
+
])
|
|
67
|
+
toptalCommits()
|
|
68
|
+
|
|
69
|
+
expect(global.fail).toHaveBeenCalledTimes(1)
|
|
70
|
+
})
|
|
71
|
+
|
|
72
|
+
it('commit message not starting with an uppercase letter', () => {
|
|
73
|
+
global.danger.git = prepareCommitsWithMessages([
|
|
74
|
+
'23432 starts with a number',
|
|
75
|
+
'starts with a lowercase letter',
|
|
76
|
+
'* starts with a symbol',
|
|
77
|
+
' starts with a space',
|
|
78
|
+
])
|
|
79
|
+
toptalCommits()
|
|
80
|
+
|
|
81
|
+
expect(global.fail).toHaveBeenCalledTimes(4)
|
|
82
|
+
})
|
|
83
|
+
|
|
84
|
+
it('commit message ending with a period in title', () => {
|
|
85
|
+
global.danger.git = prepareCommitsWithMessages([
|
|
86
|
+
'Commit title ending with period.',
|
|
87
|
+
])
|
|
88
|
+
toptalCommits()
|
|
89
|
+
|
|
90
|
+
expect(global.fail).toHaveBeenCalledTimes(1)
|
|
91
|
+
})
|
|
92
|
+
|
|
93
|
+
it('commit without a blank line between title and body', () => {
|
|
94
|
+
global.danger.git = prepareCommitsWithMessages([
|
|
95
|
+
'Commit title\nBody without a blank line after title',
|
|
96
|
+
])
|
|
97
|
+
toptalCommits()
|
|
98
|
+
|
|
99
|
+
expect(global.fail).toHaveBeenCalledTimes(1)
|
|
100
|
+
})
|
|
101
|
+
|
|
102
|
+
it('valid commit titles', () => {
|
|
103
|
+
global.danger.git = prepareCommitsWithMessages([
|
|
104
|
+
'Regular commit title',
|
|
105
|
+
'Commit with sentence. But does not end with a full-stop',
|
|
106
|
+
'[FOO-1234] Commit with prefix',
|
|
107
|
+
'[FOO] Prefix numbers can be omitted',
|
|
108
|
+
'[FOO][BAR] Prefixes can couple',
|
|
109
|
+
'[FOO-1234][BAR] Mixed prefixes',
|
|
110
|
+
'[FOO-1234][BAR-123][BAZ] Everything. Together',
|
|
111
|
+
])
|
|
112
|
+
toptalCommits()
|
|
113
|
+
|
|
114
|
+
expect(global.fail).toHaveBeenCalledTimes(0)
|
|
115
|
+
})
|
|
116
|
+
|
|
117
|
+
it('invalid commit title prefixes', () => {
|
|
118
|
+
global.danger.git = prepareCommitsWithMessages([
|
|
119
|
+
'[FOO-1234] commit with lowercase',
|
|
120
|
+
'[FOO-BAR] Prefix with letter after dash',
|
|
121
|
+
'[FOO] [BAR] Prefixes with space between',
|
|
122
|
+
'[FOO]Prefix without space',
|
|
123
|
+
])
|
|
124
|
+
toptalCommits()
|
|
125
|
+
|
|
126
|
+
expect(global.fail).toHaveBeenCalledTimes(4)
|
|
127
|
+
})
|
|
128
|
+
})
|
|
@@ -0,0 +1,62 @@
|
|
|
1
|
+
/* globals danger, fail */
|
|
2
|
+
const {
|
|
3
|
+
VALID_COMMIT_TITLE_REGEX,
|
|
4
|
+
VALID_PR_CODE_REGEX,
|
|
5
|
+
WHITELISTED_USERS,
|
|
6
|
+
DEFAULT_PR_TITLE_ERROR_MESSAGE,
|
|
7
|
+
MISSING_TICKET_CODE_ERROR_MESSAGE,
|
|
8
|
+
} = require('../../config')
|
|
9
|
+
|
|
10
|
+
const getTicketCode = text => {
|
|
11
|
+
const ticketCode = text.match(VALID_PR_CODE_REGEX)
|
|
12
|
+
|
|
13
|
+
if (!ticketCode) {
|
|
14
|
+
return
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
return ticketCode[0]
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
const showError = (error = DEFAULT_PR_TITLE_ERROR_MESSAGE) => {
|
|
21
|
+
fail(error)
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
const toptalPRTitle = () => {
|
|
25
|
+
if (!danger.github) {
|
|
26
|
+
return
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
if (WHITELISTED_USERS.includes(danger.github.pr.user.login)) {
|
|
30
|
+
return
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
const { title } = danger.github.pr
|
|
34
|
+
const labelNames = danger.github.issue.labels.map(({ name }) => name)
|
|
35
|
+
|
|
36
|
+
const ticketCode = getTicketCode(title)
|
|
37
|
+
|
|
38
|
+
if (!ticketCode && !labelNames.includes('no-jira')) {
|
|
39
|
+
showError(MISSING_TICKET_CODE_ERROR_MESSAGE)
|
|
40
|
+
|
|
41
|
+
return
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
const splitTitle = title.split(ticketCode)
|
|
45
|
+
|
|
46
|
+
if (!splitTitle.length) {
|
|
47
|
+
showError()
|
|
48
|
+
|
|
49
|
+
return
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
const titleText = splitTitle[splitTitle.length - 1]
|
|
53
|
+
const isTitleFormatValid = VALID_COMMIT_TITLE_REGEX.test(titleText)
|
|
54
|
+
|
|
55
|
+
if (!isTitleFormatValid) {
|
|
56
|
+
showError()
|
|
57
|
+
}
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
module.exports = {
|
|
61
|
+
toptalPRTitle,
|
|
62
|
+
}
|