diff --git a/.travis.yml b/.travis.yml index 5e51258..ee2bc00 100644 --- a/.travis.yml +++ b/.travis.yml @@ -22,6 +22,8 @@ script: # - shellcheck -x $(git ls-files | grep '[.]sh$') - ./node_modules/.bin/madge -c . - yarn audit + - yarn build + - yarn lint - yarn test before_deploy: diff --git a/README.md b/README.md index ffc4443..a1dc5bd 100644 --- a/README.md +++ b/README.md @@ -31,10 +31,13 @@ The plugin should be added to your config "projectId": "UH", "releaseNameTemplate": "Test v${version}", "jiraHost": "uphabit.atlassian.net", - "ticketPrefixes": [ "TEST", "UH"] + "ticketPrefixes": [ "TEST", "UH"], + "ticketRegex": "[a-zA-Z]{3,5}-\\d{3,5}" }] ] } + +Please note that `ticketRegex` cannot be used together with `ticketPrefixes`. ``` ```typescript interface Config { @@ -42,9 +45,13 @@ interface Config { /// A domain of a jira instance ie: `uphabit.atlasian.net` jiraHost: string; - // A list of prefixes to match when looking for tickets in commits + // A list of prefixes to match when looking for tickets in commits. Cannot be used together with ticketRegex. // ie. ['TEST'] would match `TEST-123` and `TEST-456` - ticketPrefixes: string[]; + ticketPrefixes?: string[]; + + // A unescaped regex to match tickets in commits (without slashes). Cannot be used together with ticketPrefixes. + // ie. [a-zA-Z]{4}-\d{3,5} would match any ticket with 3 letters a dash and 3 to 5 numbers, such as `TEST-456`, `TEST-5643` and `TEST-56432` + ticketRegex?: string; // The id or key for the project releases will be created in projectId: string; diff --git a/babel.config.js b/babel.config.js new file mode 100644 index 0000000..6b53630 --- /dev/null +++ b/babel.config.js @@ -0,0 +1,6 @@ +module.exports = { + presets: [ + '@babel/preset-env', + '@babel/preset-typescript', + ], +}; \ No newline at end of file diff --git a/lib/success.ts b/lib/success.ts index 08a90f8..7f6d742 100644 --- a/lib/success.ts +++ b/lib/success.ts @@ -5,14 +5,20 @@ import { makeClient } from './jira'; import { GenerateNotesContext, PluginConfig } from './types'; import { escapeRegExp } from './util'; -function getTickets(config: PluginConfig, context: GenerateNotesContext): string[] { - const patterns = config.ticketPrefixes! +export function getTickets(config: PluginConfig, context: GenerateNotesContext): string[] { + let patterns: RegExp[] = []; + + if (config.ticketRegex !== undefined) { + patterns = [new RegExp(config.ticketRegex, 'giu')]; + } else { + patterns = config.ticketPrefixes! .map(prefix => new RegExp(`\\b${escapeRegExp(prefix)}-(\\d+)\\b`, 'giu')); + } const tickets = new Set(); for (const commit of context.commits) { for (const pattern of patterns) { - const matches = pattern.exec(commit.message); + const matches = commit.message.match(pattern); if (matches) { tickets.add(matches[0]); context.logger.info(`Found ticket ${matches[0]} in commit: ${commit.commit.short}`); diff --git a/lib/types.ts b/lib/types.ts index 2fc084b..38bd367 100644 --- a/lib/types.ts +++ b/lib/types.ts @@ -56,7 +56,8 @@ export interface BaseConfig { } export interface PluginConfig extends BaseConfig { - ticketPrefixes: string[]; + ticketPrefixes?: string[]; + ticketRegex?: string; projectId: string; releaseNameTemplate?: string; jiraHost: string; diff --git a/lib/verifyConditions.ts b/lib/verifyConditions.ts index b248577..8692c3c 100644 --- a/lib/verifyConditions.ts +++ b/lib/verifyConditions.ts @@ -10,13 +10,28 @@ export async function verifyConditions(config: PluginConfig, context: PluginCont if (typeof config.projectId !== 'string') { throw new SemanticReleaseError(`config.projectId must be a string`); } - if (!Array.isArray(config.ticketPrefixes)) { - throw new SemanticReleaseError(`config.ticketPrefixes must be an array of string`); + + if (!config.ticketPrefixes && !config.ticketRegex) { + throw new SemanticReleaseError('Either config.ticketPrefixes or config.ticketRegex must be passed'); + } + + if (config.ticketPrefixes && config.ticketRegex) { + throw new SemanticReleaseError(`config.ticketPrefixes and config.ticketRegex cannot be passed at the same time`); } - for (const prefix of config.ticketPrefixes) { - if (typeof prefix !== 'string') { + + if (config.ticketPrefixes) { + if (!Array.isArray(config.ticketPrefixes)) { throw new SemanticReleaseError(`config.ticketPrefixes must be an array of string`); } + for (const prefix of config.ticketPrefixes) { + if (typeof prefix !== 'string') { + throw new SemanticReleaseError(`config.ticketPrefixes must be an array of string`); + } + } + } + + if (config.ticketRegex && typeof config.ticketRegex !== 'string') { + throw new SemanticReleaseError(`config.ticketRegex must be an string`); } if (config.releaseNameTemplate) { diff --git a/package.json b/package.json index 23685a5..a75bd3e 100644 --- a/package.json +++ b/package.json @@ -7,7 +7,9 @@ "private": false, "scripts": { "prepublishOnly": "mkdir -p dist && rm -rf dist && tsc -p .", - "test": "tsc -p . && tslint -p . && true \n#\n# No tests 😱\n#" + "lint": "tslint -p .", + "build": "tsc -p .", + "test": " yarn jest" }, "dependencies": { "@semantic-release/error": "^2.2.0", @@ -16,9 +18,13 @@ "tslib": "^1.9.2" }, "devDependencies": { + "@babel/preset-env": "^7.5.5", + "@babel/preset-typescript": "^7.3.3", + "@types/jest": "^24.0.17", "@types/lodash": "^4.14.120", "@types/node": "^10.12.19", "@types/signale": "^1.2.0", + "jest": "^24.8.0", "madge": "^3.4.3", "tslint": "^5.12.1", "tslint-config-airbnb": "^5.11.1", diff --git a/test-config/package.json b/test-config/package.json index ab3c98b..8873ad1 100644 --- a/test-config/package.json +++ b/test-config/package.json @@ -8,7 +8,7 @@ "@semantic-release/commit-analyzer": "^6.1.0", "@semantic-release/npm": "^5.1.4", "@semantic-release/release-notes-generator": "^7.1.4", - "semantic-release": "^15.13.3", + "semantic-release": "^15.13.3 || ^16.0.0", "semantic-release-jira-releases": "./.." } } diff --git a/test/fakedata.ts b/test/fakedata.ts new file mode 100644 index 0000000..2b705bb --- /dev/null +++ b/test/fakedata.ts @@ -0,0 +1,76 @@ +import { BaseConfig, Person, Commit, PreviousRelease, UpcomingRelease, PluginConfig, PluginContext, GenerateNotesContext } from "../lib/types"; + +export const baseConfig: BaseConfig = { + $0: '', + branch: 'test', + debug: true, + dryRun: true, +} + +export const date = new Date("2019-01-01T00:00:00.000Z") + +export const author: Person = { + name: 'test', + email: 'email', + date, +} + +export const commits: Commit[] = [ + 'chore: fixing whitespace', + 'docs: adding regex unescaping', + 'fix: escaping regex', + 'docs: [FIX-321] editing readme', + 'feat: [UH-1258] better logging ', + 'feat: [UH-1258] Implement release creation', + 'fix: [FIX-123] typescript config', + 'fix: [TEST-123] test commit', +].map(m => ({ + author, + committer: author, + commitDate: date, + body: '', + hash: '', + message: m, + subject: '', + commit: { + long: '', + short: '' + } +})) + +export const previousRelease: PreviousRelease = { + gitHead: '', + gitTag: '', + version: '' +} + +export const upcomingRelease: UpcomingRelease = { + ...previousRelease, + notes: '', + type: '' +} + +export const pluginConfig: Partial = { + ...baseConfig, + projectId: 'TEST', + jiraHost: 'testjira.com' +} + +export const logger = { + info: jest.fn() +} + +export const pluginContext: PluginContext = { + cwd: '', + env: {}, + logger: logger as any, + options: baseConfig, + stderr: null, + stdout: null, +} +export const context: GenerateNotesContext = { + ...pluginContext, + commits, + lastRelease: previousRelease, + nextRelease: upcomingRelease +} \ No newline at end of file diff --git a/test/test.ts b/test/test.ts new file mode 100644 index 0000000..5550868 --- /dev/null +++ b/test/test.ts @@ -0,0 +1,35 @@ +import { getTickets } from "../lib/success"; +import { pluginConfig, context } from "./fakedata"; +import { PluginConfig } from "../lib/types"; + +describe('Success tests', () => { + describe('#getTickets', () => { + it('should analyze tickets with one ticketPrefix', () => { + const config = { + ...pluginConfig, + ticketPrefixes: ['UH'] + } as PluginConfig; + expect(getTickets(config, context)).toEqual(['UH-1258']) + }) + + it('should analyze tickets with many ticketPrefix', () => { + const config = { + ...pluginConfig, + ticketPrefixes: ['UH', 'FIX'] + } as PluginConfig + expect(getTickets(config, context)).toEqual(['FIX-321', 'UH-1258', 'FIX-123']) + }) + + it('should analyze tickets with ticketRegex', () => { + const ticketRegex = '[A-Za-z]+-\\d+'; + + const config: PluginConfig = { + ...pluginConfig, + ticketRegex + } as PluginConfig; + + expect(getTickets(config, context)).toEqual(['FIX-321', 'UH-1258', 'FIX-123', 'TEST-123']) + }) + }) +}) +