diff --git a/README.md b/README.md index adedfd9..1d8f433 100644 --- a/README.md +++ b/README.md @@ -199,10 +199,19 @@ Note, [npm-package-name charset](https://www.npmjs.com/package/validate-npm-pack '2022.6.13-examplecom.v1.0.0.ZXhhbXBsZS5jb20-f1' // date name ver b64 format ``` +Anyway, it's still possible to override the default config by `tagFormat` option: + +| tagFormat | Example | +|-----------|------------------------------------------------------| +| f0 | 2022.6.22-qiwi.pijma-native.v1.0.0-beta.0+foo.bar-f0 | +| f1 | 2022.6.13-examplecom.v1.0.0.ZXhhbXBsZS5jb20-f1 | +| lerna | @qiwi/pijma-ssr@1.1.12 | +| pure | 1.2.3-my.package | + ### Meta -Each release stores its result into the `meta` branch. +Each release pushes its result to the `meta` branch. `2022-6-26-semrel-extra-zxbr-test-c-1-3-1-f0.json` ```json { diff --git a/src/main/js/analyze.js b/src/main/js/analyze.js index be6da87..d66cf2f 100644 --- a/src/main/js/analyze.js +++ b/src/main/js/analyze.js @@ -22,9 +22,15 @@ export const analyze = async (pkg) => { ) pkg.preversion = pre && pkg.version pkg.manifest.version = pkg.version - pkg.tag = releaseType ? formatTag({name: pkg.name, version: pkg.version}) : null + pkg.tag = releaseType ? formatTag({name: pkg.name, version: pkg.version, format: pkg.config.tagFormat}) : null - log({pkg})('semantic changes', changes, 'nextVersion', pkg.version, 'latestVersion', latestVersion) + log({pkg})( + 'semantic changes', changes, + 'releaseType', releaseType, + 'prevVersion', latestVersion, + 'nextVersion', pkg.version, + 'nextTag', pkg.tag + ) } export const releaseSeverityOrder = ['major', 'minor', 'patch'] diff --git a/src/main/js/changelog.js b/src/main/js/changelog.js index 2bc9220..9764ffd 100644 --- a/src/main/js/changelog.js +++ b/src/main/js/changelog.js @@ -22,9 +22,8 @@ export const pushChangelog = queuefy(async (pkg) => { }) export const formatReleaseNotes = async (pkg) => { - const {name, version, absPath: cwd, config: {ghBasicAuth: basicAuth}} = pkg + const {name, version, tag = formatTag({name, version}), absPath: cwd, config: {ghBasicAuth: basicAuth}} = pkg const {repoPublicUrl} = await getRepo(cwd, {basicAuth}) - const tag = formatTag({name, version}) const releaseDiffRef = `## [${name}@${version}](${repoPublicUrl}/compare/${pkg.latest.tag?.ref}...${tag}) (${new Date().toISOString().slice(0, 10)})` const releaseDetails = Object.values(pkg.changes .reduce((acc, {group, subj, short, hash}) => { diff --git a/src/main/js/gh.js b/src/main/js/gh.js index 12f92d6..c3a2ced 100644 --- a/src/main/js/gh.js +++ b/src/main/js/gh.js @@ -14,9 +14,8 @@ export const ghRelease = async (pkg) => { log({pkg})('create gh release') const now = Date.now() - const {name, version, absPath: cwd} = pkg + const {name, version, absPath: cwd, tag = formatTag({name, version})} = pkg const {repoName} = await getRepo(cwd, {basicAuth}) - const tag = formatTag({name, version}) const releaseNotes = await formatReleaseNotes(pkg) const releaseData = JSON.stringify({ name: tag, diff --git a/src/main/js/meta.js b/src/main/js/meta.js index f0927ec..15d6cdc 100644 --- a/src/main/js/meta.js +++ b/src/main/js/meta.js @@ -8,8 +8,7 @@ import {fetchRepo, pushCommit, getTags as getGitTags, pushTag} from './git.js' import {fetchManifest} from './npm.js' export const pushReleaseTag = async (pkg) => { - const {name, version, config: {gitCommitterEmail, gitCommitterName}} = pkg - const tag = formatTag({name, version}) + const {name, version, tag = formatTag({name, version}),config: {gitCommitterEmail, gitCommitterName}} = pkg const cwd = pkg.context.git.root pkg.context.git.tag = tag @@ -21,8 +20,7 @@ export const pushReleaseTag = async (pkg) => { export const pushMeta = queuefy(async (pkg) => { log({pkg})('push artifact to branch \'meta\'') - const {name, version, absPath: cwd, config: {gitCommitterEmail, gitCommitterName, ghBasicAuth: basicAuth}} = pkg - const tag = formatTag({name, version}) + const {name, version, tag = formatTag({name, version}), absPath: cwd, config: {gitCommitterEmail, gitCommitterName, ghBasicAuth: basicAuth}} = pkg const to = '.' const branch = 'meta' const msg = `chore: release meta ${name} ${version}` @@ -53,7 +51,10 @@ export const getLatest = async (pkg) => { } } +const isSafeName = n => /^(@?[a-z0-9-]+\/)?[a-z0-9-]+$/.test(n) + const f0 = { + name: 'f0', parse(tag) { if (!tag.endsWith('-f0')) return null @@ -66,10 +67,10 @@ const f0 = { const date = parseDateTag(_date) const name = _name.includes('.') ? `@${_name.replace('.', '/')}` : _name - return {date, name, version, format: 'f0', ref: tag} + return {date, name, version, format: this.name, ref: tag} }, format({name, date = new Date(), version}) { - if (!/^(@?[a-z0-9-]+\/)?[a-z0-9-]+$/.test(name) || !semver.valid(version)) return null + if (!isSafeName(name) || !semver.valid(version)) return null const d = formatDateTag(date) const n = name.replace('@', '').replace('/', '.') @@ -79,6 +80,7 @@ const f0 = { } const f1 = { + name: 'f1', parse(tag) { if (!tag.endsWith('-f1')) return null @@ -91,7 +93,7 @@ const f1 = { const date = parseDateTag(_date) const name = Buffer.from(b64, 'base64url').toString('utf8') - return {date, name, version, format: 'f1', ref: tag} + return {date, name, version, format: this.name, ref: tag} }, format({name, date = new Date(), version}) { if (!semver.valid(version)) return null @@ -105,35 +107,72 @@ const f1 = { } const lerna = { + name: 'lerna', parse(tag) { const pattern = /^(@?[a-z0-9-]+(?:\/[a-z0-9-]+)?)@(v?\d+\.\d+\.\d+.*)/ const [, name, version] = pattern.exec(tag) || [] if (!semver.valid(version)) return null - return {name, version, format: 'lerna', ref: tag} + return {name, version, format: this.name, ref: tag} }, - // format({name, version}) { - // if (!semver.valid(version)) return null - // - // return `${name}@${version}` - // } + format({name, version}) { + if (!semver.valid(version)) return null + + return `${name}@${version}` + } } -// TODO -// const variants = [f0, f1] -// export const parseTag = (tag) => { -// for (const variant of variants) { -// const parsed = variant.parse(tag) -// if (parsed) return parsed -// } -// -// return null -// } +const pure = { + name: 'pure', + parse(tag) { + if (tag.endsWith('-f0') || tag.endsWith('-f1')) { + return null + } + const parsed = semver.parse(tag) || {} + const {prerelease} = parsed + if (!prerelease?.length) { + return null + } + + const [n, o = ''] = prerelease.reverse() + const name = o === 'x' ? n : `@${o}/${n}` + const version = tag.slice(0, -1 - n.length - (o ? o.length + 1 : 0)) + + return {format: this.name, ref: tag, name, version} + }, + format({name, version}) { + const parsed = semver.parse(version) + if (!parsed || !isSafeName(name)) { + return null + } + const {prerelease} = parsed + const [n, o] = name.slice(name[0] === '@' ? 1 : 0).split('/').reverse() + const extra = prerelease.length + ? '.' + [o || 'x', n].join('.') + : '-' + [o, n].filter(Boolean).join('.') + return version + extra + } +} + +const getFormatter = (tagFormat) => { + if (!tagFormat) { + return { + parse() {}, + format() {} + } + } + const formatter = [f0, f1, pure, lerna].find(f => f.name === tagFormat) + if (!formatter) { + throw new Error(`Unsupported tag format: ${tagFormat}`) + } + + return formatter +} -export const parseTag = (tag) => f0.parse(tag) || f1.parse(tag) || lerna.parse(tag) || null +export const parseTag = (tag) => f0.parse(tag) || f1.parse(tag) || lerna.parse(tag) || pure.parse(tag) || null -export const formatTag = (tag) => f0.format(tag) || f1.format(tag) || null +export const formatTag = (tag, tagFormat = tag.format) => getFormatter(tagFormat).format(tag) || f0.format(tag) || f1.format(tag) || null export const getTags = async (cwd, ref = '') => (await getGitTags(cwd, ref)) diff --git a/src/test/js/meta.test.js b/src/test/js/meta.test.js index 2abea72..0778ecf 100644 --- a/src/test/js/meta.test.js +++ b/src/test/js/meta.test.js @@ -160,18 +160,25 @@ test('formatTag() / parseTag()', () => { version: '1.1.12', format: 'lerna', ref: '@qiwi/pijma-ssr@1.1.12' - }, - true + } + ], + [ + '1.2.3-my.package', + { + name: '@my/package', + version: '1.2.3', + format: 'pure', + ref: '1.2.3-my.package' + } ] ] - cases.forEach(([tag, meta, parseonly]) => { + cases.forEach(([tag, meta]) => { const parsed = parseTag(tag) assert.equal(parsed, meta) - if (meta !== null && !parseonly) { + if (meta !== null) { assert.is(formatTag(meta), tag) - assert.ok(semver.valid(tag)) } }) })