Skip to content
This repository has been archived by the owner on Nov 3, 2022. It is now read-only.

Commit

Permalink
feat: support config from cli args and config file (#61)
Browse files Browse the repository at this point in the history
* feat: support config from cli args and config file

* feat: update docs

* feat: update config names + cli args

* cr: update docs

* cr: update README formatting 

update README formatting

* fix: Make the config file keys camelCase, env UPPER_CASE, cli kebab-case

Co-authored-by: Jared Rand <jared.rand@tophatmonocle.com>
Co-authored-by: Noah <noah.negin-ulster@tophatmonocle.com>
  • Loading branch information
3 people committed Mar 26, 2021
1 parent 2f27d7a commit 39a6549
Show file tree
Hide file tree
Showing 11 changed files with 304 additions and 22 deletions.
2 changes: 1 addition & 1 deletion .husky/.gitignore
Original file line number Diff line number Diff line change
@@ -1 +1 @@
_
_
40 changes: 39 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -31,19 +31,57 @@ Or just run it directly in CI via `npx commit-watch` or `yarn dlx commit-watch`.

Before running commit-watch, you need to ensure the relevant environment variables are set. For a full list of required and optional environment variables, see the section below.

### Environment Variables:
### Configuration Variables:

| Name | Value | Description |
|------------------------------|------------------------------------------------------|------------------------------------------------------------------------------------------------------|
| COMMITWATCH_GITHUB_TOKEN | Required | Personal access token with write access to GitHub status checks, and read access to your repository. |
| CI_REPO_OWNER | Required | The "owner" from https://github.com/\<owner\>/\<name\>. |
| CI_REPO_NAME | Required | The "name" from https://github.com/\<owner\>/\<name\>. That is, your repository name. |
| GIT_URL | Not Required | The full Git URL from git@github.com:\<owner\>/\<name\>.git. That is, the URL you'd use to clone your repository. |
| CI_COMMIT_SHA | Required | The commit sha to run the linter against. |
| CI_BASE_BRANCH | Defaults to `origin/master`. | The base branch to compare the commit sha against. |
| COMMIT_WATCH_OUTPUT_DIR | Defaults to `./artifacts/test_results/commitwatch/`. | Directory to write the junit report to. |
| COMMIT_WATCH_OUTPUT_FILENAME | Defaults to `commitwatch.junit.xml`. | The name of the junit report. |
| VERBOSE | Defaults to `0`. | Whether to enable verbose mode. |

These variables can be set in the folowing ways:
1. By having environment variables with matching names.
2. By specifying a .js config file containing the variables when running the command.
3. By specifying the variables in the command line arguments when running the command.

To use specify a config file use the --config-file cli argument to pass in the relative path of the file.

ex. commit-watch --config-file commit-watch.config.js

```javascript
//commit-watch.config.js


module.exports = {
ciRepoOwner: 'Hans Moleman',
ciRepoName: 'manahattan-project',
ciBaseBranch: 'develop',
outputDir: 'junit_reports',
outputFilename: 'myreport.juint.xml',
gitUrl: 'git@github.com:EXAMPLE_ORG/EXAMPLE_REPO.git',
verbose: true,
}
```



To pass in any specific config parameter as a command line argument use the parameter name as a cli argument.

ex. commit-watch --github-token password123

If config parameters are present from multiple sources they are used in the following order:

1. Configs passed in as cli args
2. Configs from a config file
3. Environment variable configs


## Contributors ✨

[See Contributing Guide](./CONTRIBUTING.md) to get started contributing.
Expand Down
3 changes: 2 additions & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -61,7 +61,8 @@
"axios": "^0.21.1",
"chalk": "^4.1.0",
"git-raw-commits": "^2.0.10",
"junit-report-builder": "^2.1.0"
"junit-report-builder": "^2.1.0",
"minimist": "^1.2.5"
},
"engines": {
"node": ">=12.0.0"
Expand Down
24 changes: 12 additions & 12 deletions src/getCIVars.js
Original file line number Diff line number Diff line change
@@ -1,23 +1,23 @@
const getRepo = env => {
if (env.CI_REPO_NAME) {
return env.CI_REPO_NAME
const getRepo = configs => {
if (configs.CI_REPO_NAME) {
return configs.CI_REPO_NAME
}

const GET_REPO_FROM_STRING = /github\.com[/:](.*).git/
if (env.GIT_URL) {
return GET_REPO_FROM_STRING.exec(env.GIT_URL)[1]
if (configs.GIT_URL) {
return GET_REPO_FROM_STRING.exec(configs.GIT_URL)[1]
}
return null
}

const getCIVars = env => {
const commitSha = env.CI_COMMIT_SHA || env.GIT_COMMIT
const baseBranch = env.CI_BASE_BRANCH || 'origin/master'
const githubAccessToken = env.COMMITWATCH_GITHUB_TOKEN
const getCIVars = configs => {
const commitSha = configs.CI_COMMIT_SHA || configs.GIT_COMMIT
const baseBranch = configs.CI_BASE_BRANCH || 'origin/master'
const githubAccessToken = configs.GITHUB_TOKEN

const repo = getRepo(env)
const repoOwner = env.CI_REPO_OWNER || repo?.split('/')[0]
const repoName = env.CI_REPO_NAME || repo?.split('/')[1]
const repo = getRepo(configs)
const repoOwner = configs.CI_REPO_OWNER || repo?.split('/')[0]
const repoName = configs.CI_REPO_NAME || repo?.split('/')[1]

return {
baseBranch,
Expand Down
116 changes: 115 additions & 1 deletion src/getConfig.js
Original file line number Diff line number Diff line change
@@ -1,8 +1,122 @@
import minimist from 'minimist'

import getCIVars from './getCIVars'
import ensureValid from './ensureValid'
import { parseConfigFile } from './parseConfigFile'

const SUPPORTED_CONFIGS_VARS = [
{
envVarName: 'COMMITWATCH_GITHUB_TOKEN',
cliVarName: 'github-token',
fileVarName: 'githubToken',
configName: 'GITHUB_TOKEN',
},
{
envVarName: 'CI_REPO_OWNER',
cliVarName: 'ci-repo-owner',
fileVarName: 'ciRepoOwner',
configName: 'CI_REPO_OWNER',
},
{
envVarName: 'CI_REPO_NAME',
cliVarName: 'ci-repo-name',
fileVarName: 'ciRepoName',
configName: 'CI_REPO_NAME',
},
{
envVarName: 'GIT_URL',
cliVarName: 'git-url',
fileVarName: 'gitUrl',
configName: 'GIT_URL',
},
{
envVarName: 'CI_COMMIT_SHA',
cliVarName: 'ci-commit-sha',
fileVarName: 'ciCommitSha',
configName: 'CI_COMMIT_SHA',
},
{
envVarName: 'CI_BASE_BRANCH',
cliVarName: 'ci-base-branch',
fileVarName: 'ciBaseBranch',
configName: 'CI_BASE_BRANCH',
},
{
envVarName: 'COMMIT_WATCH_OUTPUT_DIR',
cliVarName: 'output-dir',
fileVarName: 'outputDir',
configName: 'OUTPUT_DIR',
},
{
envVarName: 'COMMIT_WATCH_OUTPUT_FILENAME',
cliVarName: 'output-filename',
fileVarName: 'outputFilename',
configName: 'OUTPUT_FILENAME',
},
{
envVarName: 'VERBOSE',
cliVarName: 'verbose',
fileVarName: 'verbose',
configName: 'VERBOSE',
},
]

const getCLIArgs = () => minimist(process.argv.slice(2))

export const getBaseConfigs = () => {
const filterUndefineds = obj =>
Object.keys(obj).reduce((acc, currKey) => {
if (!obj[currKey]) {
return acc
}

return {
...acc,
[currKey]: obj[currKey],
}
}, {})

const cliArgs = getCLIArgs()

const configFileArgs = cliArgs['config-file']
? parseConfigFile(cliArgs['config-file'])
: {}

const {
envConfigs,
fileConfigs,
cliConfigs,
} = SUPPORTED_CONFIGS_VARS.reduce(
(acc, nextConfig) => ({
envConfigs: {
...acc.envConfigs,
[nextConfig.configName]: process.env[nextConfig.envVarName],
},
fileConfigs: {
...acc.fileConfigs,
[nextConfig.configName]: configFileArgs[nextConfig.fileVarName],
},
cliConfigs: {
...acc.cliConfigs,
[nextConfig.configName]: cliArgs[nextConfig.cliVarName],
},
}),
{
envConfigs: {},
fileConfigs: {},
cliConfigs: {},
},
)

return {
...filterUndefineds(envConfigs),
...filterUndefineds(fileConfigs),
...filterUndefineds(cliConfigs),
}
}

const getConfig = customConfig => {
const ciVars = getCIVars(process.env)
const ciVars = getCIVars(getBaseConfigs())

const config = {
baseBranch: ciVars.baseBranch,
Expand Down
4 changes: 3 additions & 1 deletion src/logger.js
Original file line number Diff line number Diff line change
@@ -1,5 +1,7 @@
import chalk from 'chalk'

import { getBaseConfigs } from './getConfig'

const stdout = console.log // eslint-disable-line no-console
const stderr = console.error // eslint-disable-line no-console

Expand All @@ -23,7 +25,7 @@ const log = message => {
}

const info = message => {
if (process.env.VERBOSE === '1') {
if (getBaseConfigs().VERBOSE === '1') {
stdout(`[INFO] ${message}`)
}
}
Expand Down
9 changes: 4 additions & 5 deletions src/main.js
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ import format from '@commitlint/format'
import logger from './logger'
import { EXIT_FAILURE, EXIT_SUCCESS, STATUSES } from './constants'
import getCommitResults from './getCommitResults'
import { getBaseConfigs } from './getConfig'

function buildJunitFile({ lintFailures, outputDir, outputFilename }) {
const suite = builder.testSuite().name('commitwatch')
Expand All @@ -24,6 +25,7 @@ function buildJunitFile({ lintFailures, outputDir, outputFilename }) {

const main = async () => {
const results = await getCommitResults()
const configs = getBaseConfigs()

if (results.status === STATUSES.FAIL) {
logger.log(chalk.redBright('commitWatch FAIL'))
Expand All @@ -32,11 +34,8 @@ const main = async () => {
buildJunitFile({
lintFailures: results.lintFailures,
outputDir:
process.env.COMMIT_WATCH_OUTPUT_DIR ??
'artifacts/test_results/commitwatch/',
outputFilename:
process.env.COMMIT_WATCH_OUTPUT_FILENAME ??
'commitwatch.junit.xml',
configs.OUTPUT_DIR ?? 'artifacts/test_results/commitwatch/',
outputFilename: configs.OUTPUT_FILENAME ?? 'commitwatch.junit.xml',
})
return EXIT_FAILURE
}
Expand Down
6 changes: 6 additions & 0 deletions src/parseConfigFile.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
import path from 'path'

export const parseConfigFile = configFileRelativePath => {
const absolutePath = path.resolve(configFileRelativePath)
return require(require.resolve(absolutePath))
}
111 changes: 111 additions & 0 deletions src/tests/getConfig.test.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,111 @@
import getConfig, { getBaseConfigs } from '../getConfig'

import CONFIG_FILE_CONFIGS from './mock.config'

describe('getBaseConfigs', () => {
const oldEnv = process.env
const oldArgv = process.argv

const ENV_VARIABLE_CONFIGS = {
COMMITWATCH_GITHUB_TOKEN: 'COMMITWATCH_GITHUB_TOKEN_ENV',
CI_REPO_OWNER: 'CI_REPO_OWNER_ENV',
CI_REPO_NAME: 'CI_REPO_NAME_ENV',
CI_COMMIT_SHA: 'CI_COMMIT_SHA_ENV',
CI_BASE_BRANCH: 'CI_BASE_BRANCH_ENV',
COMMIT_WATCH_OUTPUT_DIR: 'COMMIT_WATCH_OUTPUT_DIR_ENV',
COMMIT_WATCH_OUTPUT_FILENAME: 'COMMIT_WATCH_OUTPUT_FILENAME_ENV',
VERBOSE: 'VERBOSE_ENV',
}

const fileConfigToConfig = fileConfig =>
Object.keys(fileConfig).reduce(
(acc, currKey) => ({
...acc,
[currKey.replace(/([A-Z])/g, '_$1').toUpperCase()]: fileConfig[
currKey
],
}),
{},
)

beforeAll(() => {
process.env = ENV_VARIABLE_CONFIGS
})

afterEach(() => {
process.argv = oldArgv
})

afterAll(() => {
process.env = oldEnv
})

it('Uses environment variables as config \
if not provided config file or cli configs', () => {
expect(getBaseConfigs()).toEqual({
GITHUB_TOKEN: ENV_VARIABLE_CONFIGS.COMMITWATCH_GITHUB_TOKEN,
OUTPUT_DIR: ENV_VARIABLE_CONFIGS.COMMIT_WATCH_OUTPUT_DIR,
OUTPUT_FILENAME: ENV_VARIABLE_CONFIGS.COMMIT_WATCH_OUTPUT_FILENAME,
VERBOSE: ENV_VARIABLE_CONFIGS.VERBOSE,
CI_BASE_BRANCH: ENV_VARIABLE_CONFIGS.CI_BASE_BRANCH,
CI_COMMIT_SHA: ENV_VARIABLE_CONFIGS.CI_COMMIT_SHA,
CI_REPO_OWNER: ENV_VARIABLE_CONFIGS.CI_REPO_OWNER,
CI_REPO_NAME: ENV_VARIABLE_CONFIGS.CI_REPO_NAME,
})
})

it('Values from env variables are overwritten when a \
values from config file are available', () => {
process.argv = ['_', '_', '--config-file', 'src/tests/mock.config.js']

expect(getBaseConfigs()).toEqual({
...fileConfigToConfig(CONFIG_FILE_CONFIGS),
GITHUB_TOKEN: ENV_VARIABLE_CONFIGS.COMMITWATCH_GITHUB_TOKEN,
})
})

it('Config values passed in as cli args overwrite\
values from config file and values from env variables', () => {
const mockGithubToken = 'MOCK_GITHUB_TOKEN'
const mockOutputDir = 'MOCK_OUTPUT_DIR'

process.argv = [
'_',
'_',
'--config-file',
'src/tests/mock.config.js',
'--github-token',
mockGithubToken,
'--output-dir',
mockOutputDir,
]

expect(getBaseConfigs()).toEqual({
...fileConfigToConfig(CONFIG_FILE_CONFIGS),
OUTPUT_DIR: mockOutputDir,
GITHUB_TOKEN: mockGithubToken,
})
})

it('Correctly retrieves the repo name and owner from the GitHub URL if\
no repo name and owner is given', () => {
const mockRepoOwner = 'EXAMPLE_CORP'
const mockRepoName = 'EXAMPLE_REPO'
const mockRepo = `${mockRepoOwner}/${mockRepoName}`

process.env = {
...ENV_VARIABLE_CONFIGS,
CI_REPO_OWNER: undefined,
CI_REPO_NAME: undefined,
GIT_URL: `git@github.com:${mockRepo}.git`,
}

expect(getConfig()).toEqual({
commitSha: ENV_VARIABLE_CONFIGS.CI_COMMIT_SHA,
baseBranch: ENV_VARIABLE_CONFIGS.CI_BASE_BRANCH,
githubAccessToken: ENV_VARIABLE_CONFIGS.COMMITWATCH_GITHUB_TOKEN,
repoOwner: mockRepoOwner,
repoName: mockRepoName,
})
})
})

0 comments on commit 39a6549

Please sign in to comment.