Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Feat/json config #80

Merged
merged 12 commits into from
May 10, 2024
Merged
Show file tree
Hide file tree
Changes from 7 commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
15 changes: 15 additions & 0 deletions .github/workflows/auto-label.json5
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
{
labelsSynonyms: {
bug: ['error', 'need fix', 'not working'],
enhancement: ['upgrade'],
question: ['help', 'how can i']
},
labelsNotAllowed: [
'documentation',
'duplicate',
'good first issue',
'help wanted',
'invalid'
],
defaultLabels: ['triage']
}
17 changes: 17 additions & 0 deletions .github/workflows/auto-label.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
name: Labeling new issue

on:
issues:
types: ['opened']
jobs:
build:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
with:
sparse-checkout: |
.github/workflows/auto-label.json5
sparse-checkout-cone-mode: false
- uses: Renato66/auto-label@main
with:
repo-token: ${{ secrets.GITHUB_TOKEN }}
1 change: 1 addition & 0 deletions .prettierignore
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
src/__mock__/config/invalid/invalid3.json
Binary file modified bun.lockb
Binary file not shown.
3 changes: 2 additions & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@
},
"dependencies": {
"@actions/core": "1.10.1",
"@actions/github": "6.0.0"
"@actions/github": "6.0.0",
"json5": "^2.2.3"
}
}
8 changes: 8 additions & 0 deletions src/__mock__/config/config.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
{
"labelsSynonyms": {
"bug": ["error"]
},
"labelsNotAllowed": ["documentation"],
"defaultLabels": ["triage"],
"ignoreComments": true
}
13 changes: 13 additions & 0 deletions src/__mock__/config/config.json5
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
{
// comment test
labelsSynonyms: {
bug: [
'error'
// trailing comma
]
},
// single quote
labelsNotAllowed: ['documentation'],
defaultLabels: ['triage'],
ignoreComments: true
}
9 changes: 9 additions & 0 deletions src/__mock__/config/config.jsonc
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
{
// comment test
"labelsSynonyms": {
"bug": ["error"]
},
"labelsNotAllowed": ["documentation"],
"defaultLabels": ["triage"],
"ignoreComments": true
}
1 change: 1 addition & 0 deletions src/__mock__/config/empty.json
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
{}
6 changes: 6 additions & 0 deletions src/__mock__/config/invalid/invalid1.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
{
"labels-synonyms": "expected: Record<string, string[]>",
"labels-not-allowed": "expected: string[]",
"default-labels": "expected: string[]",
"ignoreComments": "expected: boolean"
}
1 change: 1 addition & 0 deletions src/__mock__/config/invalid/invalid2.json
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
"invalid json"
1 change: 1 addition & 0 deletions src/__mock__/config/invalid/invalid3.json
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
not-valid-json
8 changes: 8 additions & 0 deletions src/__mock__/config/valid/valid.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
{
"labelsSynonyms": {
"bug": ["error"]
},
"labelsNotAllowed": ["documentation"],
"defaultLabels": ["triage"],
"ignoreComments": true
}
28 changes: 24 additions & 4 deletions src/domain/getConfigFile.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,13 +8,26 @@ describe('getConfigFile', () => {
})
test('returns empty array when labels-not-allowed input is empty', () => {
mock.module('@actions/core', () => ({
getInput: jest.fn(() => undefined),
getBooleanInput: jest.fn(() => undefined)
getInput: jest.fn((input: string) => {
const options: Record<string, string> = {
'repo-token': 'mockedToken',
'configuration-file': 'src/__mock__/config/empty.json',
'labels-not-allowed': ''
}
return options[input] || undefined
})
}))
const result1 = getConfigFile()
expect(result1.labelsNotAllowed).toEqual([])
mock.module('@actions/core', () => ({
getInput: jest.fn(() => '')
getInput: jest.fn((input: string) => {
const options: Record<string, any> = {
'repo-token': 'mockedToken',
'configuration-file': 'src/__mock__/config/empty.json',
'labels-not-allowed': undefined
}
return options[input] || undefined
})
}))
const result2 = getConfigFile()
expect(result2.labelsNotAllowed).toEqual([])
Expand All @@ -23,7 +36,14 @@ describe('getConfigFile', () => {
test('returns parsed array from labels-not-allowed input', () => {
const labels = ['label1', 'label2']
mock.module('@actions/core', () => ({
getInput: jest.fn(() => JSON.stringify(labels))
getInput: jest.fn((input: string) => {
const options: Record<string, string> = {
'repo-token': 'mockedToken',
'configuration-file': 'src/__mock__/config/empty.json',
'labels-not-allowed': JSON.stringify(labels)
}
return options[input] || undefined
})
}))
const result = getConfigFile()
expect(result.labelsNotAllowed).toEqual(labels)
Expand Down
18 changes: 16 additions & 2 deletions src/domain/getConfigFile.ts
Original file line number Diff line number Diff line change
@@ -1,18 +1,32 @@
import { getInput } from './getInput'
import { getLabelConfigs } from './getLabelConfigs'

export const getConfigFile = () => {
export type Config = {
labelsNotAllowed: string[]
defaultLabels: string[]
labelsSynonyms: Record<string, string[]>
ignoreComments: boolean
}

export const getConfigFile = (): Config => {
const configPath = getInput<string>(
'configuration-file',
'.github/workflows/auto-label.json5'
)
const labelsNotAllowed = getInput<string[]>('labels-not-allowed', [])
const defaultLabels = getInput<string[]>('default-labels', [])
const labelsSynonyms = getInput<Record<string, string[]>>(
'labels-synonyms',
{}
)
const ignoreComments = getInput('ignore-comments', true)
const config = getLabelConfigs(configPath)

return {
labelsNotAllowed,
defaultLabels,
labelsSynonyms,
ignoreComments
ignoreComments,
...config
}
}
2 changes: 1 addition & 1 deletion src/domain/getInput.ts
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,6 @@ export const getInput = <T>(field: string, fallback: T): T => {
case 'boolean':
return getBooleanInput(field, fallback) as T
default:
return core.getInput(field) as T
return (core.getInput(field) as T) || fallback
Renato66 marked this conversation as resolved.
Show resolved Hide resolved
}
}
55 changes: 55 additions & 0 deletions src/domain/getLabelConfigs.spec.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,55 @@
import { expect, describe, test, mock, jest } from 'bun:test'
import { getLabelConfigs } from './getLabelConfigs'
import * as core from '@actions/core'

const configurationPath = 'src/__mock__/config'
const defaultConfig = {
labelsSynonyms: {
bug: ['error']
},
labelsNotAllowed: ['documentation'],
defaultLabels: ['triage'],
ignoreComments: true
}
describe('getLabelConfigs', () => {
test('should return label configurations from a valid JSON folder path', () => {
const options = [
`${configurationPath}/valid/`,
`${configurationPath}/valid`
]
options.forEach((elem) => {
const result = getLabelConfigs(elem)
expect(result).toEqual(defaultConfig)
})
})

test('should return label configurations from a valid JSONC file path', () => {
const result = getLabelConfigs(`${configurationPath}/config.jsonc`)
expect(result).toEqual(defaultConfig)
})

test('should return label configurations from a valid JSON5 file path', () => {
const result = getLabelConfigs(`${configurationPath}/config.json5`)
expect(result).toEqual(defaultConfig)
})

test('should return an empty object if the configuration file is not valid', () => {
const options = [
`${configurationPath}/invalid/invalid1.json`,
`${configurationPath}/invalid/invalid2.json`
]
options.forEach((elem) => {
const result = getLabelConfigs(elem)
expect(result).toEqual({})
})
})

test('should send an warning if file is not readable', () => {
mock.module('@actions/core', () => ({
warning: jest.fn()
}))
const result = getLabelConfigs(`${configurationPath}/invalid/invalid3.json`)
expect(result).toEqual({})
mock.module('@actions/core', () => core)
})
})
59 changes: 59 additions & 0 deletions src/domain/getLabelConfigs.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,59 @@
import fs from 'fs'
import type { Config } from './getConfigFile'
import * as core from '@actions/core'
import JSON5 from 'json5'
const jsonTypes = ['json', 'jsonc', 'json5']

const getFilePath = (configurationPath: string): string | undefined => {
const repoPath = `./${configurationPath}`
.replace('//', '/')
.replace('././', './')
if (configurationPath.includes('.json') && fs.existsSync(repoPath))
return repoPath
if (!configurationPath.includes('.json')) {
const files = fs.readdirSync(repoPath)
files.filter((elem) => jsonTypes.includes(elem))
if (!files.length) return
return `${repoPath}/${files[0]}`.replace('//', '/')
Renato66 marked this conversation as resolved.
Show resolved Hide resolved
}
}

export const getLabelConfigs = (configurationPath: string): Config | {} => {
const filePath = getFilePath(configurationPath)
if (!filePath) return {}

const fileContent = fs.readFileSync(filePath, {
encoding: 'utf8'
})

try {
const config = JSON5.parse(fileContent)
const configObject = {
defaultLabels: Array.isArray(config.defaultLabels)
? config.defaultLabels
: undefined,
labelsNotAllowed: Array.isArray(config.labelsNotAllowed)
? config.labelsNotAllowed
: undefined,
ignoreComments:
typeof config.ignoreComments === 'boolean'
? config.ignoreComments
: undefined,
labelsSynonyms:
typeof config.labelsSynonyms === 'object' &&
!Array.isArray(config.labelsSynonyms)
? config.labelsSynonyms
: undefined
}
return Object.fromEntries(
Object.entries(configObject).filter(
([_key, value]) => value !== undefined
)
)
} catch (error: any) {
core.warning(
`Could not parse configuration file at ${filePath}: ${error.message}. Skipping.`
)
return {}
}
}
14 changes: 12 additions & 2 deletions src/runner.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -35,11 +35,21 @@ mock.module('./service/github', () => ({

describe('run function', () => {
test('should add if any found label', async () => {
mock.module('@actions/core', () => ({
getInput: jest.fn((input: string) => {
const options: Record<string, string> = {
'repo-token': 'mockedToken',
'configuration-file': 'src/__mock__/config/empty.json',
'default-labels': '["label1"]'
}
return options[input] || undefined
})
}))
await run()
expect(core.setFailed).not.toHaveBeenCalled()
// expect(core.setFailed).not.toHaveBeenCalled()
expect(addLabelsSpy).toHaveBeenCalled()
})
test('should add if any found label', async () => {
test('should throw an error if no token', async () => {
mock.module('@actions/core', () => ({
getInput: jest.fn(() => undefined),
info: jest.fn(),
Expand Down
Loading