Skip to content

Commit

Permalink
feat: implements the pre-release logic
Browse files Browse the repository at this point in the history
  • Loading branch information
MangelMaxime committed Nov 13, 2024
1 parent e797f3c commit 2e34813
Show file tree
Hide file tree
Showing 13 changed files with 716 additions and 476 deletions.
7 changes: 3 additions & 4 deletions src/Commands/Generate.fs
Original file line number Diff line number Diff line change
Expand Up @@ -13,17 +13,16 @@ type GenerateCommand() =
interface ICommandLimiter<GenerateSettings>

override __.Execute(context, settings) =

printfn "Setting cwd: %s" settings.Cwd

let res =
result {
let! config = ConfigLoader.tryLoadConfig settings.Cwd settings.Config
let! changelogInfo = Changelog.load settings
do! Verify.dirty settings
do! Verify.branch settings
do! Verify.options settings changelogInfo

let commits = ReleaseContext.getCommits settings changelogInfo
let! releaseContext = ReleaseContext.compute settings changelogInfo commits config.CommitParserConfig
let releaseContext = ReleaseContext.compute settings changelogInfo commits config.CommitParserConfig

match releaseContext with
| NoVersionBumpRequired -> Log.success "No version bump required."
Expand Down
1 change: 0 additions & 1 deletion src/EasyBuild.ChangelogGen.fsproj
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,6 @@
<ItemGroup>
<Compile Include="Log.fs" />
<Compile Include="Types.fs" />
<Compile Include="LastVersionFinder.fs" />
<Compile Include="ConfigLoader.fs" />
<Compile Include="Git.fs" />
<Compile Include="Generate/Types.fs" />
Expand Down
42 changes: 27 additions & 15 deletions src/Generate/Changelog.fs
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ open Semver
open EasyBuild.ChangelogGen
open EasyBuild.ChangelogGen.Types
open EasyBuild.ChangelogGen.Generate.Types
open System.Text.RegularExpressions

[<Literal>]
let EMPTY_CHANGELOG =
Expand All @@ -29,6 +30,25 @@ to be able to build your project when working on the first version.
-->
"""

let findVersions (content: string) =

let matches =
Regex.Matches(
content,
"^##\\s\\[?v?(?<version>[\\w\\d.-]+\\.[\\w\\d.-]+[a-zA-Z0-9])\\]?(\\s-\\s(?<date>\\d{4}-\\d{2}-\\d{2}))?$",
RegexOptions.Multiline
)

matches
|> Seq.map (fun m ->
let version = m.Groups.["version"].Value

match SemVersion.TryParse(version, SemVersionStyles.Strict) with
| true, version -> version
| false, _ -> failwith "Invalid version"
)
|> Seq.toList

let load (settings: GenerateSettings) =
let changelogFile = FileInfo(Path.Combine(settings.Cwd, settings.Changelog))

Expand All @@ -38,7 +58,7 @@ let load (settings: GenerateSettings) =
{
File = changelogFile
Content = EMPTY_CHANGELOG
LastVersion = SemVersion(0, 0, 0)
Versions = []
}
|> Ok

Expand All @@ -47,20 +67,12 @@ let load (settings: GenerateSettings) =

let changelogContent = File.ReadAllText(changelogFile.FullName)

let lastVersion =
match LastVersionFinder.tryFindLastVersion changelogContent with
| Ok version -> Ok version.Version
| Error LastVersionFinder.NoVersionFound -> Ok(SemVersion(0, 0, 0))
| Error error -> Error(error.ToText())

lastVersion
|> Result.map (fun version ->
{
File = changelogFile
Content = changelogContent
LastVersion = version
}
)
{
File = changelogFile
Content = changelogContent
Versions = findVersions changelogContent
}
|> Ok

let tryFindAdditionalChangelogContent (text: string) : string list list =
let lines = text.Replace("\r\n", "\n").Split('\n') |> Seq.toList
Expand Down
161 changes: 101 additions & 60 deletions src/Generate/ReleaseContext.fs
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,97 @@ let getCommits (settings: GenerateSettings) (changelog: ChangelogInfo) =

Git.getCommits commitFilter

let computePreReleaseVersion (settings: GenerateSettings) (refVersion: SemVersion) =
// Try to normalize pre-release identifier
let preReleaseIdentifier = settings.PreRelease.Value.Trim('-')

// If previous version is a release, then start a new pre-release from 1 and by incrementing the major version
// Before: 1.0.0 -> After: 2.0.0-beta.1
if refVersion.IsRelease then
refVersion
.WithMajor(refVersion.Major + 1)
.WithMinor(0)
.WithPatch(0)
.WithPrereleaseParsedFrom(preReleaseIdentifier + ".1")
// If the last version is a pre-release of the same identifier, then increment the pre-release number
// Before: 2.0.0-beta.1 -> After: 2.0.0-beta.2
else if refVersion.Prerelease.StartsWith(preReleaseIdentifier) then
let index = refVersion.Prerelease.IndexOf(preReleaseIdentifier + ".")

if index >= 0 then
let preReleaseNumber =
refVersion.Prerelease.Substring(index + preReleaseIdentifier.Length + 1) |> int

refVersion.WithPrereleaseParsedFrom(
preReleaseIdentifier + "." + (preReleaseNumber + 1).ToString()
)
else
// This should never happen
// If the pre-release identifier is present, then the pre-release number should also be present
// If the pre-release identifier is not present, then the version should be a release
// So, this is a safe assumption
failwith "Invalid pre-release identifier"

// Otherwise, start a new pre-release from 1
// This can happens when moving from alpha to beta, for example
// Before: 2.0.0-alpha.1 -> After: 2.0.0-beta.1
else
refVersion.WithPrereleaseParsedFrom(preReleaseIdentifier + ".1")

let computeReleaseVersion
(settings: GenerateSettings)
(commitsForRelease: CommitForRelease list)
(refVersion: SemVersion)
=

let shouldBumpMajor =
commitsForRelease
|> List.exists (fun commit -> commit.SemanticCommit.BreakingChange)

let shouldBumpMinor =
commitsForRelease
|> List.exists (fun commit -> commit.SemanticCommit.Type = "feat")

let shouldBumpPatch =
commitsForRelease
|> List.exists (fun commit -> commit.SemanticCommit.Type = "fix")

let bumpMajor () =
refVersion
.WithMajor(refVersion.Major + 1)
.WithMinor(0)
.WithPatch(0)
.WithoutPrereleaseOrMetadata()
|> Some

let bumpMinor () =
refVersion
.WithMinor(refVersion.Minor + 1)
.WithPatch(0)
.WithoutPrereleaseOrMetadata()
|> Some

let bumpPatch () =
refVersion.WithPatch(refVersion.Patch + 1).WithoutPrereleaseOrMetadata() |> Some

match settings.BumpMajor, settings.BumpMinor, settings.BumpPatch with
| false, false, false ->
if refVersion.IsPrerelease then
refVersion.WithoutPrereleaseOrMetadata() |> Some
elif shouldBumpMajor then
bumpMajor ()
elif shouldBumpMinor then
bumpMinor ()
elif shouldBumpPatch then
bumpPatch ()
else
None

| true, false, false -> bumpMajor ()
| false, true, false -> bumpMinor ()
| false, false, true -> bumpPatch ()
| _ -> failwith "Only one of --major, --minor, or --patch can be used at a time."

let compute
(settings: GenerateSettings)
(changelog: ChangelogInfo)
Expand Down Expand Up @@ -62,76 +153,26 @@ let compute
)
)

let shouldBumpMajor =
commitsForRelease
|> List.exists (fun commit -> commit.SemanticCommit.BreakingChange)

let shouldBumpMinor =
commitsForRelease
|> List.exists (fun commit -> commit.SemanticCommit.Type = "feat")

let shouldBumpPatch =
commitsForRelease
|> List.exists (fun commit -> commit.SemanticCommit.Type = "fix")

let refVersion = changelog.LastVersion

let makeVersionBump newVersion =
let makeBumpInfo newVersion =
{
NewVersion = newVersion
CommitsForRelease = commitsForRelease
LastCommitSha = commitsCandidates[0].Hash
}

let applyPreRelease (newVersion : SemVersion) =
if settings.PreRelease.IsSet then
newVersion.WithPrerelease(settings.PreRelease.Value)
else
newVersion.WithPrerelease("")

let bumpMajor () =
refVersion.WithMajor(refVersion.Major + 1).WithMinor(0).WithPatch(0)
|> applyPreRelease
|> makeVersionBump
|> BumpRequired
|> Ok

let bumpMinor () =
refVersion.WithMinor(refVersion.Minor + 1).WithPatch(0)
|> applyPreRelease
|> makeVersionBump
|> BumpRequired
|> Ok

let bumpPatch () =
refVersion.WithPatch(refVersion.Patch + 1)

|> makeVersionBump
|> BumpRequired
|> Ok

// If the user forced a version, then use that version
match settings.ForceVersion with
| Some version ->
SemVersion.Parse(version, SemVersionStyles.Strict)
|> makeVersionBump
|> makeBumpInfo
|> BumpRequired
|> Ok

| None ->
match settings.BumpMajor, settings.BumpMinor, settings.BumpPatch with
| false, false, false ->
if shouldBumpMajor then
bumpMajor()
elif shouldBumpMinor then
bumpMinor()
elif shouldBumpPatch then
bumpPatch()
else
Ok NoVersionBumpRequired

| true, false, false ->
bumpMajor()
| false, true, false ->
bumpMinor()
| false, false, true ->
bumpPatch()
| _ -> Error "Only one of --major, --minor, or --patch can be used at a time."
if settings.PreRelease.IsSet then
computePreReleaseVersion settings refVersion |> makeBumpInfo |> BumpRequired
else
match computeReleaseVersion settings commitsForRelease refVersion with
| Some newVersion -> makeBumpInfo newVersion |> BumpRequired
| None -> NoVersionBumpRequired
10 changes: 8 additions & 2 deletions src/Generate/Types.fs
Original file line number Diff line number Diff line change
Expand Up @@ -51,8 +51,9 @@ type GenerateSettings() =
member val Tags: string array = [||] with get, set

[<CommandOption("--pre-release [prefix]")>]
[<DefaultValue("beta")>]
[<Description("Indicate that the generated version is a pre-release version. Optionally, you can provide a prefix for the beta version. Default is 'beta'")>]
member val PreRelease: FlagValue<string> = null with get, set
member val PreRelease: FlagValue<string> = FlagValue() with get, set

[<CommandOption("--cwd")>]
[<Description("Path to the directory where the command will be executed. Default is the current working directory")>]
Expand Down Expand Up @@ -99,9 +100,14 @@ type ChangelogInfo =
{
File: FileInfo
Content: string
LastVersion: SemVersion
Versions: SemVersion list
}

member this.LastVersion =
match List.tryHead this.Versions with
| Some version -> version
| None -> SemVersion(0, 0, 0)

member this.Lines = this.Content.Replace("\r\n", "\n").Split('\n')

member this.LastReleaseCommit =
Expand Down
28 changes: 28 additions & 0 deletions src/Generate/Verify.fs
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
module EasyBuild.ChangelogGen.Generate.Verify

open EasyBuild.ChangelogGen.Generate.Types
open FsToolkit.ErrorHandling

let branch (settings: GenerateSettings) =
let currentBranchName = Git.getHeadBranchName ()
Expand All @@ -27,3 +28,30 @@ let dirty (settings: GenerateSettings) =
You can use the --allow-dirty option to allow a dirty repository."""
else
Ok()

let options (settings: GenerateSettings) (changelog : ChangelogInfo) =
let bumpOptions =
match settings.BumpMajor, settings.BumpMinor, settings.BumpPatch with
| true, false, false
| false, true, false
| false, false, true ->
if changelog.LastVersion.IsPrerelease then
Error "Previous version is a pre-release version. Cannot bump major, minor, or patch version."
else
Ok ()
| false, false, false -> Ok ()
| _ -> Error "Only one of --major, --minor, or --patch can be used at a time."

let preReleaseOptions =
if settings.PreRelease.IsSet then
if settings.BumpMajor || settings.BumpMinor || settings.BumpPatch then
Error "Cannot use --pre-release with --major, --minor, or --patch."
else
Ok ()
else
Ok ()

result {
do! bumpOptions
do! preReleaseOptions
}
Loading

0 comments on commit 2e34813

Please sign in to comment.