Skip to content
Merged
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
Original file line number Diff line number Diff line change
Expand Up @@ -90,8 +90,7 @@ class CmdModuleInfo extends CmdBase {
throw new AbortOperationException("Incorrect number of arguments")
}

def moduleRef = '@' + args[0]
def reference = ModuleReference.parse(moduleRef)
def reference = ModuleReference.parse(args[0])

// Get config
def baseDir = root ?: Paths.get('.').toAbsolutePath().normalize()
Expand All @@ -117,10 +116,10 @@ class CmdModuleInfo extends CmdBase {
log.warn "Failed to fetch metadata from registry: ${e.message}"
}
if( !release ) {
throw new AbortOperationException("No release information available for ${reference.nameWithoutPrefix}")
throw new AbortOperationException("No release information available for ${reference}")
}
if( !release.metadata ) {
log.info("No metadata found for $reference.nameWithoutPrefix ${release.version ? "($release.version)" : ''}")
log.info("No metadata found for $reference ${release.version ? "($release.version)" : ''}")
}
def moduleUrl = buildModuleUrl(registryConfig.url, reference, release.version)
if( !output || output == 'text' ) {
Expand All @@ -135,7 +134,7 @@ class CmdModuleInfo extends CmdBase {
private void printFormattedInfo(ModuleReference reference, ModuleRelease release, String moduleUrl) {
ModuleMetadata metadata = release.metadata
println ""
println "Module: ${reference.nameWithoutPrefix}"
println "Module: ${reference}"
println "Version: ${release.version}"
println "URL: ${moduleUrl}"
println "Description: ${metadata.description ?: release.description ?: 'N/A'}"
Expand Down Expand Up @@ -218,7 +217,7 @@ class CmdModuleInfo extends CmdBase {

private List<String> generateUsageTemplate(ModuleReference reference, ModuleMetadata metadata) {
def template = new ArrayList<String>()
template.add("nextflow module run ${reference.nameWithoutPrefix}".toString())
template.add("nextflow module run ${reference}".toString())
if( version )
template.add(" -version $version".toString())

Expand Down Expand Up @@ -281,7 +280,7 @@ class CmdModuleInfo extends CmdBase {
private void printJsonInfo(ModuleReference reference, ModuleRelease release, String moduleUrl) {
def metadata = release?.metadata
def info = [
name : reference.nameWithoutPrefix,
name : reference.toString(),
fullName : reference.fullName,
version : release.version,
url : moduleUrl,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -70,9 +70,7 @@ class CmdModuleInstall extends CmdBase {
throw new AbortOperationException("Incorrect number of arguments")
}

def moduleRef = '@' + args[0]

def reference = ModuleReference.parse(moduleRef)
def reference = ModuleReference.parse(args[0])

// Get config
def baseDir = root ?: Paths.get('.').toAbsolutePath().normalize()
Expand All @@ -96,7 +94,7 @@ class CmdModuleInstall extends CmdBase {
def installedVersion = version ?: resolver.resolveVersion(reference)
specFile.addModuleEntry(reference.fullName, installedVersion)

println "Module ${reference.nameWithoutPrefix}@${installedVersion} installed and configured successfully"
println "Module ${reference}@${installedVersion} installed and configured successfully"
}
catch( AbortOperationException e ) {
throw e
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -111,15 +111,15 @@ class CmdModuleList extends CmdBase {

installed.each { module ->
def status = getStatusString(module.integrity)
println "${module.reference.nameWithoutPrefix.padRight(40)}${(module.installedVersion ?: 'unknown').padRight(15)}${status}"
println "${module.reference.toString().padRight(40)}${(module.installedVersion ?: 'unknown').padRight(15)}${status}"
}
println ""
}

private void printJsonList(List<InstalledModule> installed) {
def modules = installed.collect { module ->
[
name : module.reference.nameWithoutPrefix,
name : module.reference.toString(),
version : module.installedVersion ?: 'unknown',
integrity: module.integrity.toString(),
directory: module.directory.toString()
Expand All @@ -136,8 +136,8 @@ class CmdModuleList extends CmdBase {
return 'OK'
case ModuleIntegrity.MODIFIED:
return 'MODIFIED'
case ModuleIntegrity.MISSING_CHECKSUM:
return 'NO CHECKSUM'
case ModuleIntegrity.NO_REMOTE_MODULE:
return 'LOCAL'
case ModuleIntegrity.CORRUPTED:
return 'CORRUPTED'
default:
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,7 @@ import nextflow.cli.CmdBase
import nextflow.config.ConfigBuilder
import nextflow.config.RegistryConfig
import nextflow.exception.AbortOperationException
import nextflow.module.ModuleChecksum
import nextflow.module.ModuleSpec
import nextflow.module.ModuleReference
import nextflow.module.ModuleRegistryClient
Expand Down Expand Up @@ -60,6 +61,9 @@ class CmdModulePublish extends CmdBase {
@TestOnly
protected ModuleRegistryClient client

//Flag if publish is invoked from a scope/name. In this case we should create/update the .module-info with the correct checksum
private boolean useModuleReference = false

@Override
String getName() {
return 'publish'
Expand Down Expand Up @@ -115,14 +119,10 @@ class CmdModulePublish extends CmdBase {

private void publishModule(Path moduleDir, RegistryConfig registryConfig, ModuleSpec manifest){
log.info "Creating module bundle..."
def storage = new ModuleStorage(moduleDir.parent)
def tempBundleFile = Files.createTempFile("nf-module-publish-", ".tar.gz")

try {
storage.createBundle(moduleDir, tempBundleFile)

// Compute bundle checksum
def checksum = storage.computeBundleChecksum(tempBundleFile)
def checksum = ModuleStorage.createBundle(moduleDir, tempBundleFile)
log.info "Bundle checksum: ${checksum}"

// Read bundle content as bytes
Expand All @@ -139,6 +139,14 @@ class CmdModulePublish extends CmdBase {
def registryClient = new ModuleRegistryClient(registryConfig)
def response = registryClient.publishModule(manifest.name, request, registryUrl)

if (useModuleReference) {
// If publish is performed using the module reference we should create/update the .module-info with the correct checksum
try {
ModuleChecksum.save(moduleDir, ModuleChecksum.compute(moduleDir))
}catch (Exception e){
log.warn("Unable to save the checksum - ${e.message}")
}
}
println "✓ Module published successfully!"
println ""
println "Module details:"
Expand Down Expand Up @@ -246,13 +254,13 @@ class CmdModulePublish extends CmdBase {
return Paths.get(module).toAbsolutePath().normalize()
}

final ref = ModuleReference.parse('@' + module)
final ref = ModuleReference.parse(module)
final localStorage = new ModuleStorage(root ?: Paths.get('.').toAbsolutePath().normalize())

if (!localStorage.isInstalled(ref)){
throw new AbortOperationException("No module diretory found for $module")
}

useModuleReference = true
return localStorage.getModuleDir(ref)
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -68,9 +68,7 @@ class CmdModuleRemove extends CmdBase {
throw new AbortOperationException("Cannot use both -keep-config and -keep-files flags together")
}

def moduleRef = '@' + args[0]

def reference = ModuleReference.parse(moduleRef)
def reference = ModuleReference.parse(args[0])

// Get config
def baseDir = root ?: Paths.get('.').toAbsolutePath().normalize()
Expand All @@ -87,15 +85,15 @@ class CmdModuleRemove extends CmdBase {

// Remove local files unless -keep-files is set
if( !keepFiles ) {
println "Removing module files for ${reference.nameWithoutPrefix}..."
println "Removing module files for ${reference}..."
filesRemoved = storage.removeModule(reference)
if( filesRemoved ) {
println "Module files removed successfully"
} else {
println "Module ${reference.nameWithoutPrefix} was not installed locally"
println "Module ${reference} was not installed locally"
}
} else {
println "Keeping module files for ${reference.nameWithoutPrefix} (due to -keep-files flag)"
println "Keeping module files for ${reference} (due to -keep-files flag)"
}

// Remove config entry unless -keep-config is set
Expand All @@ -105,17 +103,17 @@ class CmdModuleRemove extends CmdBase {
if( configRemoved ) {
println "Module entry removed from configuration"
} else {
println "Module ${reference.nameWithoutPrefix} was not configured in nextflow_spec.json"
println "Module ${reference} was not configured in nextflow_spec.json"
}
} else {
println "Keeping module entry in nextflow_spec.json (due to -keep-config flag)"
}

// Summary
if( filesRemoved || configRemoved ) {
println "\nModule ${reference.nameWithoutPrefix} removal completed"
println "\nModule ${reference} removal completed"
} else {
println "\nModule ${reference.nameWithoutPrefix} was not found"
println "\nModule ${reference} was not found"
}
}
catch( AbortOperationException e ) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -61,15 +61,12 @@ class CmdModuleRun extends CmdRun {
throw new AbortOperationException("Arguments not provided")
}

// Parse module reference (first argument starting with @)
String moduleRef = '@' + args[0]

// Parse and validate module reference
ModuleReference reference
try {
reference = ModuleReference.parse(moduleRef)
reference = ModuleReference.parse(args[0])
} catch( Exception e ) {
throw new AbortOperationException("Invalid module reference: ${moduleRef}", e)
throw new AbortOperationException("Invalid module reference: ${args[0]}", e)
}

// Get config
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -67,7 +67,7 @@ class DefaultRemoteModuleResolver implements RemoteModuleResolver {
// Resolve module (will auto-install if missing or version mismatch)
def mainFile = resolver.resolve(reference, null, true)

log.info "Module ${reference.nameWithoutPrefix} resolved to ${mainFile}"
log.debug "Module ${reference} resolved to ${mainFile}"
return mainFile
} catch (Exception e) {
throw new IllegalModulePath(
Expand Down Expand Up @@ -97,4 +97,4 @@ class DefaultRemoteModuleResolver implements RemoteModuleResolver {
}
return new ModulesConfig(modules)
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -39,7 +39,7 @@ class InstalledModule {
Path directory
Path mainFile
Path manifestFile
Path checksumFile
Path moduleInfoFile
String installedVersion
String expectedChecksum

Expand All @@ -54,9 +54,9 @@ class InstalledModule {
return ModuleIntegrity.CORRUPTED
}

// Check if checksum file exists
if( !Files.exists(checksumFile) ) {
return ModuleIntegrity.MISSING_CHECKSUM
// Check if .module-info file exists
if( !Files.exists(moduleInfoFile) ) {
return ModuleIntegrity.NO_REMOTE_MODULE
}

try {
Expand All @@ -71,7 +71,7 @@ class InstalledModule {
return ModuleIntegrity.MODIFIED
}
} catch( Exception e ) {
log.warn "Failed to compute checksum for module ${reference.nameWithoutPrefix}: ${e.message}"
log.warn "Failed to compute checksum for module ${reference}: ${e.message}"
return ModuleIntegrity.CORRUPTED
}
}
Expand All @@ -84,6 +84,6 @@ class InstalledModule {
enum ModuleIntegrity {
VALID, // Checksum matches
MODIFIED, // Checksum mismatch (local changes)
MISSING_CHECKSUM, // No .checksum file
NO_REMOTE_MODULE, // No .module-info file (local-only module, no registry origin)
CORRUPTED // Missing required files
}
Original file line number Diff line number Diff line change
Expand Up @@ -33,7 +33,7 @@ import java.security.MessageDigest
class ModuleChecksum {

public static final String CHECKSUM_ALGORITHM = "SHA-256"
public static final String CHECKSUM_FILE = ".checksum"
public static final String MODULE_INFO_FILE = ".module-info"

/**
* Compute the SHA-256 checksum of a module directory
Expand All @@ -54,7 +54,7 @@ class ModuleChecksum {
try( final walkStream = Files.walk(moduleDir) ) {
walkStream
.filter { Path path -> Files.isRegularFile(path) }
.filter { Path path -> !path.fileName.toString().equals(CHECKSUM_FILE) }
.filter { Path path -> !path.fileName.toString().equals(MODULE_INFO_FILE) }
.sorted()
.each { Path path -> files.add(path) }
}
Expand Down Expand Up @@ -85,28 +85,35 @@ class ModuleChecksum {
}

/**
* Save a checksum to the .checksum file in the module directory
* Save a checksum to the .module-info file in the module directory
*
* @param moduleDir The module directory path
* @param checksum The checksum to save
*/
static void save(Path moduleDir, String checksum) {
def checksumFile = moduleDir.resolve(CHECKSUM_FILE)
Files.writeString(checksumFile, checksum)
def moduleInfoFile = moduleDir.resolve(MODULE_INFO_FILE)
def props = new Properties()
// If file exists loads to update current just checksum property
if( Files.exists( moduleInfoFile))
moduleInfoFile.withInputStream { is -> props.load(is) }
props.setProperty('checksum', checksum)
moduleInfoFile.withOutputStream { os -> props.store(os, null) }
}

/**
* Load a checksum from the .checksum file in the module directory
* Load a checksum from the .module-info file in the module directory
*
* @param moduleDir The module directory path
* @return The checksum, or null if file doesn't exist
*/
static String load(Path moduleDir) {
def checksumFile = moduleDir.resolve(CHECKSUM_FILE)
if( !Files.exists(checksumFile) ) {
def moduleInfoFile = moduleDir.resolve(MODULE_INFO_FILE)
if( !Files.exists(moduleInfoFile) ) {
return null
}
return checksumFile.text
def props = new Properties()
moduleInfoFile.withInputStream { is -> props.load(is) }
return props.getProperty('checksum')
}

/**
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -34,7 +34,7 @@ class ModuleReference {
// Pattern allows: optional @, scope with letters/digits/hyphens/dots/underscores, name segments separated by slashes (no trailing slash)
// Scope: starts with letter/digit, followed by letters/digits/dots/underscores/hyphens
// Name: one or more segments (each starting with letter, followed by letters/digits/underscores/hyphens), separated by slashes
private static final Pattern MODULE_NAME_PATTERN = ~/^@?([a-z0-9][a-z0-9._\-]*)\/([a-z][a-z0-9_\-]*(?:\/[a-z][a-z0-9_\-]*)*)$/
private static final Pattern MODULE_NAME_PATTERN = ~/^([a-z0-9][a-z0-9._\-]*)\/([a-z][a-z0-9._\-]*(?:\/[a-z][a-z0-9._\-]*)*)$/

final String scope
final String name
Expand All @@ -43,7 +43,7 @@ class ModuleReference {
ModuleReference(String scope, String name) {
this.scope = scope
this.name = name
this.fullName = "@${scope}/${name}"
this.fullName = "${scope}/${name}"
}

/**
Expand All @@ -65,23 +65,14 @@ class ModuleReference {
if( !matcher.matches() ) {
throw new AbortOperationException(
"Invalid module reference: '${source}'. " +
"Expected format: [@]scope/name where scope is lowercase alphanumeric with dots/underscores/hyphens " +
"Expected format: scope/name where scope is lowercase alphanumeric with dots/underscores/hyphens " +
"and name is lowercase alphanumeric with underscores/hyphens, optionally with slash-separated segments"
)
}

return new ModuleReference(matcher.group(1), matcher.group(2))
}

/**
* Get the module name without the @ prefix
*
* @return Module name in format "scope/name"
*/
String getNameWithoutPrefix() {
return "${scope}/${name}"
}

@Override
String toString() {
return fullName
Expand Down
Loading
Loading