Skip to content

Commit 41203e7

Browse files
refactor(publish): Improve clarity of tagging/versioning logic (#95)
* Change order of tag/version logic * Rename tag to manualTag * Fix type comments * Log generated version
1 parent 30a3ba4 commit 41203e7

File tree

2 files changed

+72
-79
lines changed

2 files changed

+72
-79
lines changed

src/publish/index.js

Lines changed: 66 additions & 73 deletions
Original file line numberDiff line numberDiff line change
@@ -38,11 +38,14 @@ export const publish = async (options) => {
3838

3939
// Get tags
4040
/** @type {string[]} */
41-
let tags = execSync('git tag').toString().split('\n')
41+
const allTags = execSync('git tag').toString().split('\n')
4242

43-
// Filter tags to our branch/pre-release combo
44-
tags = tags
43+
const filteredTags = allTags
44+
// Ensure tag is valid
4545
.filter((t) => semver.valid(t))
46+
// sort by latest
47+
.sort(semver.compare)
48+
// Filter tags to our branch/pre-release combo
4649
.filter((t) => {
4750
// If this is an older release, filter to only include that version
4851
if (branchConfig.previousVersion) {
@@ -54,29 +57,31 @@ export const publish = async (options) => {
5457
return !isMainBranch
5558
}
5659
})
57-
// sort by latest
58-
.sort(semver.compare)
5960

6061
// Get the latest tag
61-
let latestTag = /** @type {string} */ ([...tags].pop())
62+
let latestTag = filteredTags.at(-1)
6263

6364
let range = `${latestTag}..HEAD`
6465

6566
// If RELEASE_ALL is set via a commit subject or body, all packages will be
6667
// released regardless if they have changed files matching the package srcDir.
6768
let RELEASE_ALL = false
6869

70+
// Validate manual tag
71+
if (tag) {
72+
if (!semver.valid(tag)) {
73+
throw new Error(`tag '${tag}' is not a semantically valid version`)
74+
}
75+
if (!tag.startsWith('v')) {
76+
throw new Error(`tag must start with "v" (e.g. v0.0.0). You supplied ${tag}`)
77+
}
78+
if (allTags.includes(tag)) {
79+
throw new Error(`tag ${tag} has already been released`)
80+
}
81+
}
82+
6983
if (!latestTag || tag) {
7084
if (tag) {
71-
if (!semver.valid(tag)) {
72-
throw new Error(`tag '${tag}' is not a semantically valid version`)
73-
}
74-
if (!tag.startsWith('v')) {
75-
throw new Error(`tag must start with "v" (e.g. v0.0.0). You supplied ${tag}`)
76-
}
77-
if (tags.includes(tag)) {
78-
throw new Error(`tag ${tag} has already been released`)
79-
}
8085
console.info(`Tag is set to ${tag}. This will force release all packages. Publishing...`)
8186
RELEASE_ALL = true
8287

@@ -124,9 +129,7 @@ export const publish = async (options) => {
124129
return !exclude
125130
})
126131

127-
console.info(
128-
`Parsing ${commitsSinceLatestTag.length} commits since ${latestTag}...`,
129-
)
132+
console.info(`Parsing ${commitsSinceLatestTag.length} commits since ${latestTag}...`)
130133

131134
/**
132135
* Parses the commit messsages, log them, and determine the type of release needed
@@ -148,19 +151,58 @@ export const publish = async (options) => {
148151
if (commit.body.includes('BREAKING CHANGE')) {
149152
releaseLevel = Math.max(releaseLevel, 2)
150153
}
151-
if (
152-
commit.subject.includes('RELEASE_ALL')
153-
|| commit.body.includes('RELEASE_ALL')
154-
) {
154+
if (commit.subject.includes('RELEASE_ALL') || commit.body.includes('RELEASE_ALL')) {
155155
RELEASE_ALL = true
156156
}
157157
}
158-
159158
return releaseLevel
160159
},
161160
-1,
162161
)
163162

163+
// If there is a breaking change and no manual tag is set, do not release
164+
if (recommendedReleaseLevel === 2 && !tag) {
165+
throw new Error('Major versions releases must be tagged and released manually.')
166+
}
167+
168+
// If no release is semantically necessary and no manual tag is set, do not release
169+
if (recommendedReleaseLevel === -1 && !tag) {
170+
console.info(`There have been no changes since ${latestTag} that require a new version. You're good!`)
171+
return
172+
}
173+
174+
// If no release is samantically necessary but a manual tag is set, do a patch release
175+
if (recommendedReleaseLevel === -1 && tag) {
176+
recommendedReleaseLevel = 0
177+
}
178+
179+
const releaseType = branchConfig.prerelease
180+
? 'prerelease'
181+
: /** @type {const} */ ({ 0: 'patch', 1: 'minor', 2: 'major' })[
182+
recommendedReleaseLevel
183+
]
184+
185+
if (!releaseType) {
186+
throw new Error(`Invalid release level: ${recommendedReleaseLevel}`)
187+
}
188+
189+
const version = tag
190+
? semver.parse(tag)?.version
191+
: semver.inc(latestTag, releaseType, npmTag)
192+
193+
if (!version) {
194+
throw new Error(
195+
[
196+
'Invalid version increment from semver.inc()',
197+
`- latestTag: ${latestTag}`,
198+
`- recommendedReleaseLevel: ${recommendedReleaseLevel}`,
199+
`- prerelease: ${branchConfig.prerelease}`,
200+
].join('\n'),
201+
)
202+
}
203+
204+
console.log(`Targeting version ${version}...`)
205+
164206
/**
165207
* Uses git diff to determine which files have changed since the latest tag
166208
* @type {string[]}
@@ -187,7 +229,6 @@ export const publish = async (options) => {
187229
// If a package has a dependency that has been updated, we need to update the
188230
// package that depends on it as well.
189231
// run this multiple times so that dependencies of dependencies are also included
190-
// changes to query-core affect query-persist-client-core, which affects react-query-persist-client and then indirectly the sync/async persisters
191232
for (let runs = 0; runs < 3; runs++) {
192233
for (const pkg of packages) {
193234
const packageJson = await readPackageJson(
@@ -207,32 +248,12 @@ export const publish = async (options) => {
207248
)
208249
&& !changedPackages.find((d) => d.name === pkg.name)
209250
) {
210-
console.info(
211-
'adding package dependency',
212-
pkg.name,
213-
'to changed packages',
214-
)
251+
console.info(` Adding dependency ${pkg.name} to changed packages`)
215252
changedPackages.push(pkg)
216253
}
217254
}
218255
}
219256

220-
if (!tag) {
221-
if (recommendedReleaseLevel === 2) {
222-
console.info(
223-
`Major versions releases must be tagged and released manually.`,
224-
)
225-
return
226-
}
227-
228-
if (recommendedReleaseLevel === -1) {
229-
console.info(
230-
`There have been no changes since the release of ${latestTag} that require a new version. You're good!`,
231-
)
232-
return
233-
}
234-
}
235-
236257
const changelogCommitsMd = await Promise.all(
237258
Object.entries(
238259
commitsSinceLatestTag.reduce((prev, curr) => {
@@ -301,34 +322,6 @@ export const publish = async (options) => {
301322
.join('\n\n')
302323
})
303324

304-
if (tag && recommendedReleaseLevel === -1) {
305-
recommendedReleaseLevel = 0
306-
}
307-
308-
const releaseType = branchConfig.prerelease
309-
? 'prerelease'
310-
: /** @type {const} */ ({ 0: 'patch', 1: 'minor', 2: 'major' })[
311-
recommendedReleaseLevel
312-
]
313-
314-
if (!releaseType) {
315-
throw new Error(`Invalid release level: ${recommendedReleaseLevel}`)
316-
}
317-
318-
const version = tag
319-
? semver.parse(tag)?.version
320-
: semver.inc(latestTag, releaseType, npmTag)
321-
322-
if (!version) {
323-
throw new Error(
324-
`Invalid version increment from semver.inc(${[
325-
latestTag,
326-
recommendedReleaseLevel,
327-
branchConfig.prerelease,
328-
].join(', ')}`,
329-
)
330-
}
331-
332325
const date = new Intl.DateTimeFormat(undefined, {
333326
dateStyle: 'short',
334327
timeStyle: 'short',

src/publish/types.d.ts

Lines changed: 6 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -45,16 +45,16 @@ export type BranchConfig = {
4545
}
4646

4747
export type RunOptions = {
48-
// Contains config for publishable branches.
48+
/** Contains config for publishable branches. */
4949
branchConfigs: Record<string, BranchConfig>
50-
// List your npm packages here. The first package will be used as the versioner.
50+
/** List your npm packages here. The first package will be used as the versioner. */
5151
packages: Array<Package>
52-
// Path to root directory of your project.
52+
/** Path to root directory of your project. */
5353
rootDir: string
54-
// The branch to publish. Defaults to the current branch if none supplied.
54+
/** The branch to publish. Defaults to the current branch if none supplied. */
5555
branch?: string
56-
// The tag to publish. Must start with `v`
56+
/** A manual tag to force release. Must start with `v` */
5757
tag?: string
58-
// The GitHub token used to search for user metadata and make a GitHub release.
58+
/** The GitHub token used to search for user metadata and make a GitHub release. */
5959
ghToken?: string
6060
}

0 commit comments

Comments
 (0)