diff --git a/.gitea/workflows/main.yaml b/.gitea/workflows/main.yaml deleted file mode 100644 index dd78fce..0000000 --- a/.gitea/workflows/main.yaml +++ /dev/null @@ -1,36 +0,0 @@ -on: - push: - branches: - - main - -env: - GIT__TOKEN: ${{ secrets.TOKEN }} - -jobs: - publish: - runs-on: linux - steps: - - uses: actions/checkout@v4 - - - uses: actions/cache/restore@v4 - with: - key: gradle - path: | - ~/.gradle/caches/ - ~/.gradle/wrapper/ - ~/.konan/ - - - uses: actions/setup-java@v4 - with: - java-version: '24' - distribution: 'temurin' - - - run: ./gradlew publish - - - uses: actions/cache/save@v4 - with: - key: gradle - path: | - ~/.gradle/caches/ - ~/.gradle/wrapper/ - ~/.konan/ diff --git a/.gitea/workflows/publish.yaml b/.gitea/workflows/publish.yaml new file mode 100644 index 0000000..dad1306 --- /dev/null +++ b/.gitea/workflows/publish.yaml @@ -0,0 +1,47 @@ +on: + push: + branches: [ main ] + +jobs: + publish: + runs-on: linux + steps: + - name: Checkout + uses: actions/checkout@v4 + + - name: Setup Java + uses: actions/setup-java@v4 + with: + java-version: '24' + distribution: 'temurin' + + - name: Cache Gradle + uses: actions/cache@v4 + with: + key: gradle-${{ hashFiles('**/libs.versions.toml') }} + restore-keys: gradle + path: | + ~/.gradle/caches/ + ~/.gradle/wrapper/ + + - name: Cache Konan + uses: actions/cache@v4 + with: + key: konan-${{ hashFiles('**/libs.versions.toml') }} + restore-keys: konan + path: | + ~/.konan/ + + - name: Cache Node + uses: actions/cache@v4 + with: + key: node-${{ hashFiles('**/libs.versions.toml') }} + restore-keys: node + path: | + ~/.gradle/nodejs + ~/.gradle/yarn + + - name: Publish + run: ./gradlew publish + env: + INFENDRO__TOKEN: ${{ secrets.TOKEN }} diff --git a/README.md b/README.md index 5342a15..72154c0 100644 --- a/README.md +++ b/README.md @@ -1,14 +1,6 @@ -# Kotlin Shell +# shell-kt -This library enables the execution of shell commands in Kotlin Multiplatform. - -## Features - -* Execute commands on the shell. -* Multiplatform support - * JVM - * JavaScript - * Native (Linux) +`shell-kt` is a Kotlin Multiplatform library that enables the execution of shell commands. ## Installation @@ -20,21 +12,15 @@ repositories { } dependencies { - implementation("com.infendro:shell:1.0.0") + implementation("com.infendro:shell:1.1.0") } ``` ## Usage ```kotlin -import com.infendro.shell.Shell - -fun main() { - // the command to execute - val command = arrayOf("echo", "Hello World!") - - // execute the command - val result = Shell.run(command) +suspend fun main() { + val result = execute("echo", "Hello World!") println(result.code) // 0 println(result.value) // Hello World! diff --git a/build.gradle.kts b/build.gradle.kts index 17c2c1b..ce92cbb 100644 --- a/build.gradle.kts +++ b/build.gradle.kts @@ -1,54 +1,39 @@ -group = "com.infendro" -version = "1.0.0" +import com.infendro.plugin.infendro +import com.infendro.plugin.token -repositories { - mavenCentral() -} +group = "com.infendro" +version = "1.1.0" plugins { + alias(libs.plugins.infendro) alias(libs.plugins.multiplatform) id("maven-publish") } kotlin { jvm() - js { - nodejs() - } linuxX64() + linuxArm64() + mingwX64() + macosX64() + macosArm64() + js { nodejs() } + + repositories { + mavenCentral() + } sourceSets { jsMain.dependencies { implementation(libs.node) } } -} -publishing { - repositories { - maven { - url = uri("https://git.infendro.com/api/packages/Infendro/maven") - authentication.create("header", HttpHeaderAuthentication::class) - credentials(HttpHeaderCredentials::class) { - val token = secret("git.token") ?: throw GradleException() - - name = "Authorization" - value = "token $token" - } - } + compilerOptions { + freeCompilerArgs.add("-opt-in=kotlinx.cinterop.ExperimentalForeignApi") } } -fun secret( - key: String, -): String? { - val property = key - val environment = key - .uppercase() - .replace(".", "__") - - val prop = properties[property] as String? - val env = System.getenv(environment) - - return prop ?: env +publishing.repositories { + infendro(token) } diff --git a/gradle/libs.versions.toml b/gradle/libs.versions.toml index 6e75867..717ea07 100644 --- a/gradle/libs.versions.toml +++ b/gradle/libs.versions.toml @@ -1,8 +1,10 @@ [versions] -kotlin = "2.1.20" -node = "2025.6.9-22.13.10" +infendro = "1.1.0" +kotlin = "2.3.0" +node = "2026.2.11-24.10.4" [plugins] +infendro = { id = "com.infendro.plugin", version.ref = "infendro" } multiplatform = { id = "org.jetbrains.kotlin.multiplatform", version.ref = "kotlin" } [libraries] diff --git a/settings.gradle.kts b/settings.gradle.kts index f4f9dde..4d0e67e 100644 --- a/settings.gradle.kts +++ b/settings.gradle.kts @@ -1 +1,6 @@ rootProject.name = "shell" + +pluginManagement.repositories { + maven("https://git.infendro.com/api/packages/Infendro/maven") + mavenCentral() +} diff --git a/src/commonMain/kotlin/com/infendro/shell/Shell.kt b/src/commonMain/kotlin/com/infendro/shell/Shell.kt index 5c5badf..6c99885 100644 --- a/src/commonMain/kotlin/com/infendro/shell/Shell.kt +++ b/src/commonMain/kotlin/com/infendro/shell/Shell.kt @@ -1,20 +1,20 @@ package com.infendro.shell -object Shell { - suspend fun run( - command: Array, - directory: String? = null, - ): Result { - return execute(command, directory) - } +data class Result( + val code: Int, + val value: String, +) { + val success: Boolean + get() = code == 0 - class Result( - val code: Int, - val value: String, - ) + internal companion object { + fun from(code: Int, value: String) = Result(code, value) + + fun from(code: Int, success: () -> String, error: () -> String) = when (code) { + 0 -> Result(code, success()) + else -> Result(code, error()) + } + } } -internal expect suspend fun execute( - command: Array, - directory: String?, -): Shell.Result +expect suspend fun execute(vararg command: String, directory: String? = null): Result diff --git a/src/jsMain/kotlin/com/infendro/shell/Shell.js.kt b/src/jsMain/kotlin/com/infendro/shell/Shell.js.kt index 290477b..9ad0fc9 100644 --- a/src/jsMain/kotlin/com/infendro/shell/Shell.js.kt +++ b/src/jsMain/kotlin/com/infendro/shell/Shell.js.kt @@ -1,19 +1,17 @@ package com.infendro.shell -import kotlin.coroutines.resume -import kotlin.coroutines.suspendCoroutine import node.childProcess.ExecOptions import node.childProcess.exec +import kotlin.coroutines.resume +import kotlin.coroutines.suspendCoroutine -internal actual suspend fun execute( - command: Array, - directory: String?, -): Shell.Result = suspendCoroutine { continuation -> - val command = command.joinToString(" ") { "\"$it\"" } - val options = ExecOptions.invoke(cwd = directory) +actual suspend fun execute(vararg command: String, directory: String?): Result = suspendCoroutine { continuation -> + val command = command.joinToString(" ") { """"$it"""" } + val options = ExecOptions(cwd = directory) exec(command, options) { error, stdout, stderr -> - val code = if (error == null) 0 else error.code!!.toInt() - val value = if (error == null) stdout else stderr - continuation.resume(Shell.Result(code, value)) + val code = error?.code?.toInt() ?: 0 + + val result = Result.from(code, { stdout as String }, { stderr as String }) + continuation.resume(result) } } diff --git a/src/jvmMain/kotlin/com/infendro/shell/Shell.jvm.kt b/src/jvmMain/kotlin/com/infendro/shell/Shell.jvm.kt index c713ebc..d74f7de 100644 --- a/src/jvmMain/kotlin/com/infendro/shell/Shell.jvm.kt +++ b/src/jvmMain/kotlin/com/infendro/shell/Shell.jvm.kt @@ -1,21 +1,18 @@ package com.infendro.shell import java.io.File +import kotlin.coroutines.resume +import kotlin.coroutines.suspendCoroutine -internal actual suspend fun execute( - command: Array, - directory: String?, -): Shell.Result { - val process = ProcessBuilder(*command) - .also { - if (directory != null) { - it.directory(File(directory)) - } +actual suspend fun execute(vararg command: String, directory: String?): Result = suspendCoroutine { continuation -> + val process = ProcessBuilder(*command).also { + if (directory != null) { + val dir = File(directory) + it.directory(dir) } - .start() + }.start() val code = process.waitFor() - val value = process - .let { if (code == 0) it.inputReader() else it.errorReader() } - .readText() - return Shell.Result(code, value) + + val result = Result.from(code, { process.inputReader().readText() }, { process.errorReader().readText() }) + continuation.resume(result) } diff --git a/src/nativeMain/kotlin/com/infendro/shell/Shell.native.kt b/src/nativeMain/kotlin/com/infendro/shell/Shell.native.kt index 315cf82..f05bb42 100644 --- a/src/nativeMain/kotlin/com/infendro/shell/Shell.native.kt +++ b/src/nativeMain/kotlin/com/infendro/shell/Shell.native.kt @@ -1,33 +1,30 @@ package com.infendro.shell import kotlinx.cinterop.CPointer -import kotlinx.cinterop.ExperimentalForeignApi import kotlinx.cinterop.refTo import kotlinx.cinterop.toKString import platform.posix.* +import kotlin.coroutines.resume +import kotlin.coroutines.suspendCoroutine -@ExperimentalForeignApi -internal actual suspend fun execute( - command: Array, - directory: String?, -): Shell.Result { - val command = command.joinToString(" ") { "\"$it\"" } +actual suspend fun execute(vararg command: String, directory: String?): Result = suspendCoroutine { continuation -> + val command = command.joinToString(" ") { """"$it"""" } if (directory != null) { chdir(directory) } val stream = popen(command, "r")!! val value = stream.readLines().joinToString("") val code = pclose(stream) - return Shell.Result(code, value) + + val result = Result.from(code, value) + continuation.resume(result) } -@ExperimentalForeignApi private fun CPointer.readLines(): List { val lines = mutableListOf() val buffer = ByteArray(4096) while (true) { - val line = fgets(buffer.refTo(0), buffer.size, this) - if (line == null) break + val line = fgets(buffer.refTo(0), buffer.size, this) ?: break lines.add(line.toKString()) } return lines