Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
### Added
- #992: Implement automatic history purge logic
- #973: Enables CORS and JWT configuration for WebApplications in module.xml
- #1095: Add -outdated modifier in list command

### Fixed
- #1001: The `unmap` and `enable` commands will now only activate CPF merge once after all namespaces have been configured instead after every namespace
Expand Down
54 changes: 52 additions & 2 deletions src/cls/IPM/Main.cls
Original file line number Diff line number Diff line change
Expand Up @@ -526,6 +526,9 @@ reinstall -env /path/to/env1.json;/path/to/env2.json example-package
<example description="Shows all installed Python packages.">
list -python
</example>
<example description="Lists all installed modules with available updates in the registry.">
list -outdated
</example>

<!-- Parameters -->
<parameter name="searchString" description="Search string, * can be used." />
Expand All @@ -538,6 +541,7 @@ reinstall -env /path/to/env1.json;/path/to/env2.json example-package
<modifier name="showupstream" aliases="su" description="If specified, show the latest version for each module in configured repos if it's different than the local version." />
<modifier name="repository" aliases="repo" value="true" description="If specified, only show modules installed that belong to the provided repository." />
<modifier name="python" aliases="py" description="If specified, lists installed Embedded Python libraries instead of IPM modules." />
<modifier name="outdated" aliases="o" description="If specified, only lists installed modules that have available updates in the registry. Modules installed as dependencies are skipped." />
</command>

<command name="list-dependents" aliases="dependents">
Expand Down Expand Up @@ -2757,6 +2761,12 @@ ClassMethod ListInstalled(ByRef pCommandInfo) [ Private ]
do ..DisplayModules(.list,,,, .tModifiers)
quit
}
if $data(pCommandInfo("modifiers","outdated")) {
merge tModifiers = pCommandInfo("modifiers")
do ..GetOutdatedModulesList(.list)
Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It would be nice for the list to have column headers to display

do ..DisplayModules(.list,,,, .tModifiers)
quit
}
if (''$data(pCommandInfo("modifiers","tree"))) {
// Show tree of dependencies as well.
// Modules that are dependencies for no other are shown at the top level.
Expand Down Expand Up @@ -2821,8 +2831,7 @@ ClassMethod GetListModule(
{
new $namespace
set $namespace=ns
set tRes = ##class(%SQL.Statement).%ExecDirect(,
"select * from %IPM_Storage.ModuleItem")
set tRes = ##class(%SQL.Statement).%ExecDirect(, "select * from %IPM_Storage.ModuleItem")
$$$ThrowSQLIfError(tRes.%SQLCODE,tRes.%Message)
set in=""
while tRes.%Next(.tSC) {
Expand Down Expand Up @@ -4171,6 +4180,47 @@ ClassMethod Update(ByRef pCommandInfo)
}
}

ClassMethod GetOutdatedModulesList(Output list)
{
// Get the dependencies list to prevent displaying an outdated list
set depRes = ##class(%SQL.Statement).%ExecDirect(,
"select ModuleItem->Name ModName,Dependencies_Name DepName,Dependencies_VersionString DepVer "_
"from %IPM_Storage.ModuleItem_Dependencies")
$$$ThrowSQLIfError(depRes.%SQLCODE, depRes.%Message)
while depRes.%Next(.sc) {
$$$ThrowOnError(sc)
set visitedMap($zconvert(depRes.%Get("DepName"),"l")) = 1
}
$$$ThrowOnError(sc)

do ..GetListModules($namespace, , .installedModules)
do ..GetUpstreamPackageVersions(.serverModuleVersions)

set width = 0
for i=1:1:installedModules {
set moduleInfo = installedModules(i)
set moduleName = $listget(moduleInfo)

// Skip processing if the module is a dependency module
if moduleName="zpm"||($data(visitedMap(moduleName))) {
Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Skipping of dependencies should be an optional flag. The default should be to list all outdated modules in the namespace.

continue
}

set currentVersion = $listget(moduleInfo,2)
if $data(serverModuleVersions(moduleName)) {
set reg = $order(serverModuleVersions(moduleName,""),1,versionString)
if currentVersion'=versionString {
set currentwidth = $length(moduleName)
set list($increment(list)) = $listbuild(moduleName,$$$FormattedLine($$$Red, currentVersion)_" "_$$$FormattedLine($$$Green,versionString))
Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Similar to npm outdated, it would be great to show both the highest version available on the current major version and the highest version available overall.

if width<currentwidth {
set width = currentwidth
}
}
}
}
set list("width") = width
}

ClassMethod GetPythonInstalledLibs(Output list)
{
set target = ##class(%File).NormalizeDirectory("python", $system.Util.ManagerDirectory())
Expand Down
39 changes: 39 additions & 0 deletions tests/unit_tests/Test/PM/Unit/CLI.cls
Original file line number Diff line number Diff line change
Expand Up @@ -352,4 +352,43 @@ Method TestUninstallWithoutModuleName()
do $$$AssertNotTrue(exists, "Module removed successfully.")
}

/// Validates that the '-outdated' modifier correctly identifies and displays modules with newer registry versions
Method TestListOutdatedModules()
{
set moduleName = "irisjwt"
Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Should probably add a test with more than one module and one with dependencies.

if $isobject(##class(%IPM.Storage.Module).NameOpen(moduleName)) {
do $$$LogMessage(moduleName_" already exist. So uninstalling the module")
do ..RunCommand("uninstall "_moduleName)
}

set status = ..RunCommand("install "_moduleName_" 1.0.0")
do $$$AssertStatusOK(status, moduleName_" installed at version 1.0.0")
do ##class(%IPM.Main).GetOutdatedModulesList(.list)

set found = 0
for i=1:1:list {
set moduleInfo = list(i)
set currentModuleName = $listget(moduleInfo, 1)
set versionString = $listget(moduleInfo, 2)

// Scrub ANSI color codes
set matcher = ##class(%Regex.Matcher).%New("\x1b\[[0-9;]*m", versionString)
set cleanString = matcher.ReplaceAll("")

set cleanString = $zstrip(cleanString, "<>W")
set currentVersion = $piece(cleanString, " ", 1)
set serverVersion = $piece(cleanString, " ", 2)
if (moduleName = currentModuleName) {
set found = 1
}
if currentVersion'=serverVersion {
do $$$AssertNotEquals(currentVersion, serverVersion, "Version mismatch detected for "_moduleName)
do $$$LogMessage(currentVersion_" "_serverVersion)
do $$$LogMessage("Currently installed "_currentModuleName_" (local:"_currentVersion_") has a newer version available (server:"_serverVersion_")")
}
}
do $$$AssertTrue(found, "The module "_moduleName_" was found in the outdated list")
do ..RunCommand("list -o")
}

}