Skip to content

Commit

Permalink
feat: update tags
Browse files Browse the repository at this point in the history
Automatically moves tags for major, minor and prerelease revision.
* `1.2.3` moves tag `v1.2`
* `1.2.3` moves tag `v1` if there is no `v1.3.0`
* `1.2.3-alpha.4` moves tag `v1.2.3-alpha`
  • Loading branch information
ph-fritsche committed Jan 8, 2021
1 parent f3c7f85 commit 245e2d7
Show file tree
Hide file tree
Showing 11 changed files with 195 additions and 12 deletions.
24 changes: 21 additions & 3 deletions src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,8 @@ import { install } from './util/install'
import debugLib from 'debug'
import { forceRelease, initialRelease } from './plugin'
import { PluginSpec } from './semantic-release'
import { updateTags } from './util/updateTags'
import { gitConfig } from './util/gitConfig'

export default async function run(env = process.env): Promise<void> {
try {
Expand Down Expand Up @@ -73,11 +75,27 @@ export default async function run(env = process.env): Promise<void> {
core.setOutput('version', nextRelease.version)
core.setOutput('gitTag', nextRelease.gitTag)

const parts = ['major', 'minor', 'patch', 'revision']
const v = nextRelease.version.split(/\D/, 4)
parts.forEach((k, i) => core.setOutput(k, v[i]))
const versionRegExp = /^(?<major>\d+)\.(?<minor>\d+)\.(?<patch>\d+)(?:[-](?<revision>(?:(?<revisionType>\w+)\.)?\d+))?/
const version = nextRelease.version.match(versionRegExp)?.groups as {
major: string,
minor: string,
patch: string,
revision: string | undefined,
revisionType: string | undefined
}
Object.entries(version).forEach(([k, v]) => core.setOutput(k, v ?? ''))

core.setOutput('notes', nextRelease.notes)

if (!dryRun) {
const [tagPrefix] = nextRelease.gitTag.split(nextRelease.version)
await gitConfig(env)
const updatedTags = await updateTags('HEAD', nextRelease.version, version, tagPrefix)
if (updatedTags.length) {
core.info(`Updated tags: ${updatedTags.join(', ')}`)
}
}

} catch(e) {
core.setFailed(e?.message ?? e)
}
Expand Down
12 changes: 12 additions & 0 deletions src/util/gitConfig.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
import { spawn } from './spawn'

export async function gitConfig(env: {[k: string]: string | undefined}): Promise<void> {
const name = env.GITHUB_ACTOR || 'github-actions[bot]'

const email = env.GITHUB_ACTOR
? `${env.GITHUB_ACTOR}@users.noreply.github.com`
: '41898282+github-actions[bot]@users.noreply.github.com'

await spawn('git', ['config', '--global', 'user.name', name])
await spawn('git', ['config', '--global', 'user.email', email])
}
4 changes: 2 additions & 2 deletions src/util/install.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ import { info } from '@actions/core'
import { spawn } from './spawn'
import { resolve } from './resolve'

export function install(packages: string[], log: (msg: string) => void): Promise<void> {
export function install(packages: string[], log: (msg: string) => void): Promise<string> {
const missing = packages.filter(resolvableName => {
try {
const module = resolve(resolvableName)
Expand All @@ -25,5 +25,5 @@ export function install(packages: string[], log: (msg: string) => void): Promise
return spawn('npm', args, {})
}

return Promise.resolve()
return Promise.resolve('')
}
10 changes: 7 additions & 3 deletions src/util/spawn.ts
Original file line number Diff line number Diff line change
@@ -1,12 +1,16 @@
import * as core from '@actions/core'
import * as child_process from 'child_process'

export function spawn(cmd: string, args: string[] = [], options: child_process.SpawnOptions = {}): Promise<void> {
return new Promise<void>((res, rej) => {
export function spawn(cmd: string, args: string[] = [], options: child_process.SpawnOptions = {}): Promise<string> {
return new Promise((res, rej) => {
const child = child_process.spawn(cmd, args, options)

let output = ''
const buffer = {out: '', err: ''}
function addBuffered(type: keyof typeof buffer, data: Buffer) {
if (type === 'out') {
output += data
}
buffer[type] += data
sendBuffered(type)
}
Expand All @@ -33,7 +37,7 @@ export function spawn(cmd: string, args: string[] = [], options: child_process.S
sendBuffered('out', true)
sendBuffered('err', true)
if (code === 0) {
res()
res(output)
} else {
rej(`${cmd} ${JSON.stringify(args)} failed: ${signal ?? code}`)
}
Expand Down
42 changes: 42 additions & 0 deletions src/util/updateTags.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
import { spawn } from './spawn'

export async function updateTags(
ref: string,
versionString: string,
version: {
major: string,
minor: string,
patch: string,
revision: string | undefined,
revisionType: string | undefined,
},
tagPrefix = '',
): Promise<string[]> {
const tags: string[] = []

if (!version.revision) {
const minorTag = `${tagPrefix}${version.major}.${version.minor}`
tags.push(minorTag)

const nextMinor = `${tagPrefix}${version.major}.${Number(version.minor) + 1}.0`
const hasNextMinor = await spawn('git', ['ls-remote', 'origin', `refs/tags/${nextMinor}`])
if (!hasNextMinor) {
const majorTag = `${tagPrefix}${version.major}`
tags.push(majorTag)
}
} else if (version.revisionType) {
const revisionTag = `${tagPrefix}${version.major}.${version.minor}.${version.patch}-${version.revisionType}`
tags.push(revisionTag)
}

if (tags.length) {
for (const tag of tags) {
await spawn('git', ['tag', '-fam', versionString, tag, ref])
}
await spawn('git', ['push', '-f', 'origin',
...tags.map(t => `refs/tags/${t}:refs/tags/${t}`),
])
}

return tags
}
2 changes: 1 addition & 1 deletion test/_releaseResult.ts
Original file line number Diff line number Diff line change
Expand Up @@ -68,4 +68,4 @@ export default {
},
],
// typing does not match documented output
} as unknown as SemanticRelease.Result
} as unknown as Extract<SemanticRelease.Result, {[k: string]: unknown }>
43 changes: 42 additions & 1 deletion test/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ import type SemanticRelease from 'semantic-release'
import run from '../src/index'
import { forceRelease, initialRelease } from '../src/plugin'
import defaultResult from './_releaseResult'
import type { updateTags as realUpdateTags } from '../src/util/updateTags'

const releaseResult = defaultResult as SemanticRelease.Result

Expand Down Expand Up @@ -39,6 +40,20 @@ jest.mock('../src/util/install', () => ({
install: (packages: string[]) => install(packages),
}))

let gitConfig: () => Promise<string>
jest.mock('../src/util/gitConfig', () => ({
gitConfig: () => gitConfig(),
}))

let updateTags: typeof realUpdateTags
jest.mock('../src/util/updateTags', () => ({
updateTags: (...a: Parameters<typeof realUpdateTags>) => updateTags(...a),
}))

jest.mock('../src/util/spawn', () => ({
spawn: () => { throw 'this should not have been called - every helper should be mocked' },
}))

function setup() {
const exec = (input = {}, releaseResult: SemanticRelease.Result = false, env = {}) => {
setFailed = jest.fn()
Expand All @@ -50,6 +65,8 @@ function setup() {

install = jest.fn(() => Promise.resolve(''))
release = jest.fn(() => releaseResult)
gitConfig = jest.fn(() => Promise.resolve(''))
updateTags = jest.fn(() => Promise.resolve([]))

return run(env)
}
Expand Down Expand Up @@ -105,7 +122,8 @@ it('output release informations', () => {
major: '1',
minor: '1',
patch: '0',
revision: undefined,
revision: '',
revisionType: '',
notes: 'Release notes for version 1.1.0...',
})
})
Expand Down Expand Up @@ -180,3 +198,26 @@ it('run with inline config', () => {
})
})
})

it('call updateTag', () => {
const { exec } = setup()

const run = exec(undefined, {...defaultResult, nextRelease: {
gitHead: 'abc',
gitTag: 'v1.2.3-foo.1',
notes: 'some notes...',
type: 'major',
version: '1.2.3-foo.1',
}})

return run.finally(() => {
expect(gitConfig).toBeCalled()
expect(updateTags).toBeCalledWith('HEAD', '1.2.3-foo.1', {
major: '1',
minor: '2',
patch: '3',
revision: 'foo.1',
revisionType: 'foo',
}, 'v')
})
})
16 changes: 16 additions & 0 deletions test/util/gitConfig.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
import { gitConfig } from '../../src/util/gitConfig'

let spawnMock: (...a: unknown[]) => Promise<string>
jest.mock('../../src/util/spawn', () => ({
spawn: (...a: unknown[]) => spawnMock(...a),
}))

test('set github actor', async () => {
spawnMock = jest.fn()

await gitConfig({GITHUB_ACTOR: 'foo'})

expect(spawnMock).toHaveBeenNthCalledWith(1, 'git', ['config', '--global', 'user.name', 'foo'])
expect(spawnMock).toHaveBeenNthCalledWith(2, 'git', ['config', '--global', 'user.email', 'foo@users.noreply.github.com'])
expect(spawnMock).toHaveBeenCalledTimes(2)
})
2 changes: 1 addition & 1 deletion test/util/install.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ jest.mock('../../src/util/resolve', () => ({
resolve: (name: string) => resolveMock(name),
}))

let spawnMock: (...a: unknown[]) => Promise<void>
let spawnMock: (...a: unknown[]) => Promise<string>
jest.mock('../../src/util/spawn', () => ({
spawn: (...a: unknown[]) => spawnMock(...a),
}))
Expand Down
11 changes: 10 additions & 1 deletion test/util/spawn.ts
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,7 @@ test('defer args and options', () => {

expect(spawnMock).toBeCalledWith('foo', ['bar', 'baz'], {uid: 123456})

return expect(child).resolves.toBe(undefined)
return expect(child).resolves.toBe('')
})

test('reject on spawn error', () => {
Expand Down Expand Up @@ -58,6 +58,15 @@ test('defer stdout to debug', () => {
})
})

test('resolve to stdout', () => {
coreDebug = []
spawnMock = jest.fn(() => realSpawn(process.execPath, ['-e', `process.stdout.write('some output')`]))

const child = spawn('foo', ['bar', 'baz'], { uid: 123456 })

return expect(child).resolves.toBe('some output')
})

test('defer stderr to warning', () => {
coreWarning = []
spawnMock = jest.fn(() => realSpawn(process.execPath, ['-e', `process.stderr.write('foo')`]))
Expand Down
41 changes: 41 additions & 0 deletions test/util/updateTags.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
import { updateTags } from '../../src/util/updateTags'

let spawnMock: (...a: unknown[]) => Promise<string>
jest.mock('../../src/util/spawn', () => ({
spawn: (...a: unknown[]) => spawnMock(...a),
}))

test('set tags', async () => {
spawnMock = jest.fn()

await updateTags('abc', '1.2.3', {major: '1', minor: '2', patch: '3', revision: undefined, revisionType: undefined}, 'version')

expect(spawnMock).toHaveBeenNthCalledWith(1, 'git', ['ls-remote', 'origin', 'refs/tags/version1.3.0'])
expect(spawnMock).toHaveBeenNthCalledWith(2, 'git', ['tag', '-fam', '1.2.3', 'version1.2', 'abc'])
expect(spawnMock).toHaveBeenNthCalledWith(3, 'git', ['tag', '-fam', '1.2.3', 'version1', 'abc'])
expect(spawnMock).toHaveBeenNthCalledWith(4, 'git', ['push', '-f', 'origin', 'refs/tags/version1.2:refs/tags/version1.2', 'refs/tags/version1:refs/tags/version1'])
expect(spawnMock).toHaveBeenCalledTimes(4)
})

test('omit major on maintenance release', async () => {
spawnMock = jest.fn().mockReturnValueOnce(Promise.resolve('abcdef\trefs/tags/v1.3.0'))

await updateTags('abc', '1.2.3', {major: '1', minor: '2', patch: '3', revision: undefined, revisionType: undefined}, 'v')

expect(spawnMock).toHaveBeenNthCalledWith(1, 'git', ['ls-remote', 'origin', 'refs/tags/v1.3.0'])
expect(spawnMock).toHaveBeenNthCalledWith(2, 'git', ['tag', '-fam', '1.2.3', 'v1.2', 'abc'])
expect(spawnMock).toHaveBeenNthCalledWith(3, 'git', ['push', '-f', 'origin', 'refs/tags/v1.2:refs/tags/v1.2'])
expect(spawnMock).toHaveBeenCalledTimes(3)
})

test('set tag for prerelease', async () => {
spawnMock = jest.fn().mockReturnValueOnce(Promise.resolve('abcdef\trefs/tags/v1.3.0'))

await updateTags('abc', '1.2.3-foo.1', {major: '1', minor: '2', patch: '3', revision: 'foo.1', revisionType: 'foo'}, 'v')

expect(spawnMock).toHaveBeenNthCalledWith(1, 'git', ['tag', '-fam', '1.2.3-foo.1', 'v1.2.3-foo', 'abc'])
expect(spawnMock).toHaveBeenNthCalledWith(2, 'git', ['push', '-f', 'origin', 'refs/tags/v1.2.3-foo:refs/tags/v1.2.3-foo'])
expect(spawnMock).toHaveBeenCalledTimes(2)
})


0 comments on commit 245e2d7

Please sign in to comment.