Skip to content
Open
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
32 changes: 27 additions & 5 deletions libs/javalib/src/mill/javalib/SonatypeCentralPublishModule.scala
Original file line number Diff line number Diff line change
Expand Up @@ -59,10 +59,11 @@ trait SonatypeCentralPublishModule extends PublishModule, MavenWorkerSupport {
username: String = defaultCredentials,
password: String = defaultCredentials,
@unroll sources: Boolean = true,
@unroll docs: Boolean = true
@unroll docs: Boolean = true,
@unroll force: Boolean = false
): Task.Command[Unit] = Task.Command {
val artifact = artifactMetadata()
val credentials = getSonatypeCredentials(username, password)()
val credentials = getSonatypeCredentials(username, password, force)()
val publishData = publishArtifactsPayload(sources = sources, docs = docs)()
val publishingType = getPublishingTypeFromReleaseFlag(sonatypeCentralShouldRelease())

Expand Down Expand Up @@ -91,6 +92,17 @@ trait SonatypeCentralPublishModule extends PublishModule, MavenWorkerSupport {
worker = mavenWorker()
)
}

// bin-compat shim
def publishSonatypeCentral(
username: String,
password: String
): Task.Command[Unit] =
publishSonatypeCentral(
username,
password,
force = false
)
}

/**
Expand Down Expand Up @@ -121,12 +133,13 @@ object SonatypeCentralPublishModule extends ExternalModule with DefaultTaskModul
connectTimeout: Int = defaultConnectTimeout,
awaitTimeout: Int = defaultAwaitTimeout,
bundleName: String = "",
@unroll snapshotUri: String = PublishModule.sonatypeCentralSnapshotUri
@unroll snapshotUri: String = PublishModule.sonatypeCentralSnapshotUri,
@unroll force: Boolean = false
): Command[Unit] = Task.Command {
val artifacts = Task.sequence(publishArtifacts.value)()

val finalBundleName = if (bundleName.isEmpty) None else Some(bundleName)
val credentials = getSonatypeCredentials(username, password)()
val credentials = getSonatypeCredentials(username, password, force)()
def makeGpgArgs() = internal.PublishModule.pgpImportSecretIfProvidedAndMakeGpgArgs(
Task.env,
GpgArgs.fromUserProvided(gpgArgs)
Expand Down Expand Up @@ -279,8 +292,17 @@ object SonatypeCentralPublishModule extends ExternalModule with DefaultTaskModul

private def getSonatypeCredentials(
usernameParameterValue: String,
passwordParameterValue: String
passwordParameterValue: String,
force: Boolean
): Task[SonatypeCredentials] = Task.Anon {
val isCI = Task.env.get("CI").nonEmpty
if (!force && isCI && (usernameParameterValue.nonEmpty || passwordParameterValue.nonEmpty))
sys.error(
"--username and --password options forbidden on CI. " +
"Their use might leak secrets. " +
s"Pass those values via environment variables instead ($USERNAME_ENV_VARIABLE_NAME and $PASSWORD_ENV_VARIABLE_NAME), or pass --force alongside them. " +
"You might want to check the output of this job for a leak of those secrets or parts of them."
)
Comment on lines +298 to +305
Copy link
Member

@lefou lefou Aug 22, 2025

Choose a reason for hiding this comment

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

Since we consider this unsafe, we might as well print the same message as warning in all other cases.

Also, instead of sys.error use Task.fail.

val username =
getSonatypeCredential(usernameParameterValue, "username", USERNAME_ENV_VARIABLE_NAME)()
val password =
Expand Down
Loading