Skip to content

Commit

Permalink
Merge pull request #710 from intersystems/v0.10.x-update-module-version
Browse files Browse the repository at this point in the history
Add a `module-version` command that updates the version of a module
isc-shuliu authored Jan 30, 2025

Unverified

This commit is not signed, but one or more authors requires that any commit attributed to them is signed.
2 parents 83c3bc3 + 87d304f commit 50d3a01
Showing 6 changed files with 338 additions and 1 deletion.
3 changes: 2 additions & 1 deletion CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -14,9 +14,10 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
- #582 Added functionality to optionally see time of last update and server version of each package
- #609 Added support for `-export-deps` when running the "Package" phase of lifecycle
- #541 Added support for ORAS repository
- #704 Added support for passing in env files via `-env /path/to/env1.json;/path/to/env2.json` syntax
- #702 Added a new lifecycle phase `Initialize` which is used for preload
- #702 Added a `<CPF/>` resource, which can be used for CPF merge before/after a specified lifecycle phase or in a custom lifecycle phase.
- #704 Added support for passing in env files via `-env /path/to/env1.json;/path/to/env2.json` syntax
- #710 Added support for `module-version` command which updates the version of a module
- #716 Added support to publish under external name by passing `-use-external-name` or `-use-ext`.

### Changed
55 changes: 55 additions & 0 deletions src/cls/IPM/General/SemanticVersion.cls
Original file line number Diff line number Diff line change
@@ -206,6 +206,61 @@ Method Satisfies(pExpression As %IPM.General.SemanticVersionExpression) As %Bool
Quit pExpression.IsSatisfiedBy($this)
}

Method WithBumpMajor() As %IPM.General.SemanticVersion
{
Set tVer = ..%New()
Set tVer.Major = ..Major + 1
Set tVer.Minor = 0
Set tVer.Patch = 0
Set tVer.Prerelease = ""
Set tVer.Build = ""
Quit tVer
}

Method WithBumpMinor() As %IPM.General.SemanticVersion
{
Set tVer = ..%New()
Set tVer.Major = ..Major
Set tVer.Minor = ..Minor + 1
Set tVer.Patch = 0
Set tVer.Prerelease = ""
Set tVer.Build = ""
Quit tVer
}

Method WithBumpPatch() As %IPM.General.SemanticVersion
{
Set tVer = ..%New()
Set tVer.Major = ..Major
Set tVer.Minor = ..Minor
Set tVer.Patch = ..Patch + 1
Set tVer.Prerelease = ""
Set tVer.Build = ""
Quit tVer
}

Method WithPrerelease(pPrerelease As %String) As %IPM.General.SemanticVersion
{
Set tVer = ..%New()
Set tVer.Major = ..Major
Set tVer.Minor = ..Minor
Set tVer.Patch = ..Patch
Set tVer.Prerelease = pPrerelease
Set tVer.Build = ..Build
Quit tVer
}

Method WithBuild(pBuild As %String) As %IPM.General.SemanticVersion
{
Set tVer = ..%New()
Set tVer.Major = ..Major
Set tVer.Minor = ..Minor
Set tVer.Patch = ..Patch
Set tVer.Prerelease = ..Prerelease
Set tVer.Build = pBuild
Quit tVer
}

Storage Default
{
<Data name="SemanticVersionState">
118 changes: 118 additions & 0 deletions src/cls/IPM/Main.cls
Original file line number Diff line number Diff line change
@@ -724,6 +724,32 @@ generate /my/path -export 00000,PacketName2,IgnorePacket2^00000,PacketName3,Igno
</example>
</command>

<command name="module-version" aliases="modver">
<description>
Updates the version of the module in the current namespace.
</description>
<parameter name="module" required="true" description="Name of the module to update the version for." />
<parameter name="version" description="Either a valid SemVer string, or 'major', 'minor', 'patch'. If it's 'major', 'minor', or 'patch', will bump the cooresponding part of SemVer." />

<modifier name="prerelease" value="true" aliases="p" description="Set the prerelease to a specific value" />
<modifier name="build" value="true" aliases="b" description="Set the build to a specific value" />
<modifier name="force" description="Permit the version to be downgraded. By default, trying to downgrade the version will fail" />
<modifier name="quiet" aliases="q" description="Suppress output (except for errors)" />

<example description="Sets the version of the module HS.JSON to 1.2.3">
module-version HS.JSON 1.2.3
</example>
<example description="Bump the major version the module HS.JSON. If current version is 1.2.3, the new version will be 2.0.0">
module-version HS.JSON major
</example>
<example description="Bump the minor version and set prerelease to 'alpha' and build to 'xyz'. If current version is 1.2.3, the new version will be 1.3.0-alpha+xyz">
module-version HS.JSON minor -prerelease alpha -build xyz
</example>
<example description="Downgrade the version to 1.2.3. If the current version is 1.3.0, this will still work because the force flag is set.">
module-version HS.JSON 1.2.3 -force
</example>
</command>

</commands>
}

@@ -905,6 +931,8 @@ ClassMethod ShellInternal(pCommand As %String, Output pException As %Exception.A
Do ..EnableIPM(.tCommandInfo)
} ElseIf (tCommandInfo = "unmap") {
Do ..UnmapIPM(.tCommandInfo)
} ElseIf (tCommandInfo = "module-version") {
Do ..ModuleVersion(.tCommandInfo)
}
} Catch pException {
If (pException.Code = $$$ERCTRLC) {
@@ -3352,6 +3380,96 @@ ClassMethod UnmapIPM(ByRef pCommandInfo)
}
}

ClassMethod ModuleVersion(ByRef pCommandInfo)
{
Set modName = $Get(pCommandInfo("parameters","module"))
Set quiet = $$$HasModifier(pCommandInfo,"quiet")
Set force = $$$HasModifier(pCommandInfo,"force")
If (modName = "") {
$$$ThrowOnError($$$ERROR($$$GeneralError,"No module name specified."))
}
Set mod = ##class(%IPM.Storage.Module).NameOpen(modName,.sc)
If mod = "" {
$$$ThrowOnError($$$ERROR($$$GeneralError,"Module "_modName_" not found."))
}
Set stream = ##class(%IPM.StudioDocument.ModuleStream).NameOpen(modName)
If stream = "" {
$$$ThrowOnError($$$ERROR($$$GeneralError,"Module stream "_modName_" not found."))
}
If 'quiet {
Write !,$$$FormatText("Current version of module %1 is: %2", modName, mod.VersionString)
}

// Compute the new semver
Set versionString = $Get(pCommandInfo("parameters","version"))
If versionString = "major" {
Set semver = mod.Version.WithBumpMajor()
} ElseIf versionString = "minor" {
Set semver = mod.Version.WithBumpMinor()
} ElseIf versionString = "patch" {
Set semver = mod.Version.WithBumpPatch()
} ElseIf versionString = "" {
Set semver = ##class(%IPM.General.SemanticVersion).FromString(mod.Version.ToString())
} Else {
Set semver = ##class(%IPM.General.SemanticVersion).FromString(versionString)
}

// Validate the semver
If 'semver.%ValidateObject() {
$$$ThrowOnError($$$ERROR($$$GeneralError,"Invalid version string: "_versionString))
}

If $$$HasModifier(pCommandInfo,"prerelease") {
Set semver = semver.WithPrerelease($$$GetModifier(pCommandInfo,"prerelease"))
}
If $$$HasModifier(pCommandInfo,"build") {
Set semver = semver.WithBuild($$$GetModifier(pCommandInfo,"build"))
}

// Ensure semver is not lower than the current version, unless force is set
If ('force) && ('semver.Follows(mod.Version)) {
Set msg = $$$FormatText("Cannot set version to %1 as it is not strictly higher than the current version %2 (Use -force to override)", semver.ToString(), mod.VersionString)
Set msg = msg _ $Char(10, 13) _ "Note: when either version has a nonempty prerelease value, and differ from the other by major.minor.patch, neither version is considered strictly higher than the other."
Set msg = msg _ $Char(10, 13) _ " See https://github.com/npm/node-semver?tab=readme-ov-file#prerelease-tags for more information."
$$$ThrowOnError($$$ERROR($$$GeneralError, msg))
}
If (semver.ToString() = mod.Version.ToString()) {
If 'quiet {
Write !,"Version is unchanged - Skipping."
}
Return
}

If 'quiet {
Write !,$$$FormatText("Setting version of module %1 to: %2", modName, semver.ToString())
}

Set initTLevel = $TLevel
Set ex = $$$NULLOREF
TSTART
Try {
New %SourceControl
Set docName = modName_".ZPM"
Do ##class(%Studio.SourceControl.Interface).SourceControlCreate()
If $IsObject($Get(%SourceControl)) {
Write !,"Checking out "_docName
$$$ThrowOnError(%SourceControl.CheckOut(docName))
}
Set mod.Version = semver
Set mod.VersionString = semver.ToString()
$$$ThrowOnError(mod.%Save())
Do ##class(%IPM.StudioDocument.Module).UpdateVersion(modName,semver)
TCOMMIT
} Catch ex {
}
While ($TLevel > initTLevel) {
TROLLBACK 1
}
If $IsObject(ex) {
Throw ex
}
}

/// Get the mapping relationship for %IPM package. Format:
/// <ul>
/// <li>pIsMappedFrom(destinationNamespace) = sourceNamespace</li>
59 changes: 59 additions & 0 deletions src/cls/IPM/StudioDocument/Module.cls
Original file line number Diff line number Diff line change
@@ -344,6 +344,65 @@ Method ImportFromXML(stream As %RegisteredObject, flags As %String) As %Status
Quit $$$OK
}

/// XSLT to replace the top-level module version with a new value
XData UpdateVersionTransform [ XMLNamespace = "http://www.intersystems.com/studio/document" ]
{
<?xml version="1.0" encoding="UTF-8"?>
<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
<xsl:param name="replacement" select="'replacementValue'" />

<xsl:template match="@*|node()">
<xsl:copy>
<xsl:apply-templates select="@*|node()" />
</xsl:copy>
</xsl:template>

<xsl:template match="Module/Version">
<xsl:copy>
<xsl:value-of select="$replacement" />
</xsl:copy>
</xsl:template>

</xsl:stylesheet>
}

/// Returns a compiled XSLT to update a module's version to the specified <var>semver</var> in its XML representation
ClassMethod CompiledUpdateVersionXSLT(semver As %IPM.General.SemanticVersion) As %XML.XSLT.CompiledStyleSheet
{
Set tXData = ##class(%Dictionary.CompiledXData).IDKEYOpen($ClassName(), "UpdateVersionTransform",,.tSC)
$$$ThrowOnError(tSC)
Set s = ""
While 'tXData.Data.AtEnd {
Set s = s _ tXData.Data.Read()
}
Set s = $REPLACE(s, "replacementValue", semver.ToString())
Set stream = ##class(%Stream.TmpCharacter).%New()
$$$ThrowOnError(stream.Write(s))
$$$ThrowOnError(stream.Rewind())
Set tSC = ##class(%XML.XSLT.CompiledStyleSheet).CreateFromStream(stream, .compiledStyleSheet)
$$$ThrowOnError(tSC)

Quit compiledStyleSheet
}

/// Updates the version of <var>moduleName</var> to <var>semver</var>
ClassMethod UpdateVersion(moduleName As %String, semver As %IPM.General.SemanticVersion)
{
// Can't use %IPM.Repo.XSLTProvider because we need to pass a parameter
Set compiledTransform = ..CompiledUpdateVersionXSLT(semver)
Set document = ##class(%Library.RoutineMgr).%OpenId(moduleName_".ZPM")
If '$IsObject(document) {
// %Library.RoutineMgr:%OpenId doesn't have a byref status arg.
$$$ThrowStatus($Get(%objlasterror,$$$ERROR($$$GeneralError,"Unknown error occurred.")))
}

$$$ThrowOnError(document.Code.Rewind())
$$$ThrowOnError(##class(%XML.XSLT.Transformer).TransformStreamWithCompiledXSL(document.Code, compiledTransform, .updatedContents))
$$$ThrowOnError(document.Code.Clear())
$$$ThrowOnError(document.Code.CopyFrom(updatedContents))
$$$ThrowOnError(document.%Save())
}

XData ExternalXSL
{
<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
93 changes: 93 additions & 0 deletions tests/integration_tests/Test/PM/Integration/ModuleVersion.cls
Original file line number Diff line number Diff line change
@@ -0,0 +1,93 @@
Class Test.PM.Integration.ModuleVersion Extends Test.PM.Integration.Base
{

Parameter OriginalVersion = "0.0.2-snapshot+build";

XData AllCases [ MimeType = application/json ]
{
[
{
"args": "major -prerelease alpha -force",
"expected": "1.0.0-alpha"
},
{
"args": "minor",
"expected": "0.1.0"
},
{
"args": "patch",
"expected": "0.0.3"
},
{
"args": "-prerelease alpha -build mybuild -force",
"expected": "0.0.2-alpha+mybuild"
},
{
"args": "3.4.5-abc+xyz",
"expected": ""
},
{
"args": "3.4.5-abc+xyz -force",
"expected": "3.4.5-abc+xyz"
},
{
"args": "invalid string",
"expected": ""
},
{
"args": "0.0.1",
"expected": ""
},
{
"args": "0.0.1 -force",
"expected": "0.0.1"
},
{
"args": "0.0.2",
"expected": "0.0.2"
}
]
}

Method TestModuleVersion()
{
Set xdataID="Test.PM.Integration.ModuleVersion||AllCases"
Set compiledXdata=##class(%Dictionary.CompiledXData).%OpenId(xdataID)
Set stream=compiledXdata.Data
Do $$$AssertTrue($IsObject(stream))

Set allTestCases = [].%FromJSON(stream)
Set iter = allTestCases.%GetIterator()
While iter.%GetNext(.key, .case) {
Do $$$LogMessage("Running test case: "_case.%ToJSON())

Set args = case.args
Set expected = case.expected

Set modDir = ..GetModuleDir("module-version")
Set sc = ##class(%IPM.Main).Shell("load " _ modDir)
Do $$$AssertStatusOK(sc, "Loaded module successfully")

Set sc = ##class(%IPM.Main).Shell("modver mv " _ args)
If expected = "" {
Do $$$AssertStatusNotOK(sc, "Module-version failed as expected")
Continue
}
Do $$$AssertStatusOK(sc, "Module-version executed successfully")

Kill mod
Set mod = ##class(%IPM.Storage.Module).NameOpen("mv")
Do mod.%Reload()
Do $$$AssertEquals(mod.VersionString, expected)
Do $$$AssertEquals(mod.Version.ToString(), expected)

Kill stream
Set stream = ##class(%IPM.StudioDocument.ModuleStream).NameOpen("mv")
Do stream.%Reload()
Set content = stream.Contents.Read()
Do $$$AssertTrue(content [ ("<Version>"_expected_"</Version>"))
Do $$$AssertNotTrue(content [ ("<Version>"_..#OriginalVersion_"</Version>"))
}
}

}
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
<?xml version="1.0" encoding="UTF-8"?>
<Export generator="Cache" version="25">
<Document name="mv.ZPM">
<Module>
<Name>mv</Name>
<Version>0.0.2-snapshot+build</Version>
<Packaging>module</Packaging>
<SystemRequirements/>
</Module>
</Document>
</Export>

0 comments on commit 50d3a01

Please sign in to comment.