diff --git a/.github/workflows/Sonar.yml b/.github/workflows/Sonar.yml new file mode 100644 index 0000000..9d90b56 --- /dev/null +++ b/.github/workflows/Sonar.yml @@ -0,0 +1,21 @@ +name: Sonar +on: [pull_request, workflow_dispatch] +jobs: + build: + name: Run Sonar Scan + runs-on: ubuntu-latest + permissions: + pull-requests: write + env: + ZCMI_SANDBOX_URL: ${{ secrets.ZCMI_SANDBOX_URL }} + ZCMI_ADMIN_EMAIL: ${{ secrets.ZCMI_ADMIN_EMAIL }} + ZCMI_ADMIN_TOKEN: ${{ secrets.ZCMI_ADMIN_TOKEN }} + SONAR_TOKEN: ${{ secrets.SONAR_TOKEN }} + steps: + - uses: actions/checkout@34e114876b0b11c390a56381ad16ebd13914f8d5 # v4.3.1 + - uses: graalvm/setup-graalvm@54b4f5a65c1a84b2fdfdc2078fe43df32819e4b1 # v1.4.5 + with: + java-version: '21' + distribution: 'graalvm' + - name: call gradle to run tests and then compile native image + run: ./gradlew test sonar diff --git a/.github/workflows/TestAndCompile.yml b/.github/workflows/TestAndCompile.yml new file mode 100644 index 0000000..b8ae694 --- /dev/null +++ b/.github/workflows/TestAndCompile.yml @@ -0,0 +1,41 @@ +name: GraalVM Native Image Test run and build tests +on: [pull_request, workflow_dispatch] +jobs: + build: + name: test and compile on ${{ matrix.os }} + runs-on: ${{ matrix.os }} + permissions: + pull-requests: write + strategy: + matrix: + os: [macos-latest, windows-latest, ubuntu-latest] + env: + ZCMI_SANDBOX_URL: ${{ secrets.ZCMI_SANDBOX_URL }} + ZCMI_ADMIN_EMAIL: ${{ secrets.ZCMI_ADMIN_EMAIL }} + ZCMI_ADMIN_TOKEN: ${{ secrets.ZCMI_ADMIN_TOKEN }} + SONAR_TOKEN: ${{ secrets.SONAR_TOKEN }} + steps: + - uses: actions/checkout@34e114876b0b11c390a56381ad16ebd13914f8d5 # v4.3.1 + - uses: graalvm/setup-graalvm@54b4f5a65c1a84b2fdfdc2078fe43df32819e4b1 # v1.4.5 + with: + java-version: '21' + distribution: 'graalvm' + github-token: ${{ secrets.GITHUB_TOKEN }} + native-image-job-reports: 'true' + native-image-pr-reports: 'true' + - name: call gradle to run tests and then compile (WINDOWS) + if: matrix.os == 'windows-latest' + env: + # This solution found at https://github.com/graalvm/native-build-tools/issues/754#issuecomment-3450913085 + # Needed for Windows; to resolve issue with temp folder during native image build, due to relativization + # problem when different drives are used, such as in GHA runners. + # https://github.com/graalvm/native-build-tools/issues/754 + GRADLE_OPTS: -Djava.io.tmpdir=${{ runner.temp }} + run: ./gradlew test nativeCompile + - name: call gradle to run tests and then compile + run: ./gradlew test nativeCompile + - name: upload binary + uses: actions/upload-artifact@v4 + with: + name: zcmi-${{ matrix.os }} + path: build/native/nativeCompile/zcmi* diff --git a/.gitignore b/.gitignore index 5a03bc3..9e19835 100644 --- a/.gitignore +++ b/.gitignore @@ -13,3 +13,4 @@ out/ .settings .classpath .factorypath +.env \ No newline at end of file diff --git a/build.gradle.kts b/build.gradle.kts index bed81d9..0f61404 100644 --- a/build.gradle.kts +++ b/build.gradle.kts @@ -8,18 +8,13 @@ plugins { id("it.nicolasfarabegoli.conventional-commits") version "3.1.3" id("org.sonarqube") version "latest.release" id("jacoco") - id("org.jetbrains.kotlin.jvm") version "1.9.25" - id("org.jetbrains.kotlin.kapt") version "1.9.25" - id("org.jetbrains.kotlin.plugin.allopen") version "1.9.25" - id("groovy") + id("groovy") id("io.micronaut.application") version "4.6.1" id("com.gradleup.shadow") version "8.3.9" } version = project.properties["zcmiVersion"]!! group = "lol.pbu" - -val kotlinVersion = project.properties["kotlinVersion"]!! val z4jVersion = project.properties["z4jVersion"]!! val jansiGraalVersion = project.properties["jansiGraalVersion"]!! val jansiVersion = project.properties["jansiVersion"]!! @@ -31,49 +26,52 @@ repositories { } dependencies { - kapt("info.picocli:picocli-codegen") - kapt("io.micronaut.serde:micronaut-serde-processor") + annotationProcessor("info.picocli:picocli-codegen") + annotationProcessor("io.micronaut.serde:micronaut-serde-processor") implementation("info.picocli:picocli") implementation("lol.pbu:z4j:${z4jVersion}") - implementation("io.micronaut.kotlin:micronaut-kotlin-runtime") implementation("io.micronaut.picocli:micronaut-picocli") implementation("info.picocli:picocli-jansi-graalvm:${jansiGraalVersion}") implementation("org.fusesource.jansi:jansi:${jansiVersion}") implementation("info.picocli:picocli-shell-jline3:${picoJlineVersion}") implementation("org.jline:jline:${jlineVersion}") implementation("io.micronaut.serde:micronaut-serde-jackson") - implementation("org.jetbrains.kotlin:kotlin-reflect:${kotlinVersion}") - implementation("org.jetbrains.kotlin:kotlin-stdlib-jdk8:${kotlinVersion}") runtimeOnly("ch.qos.logback:logback-classic") - runtimeOnly("com.fasterxml.jackson.module:jackson-module-kotlin") runtimeOnly("org.yaml:snakeyaml") } -tasks.withType { - val props = Properties() - file("gradle.properties").inputStream().use { props.load(it) } - filesMatching("**/application.yml") { - filter(mapOf("tokens" to props), ReplaceTokens::class.java) - } -} application { mainClass = "lol.pbu.ZcmiCommand" } - java { sourceCompatibility = JavaVersion.toVersion("21") + targetCompatibility = JavaVersion.toVersion("21") } -kotlin { - jvmToolchain { - languageVersion.set(JavaLanguageVersion.of(21)) +micronaut { + testRuntime("spock2") + processing { + incremental(true) + annotations("lol.pbu.*") } } -sonarqube { - properties { - property("sonar.tests", "src/test/groovy") +tasks.named("dockerfileNative") { + jdkVersion = "21" +} + +tasks.withType { + val props = Properties() + file("gradle.properties").inputStream().use { props.load(it) } + filesMatching("**/application.yml") { + filter(mapOf("tokens" to props), ReplaceTokens::class.java) + } +} + +tasks.jacocoTestReport { + reports { + xml.required.set(true) } } @@ -88,25 +86,23 @@ tasks.test { finalizedBy(tasks.jacocoTestReport) } -micronaut { - testRuntime("spock2") - processing { - incremental(true) - annotations("lol.pbu.*") - } -} - -tasks.named("dockerfileNative") { - jdkVersion = "21" -} - graalvmNative.binaries { named("main") { imageName.set("zcmi") buildArgs.add("--color=always") buildArgs.add("-march=native") -// buildArgs.add("-0b") +// buildArgs.add("-Ob") } } - +sonar { + properties { + property("sonar.projectKey", "PeanutButter-Unicorn_zcmi") + property("sonar.organization", "peanutbutter-unicorn") + property("sonar.host.url", "https://sonarcloud.io") + property("sonar.coverage.jacoco.xmlReportPaths", "build/reports/jacoco/test/jacocoTestReport.xml") + property("sonar.tests", "src/test/groovy") + property("sonar.junit.reportPaths", "build/reports/jacoco/test") + property("sonar.coverage.jacoco.xmlReportPaths", "build/reports/jacoco/test/jacocoTestReport.xml") + } +} diff --git a/gradle.properties b/gradle.properties index d572275..820deb0 100644 --- a/gradle.properties +++ b/gradle.properties @@ -1,19 +1,8 @@ +org.gradle.console=rich micronautVersion=4.10.7 -kotlin.daemon.jvmargs=--add-opens=jdk.compiler/com.sun.tools.javac.api=ALL-UNNAMED \ - --add-opens=jdk.compiler/com.sun.tools.javac.code=ALL-UNNAMED \ - --add-opens=jdk.compiler/com.sun.tools.javac.comp=ALL-UNNAMED \ - --add-opens=jdk.compiler/com.sun.tools.javac.file=ALL-UNNAMED \ - --add-opens=jdk.compiler/com.sun.tools.javac.jvm=ALL-UNNAMED \ - --add-opens=jdk.compiler/com.sun.tools.javac.main=ALL-UNNAMED \ - --add-opens=jdk.compiler/com.sun.tools.javac.parser=ALL-UNNAMED \ - --add-opens=jdk.compiler/com.sun.tools.javac.processing=ALL-UNNAMED \ - --add-opens=jdk.compiler/com.sun.tools.javac.tree=ALL-UNNAMED \ - --add-opens=jdk.compiler/com.sun.tools.javac.util=ALL-UNNAMED -kotlinVersion=1.9.25 +zcmiVersion=0.0.1 z4jVersion=0.0.8 jansiGraalVersion=1.2.0 jansiVersion=2.4.2 -org.gradle.console=rich picoJlineVersion=4.7.6 -jlineVersion=3.30.5 -zcmiVersion=0.0.1 +jlineVersion=3.30.5 \ No newline at end of file diff --git a/micronaut-cli.yml b/micronaut-cli.yml index 8bab671..615c6fe 100644 --- a/micronaut-cli.yml +++ b/micronaut-cli.yml @@ -1,6 +1,6 @@ applicationType: cli defaultPackage: lol.pbu testFramework: spock -sourceLanguage: kotlin +sourceLanguage: java buildTool: gradle_kotlin -features: [app-name, graalvm, gradle, http-client-test, kapt, kotlin, logback, micronaut-build, picocli, picocli-kotlin-application, picocli-spock, readme, serialization-jackson, shade, spock, yaml, yaml-build] +features: [app-name, gradle, http-client-test, java, logback, micronaut-build, picocli, picocli-java-application, picocli-spock, readme, serialization-jackson, shade, spock, yaml, yaml-build] diff --git a/src/main/java/lol/pbu/BaseCommand.java b/src/main/java/lol/pbu/BaseCommand.java new file mode 100644 index 0000000..9da7613 --- /dev/null +++ b/src/main/java/lol/pbu/BaseCommand.java @@ -0,0 +1,34 @@ +package lol.pbu; + +import io.micronaut.core.annotation.NonNull; +import io.micronaut.core.annotation.ReflectiveAccess; +import picocli.CommandLine.Command; +import picocli.CommandLine.Model.CommandSpec; +import picocli.CommandLine.Spec; + +import java.io.PrintWriter; +import java.util.Optional; + +import static picocli.CommandLine.Help.Ansi.AUTO; + +@Command +public class BaseCommand { + + @Spec + @ReflectiveAccess + protected CommandSpec spec; + + public void err(String message) { + errWriter().ifPresent(writer -> writer.println(AUTO.string("@|bold,red " + message + "|@"))); + } + + @NonNull + public Optional errWriter() { + return getSpec().map(commandSpec -> commandSpec.commandLine().getErr()); + } + + @NonNull + public Optional getSpec() { + return Optional.ofNullable(spec); + } +} diff --git a/src/main/java/lol/pbu/Generated.java b/src/main/java/lol/pbu/Generated.java new file mode 100644 index 0000000..bb6f7fe --- /dev/null +++ b/src/main/java/lol/pbu/Generated.java @@ -0,0 +1,16 @@ +package lol.pbu; + +import java.lang.annotation.Documented; +import java.lang.annotation.Retention; +import java.lang.annotation.Target; + +import static java.lang.annotation.ElementType.CONSTRUCTOR; +import static java.lang.annotation.ElementType.METHOD; +import static java.lang.annotation.ElementType.TYPE; +import static java.lang.annotation.RetentionPolicy.RUNTIME; + +@Documented +@Retention(RUNTIME) +@Target({TYPE, METHOD, CONSTRUCTOR}) +public @interface Generated { +} diff --git a/src/main/java/lol/pbu/ZcmiCommand.java b/src/main/java/lol/pbu/ZcmiCommand.java new file mode 100644 index 0000000..35d2c3f --- /dev/null +++ b/src/main/java/lol/pbu/ZcmiCommand.java @@ -0,0 +1,33 @@ +package lol.pbu; + +import io.micronaut.configuration.picocli.PicocliRunner; + +import io.micronaut.context.annotation.Value; +import picocli.CommandLine; +import picocli.CommandLine.Command; +import picocli.jansi.graalvm.AnsiConsole; + +@Command(name = "zcmi", description = "...", versionProvider = ZcmiCommand.ZcmiVersionProvider.class, mixinStandardHelpOptions = true) +public class ZcmiCommand extends BaseCommand implements Runnable { + + @Generated + public static void main(String[] args) { + try (AnsiConsole ignored = AnsiConsole.windowsInstall()) { + PicocliRunner.run(ZcmiCommand.class, args); + } + } + + public void run() { + err("No Command Given"); + } + + static class ZcmiVersionProvider implements CommandLine.IVersionProvider { + @Value("${micronaut.application.version}") + String version; + + @Override + public String[] getVersion() { + return new String[]{version}; + } + } +} diff --git a/src/main/kotlin/lol/pbu/BaseCommand.kt b/src/main/kotlin/lol/pbu/BaseCommand.kt deleted file mode 100644 index ac16c4c..0000000 --- a/src/main/kotlin/lol/pbu/BaseCommand.kt +++ /dev/null @@ -1,29 +0,0 @@ -package lol.pbu - -import io.micronaut.core.annotation.ReflectiveAccess -import picocli.CommandLine.Command -import picocli.CommandLine.Help.Ansi.AUTO -import picocli.CommandLine.Model.CommandSpec -import picocli.CommandLine.Spec -import java.io.PrintWriter - -@Command -open class BaseCommand { - @Spec - @ReflectiveAccess - protected var spec: CommandSpec? = null - - - fun out(message: String) { - outWriter()?.println(AUTO.string(message)) - } - - fun err(message: String?) { - errWriter()?.println(AUTO.string("@|bold,red | Error|@ $message")) - } - - fun outWriter(): PrintWriter? = spec?.commandLine()?.getOut() - - fun errWriter(): PrintWriter? = spec?.commandLine()?.getErr() - -} \ No newline at end of file diff --git a/src/main/kotlin/lol/pbu/ZcmiCommand.kt b/src/main/kotlin/lol/pbu/ZcmiCommand.kt deleted file mode 100644 index 3b1cc4f..0000000 --- a/src/main/kotlin/lol/pbu/ZcmiCommand.kt +++ /dev/null @@ -1,35 +0,0 @@ -package lol.pbu - -import io.micronaut.configuration.picocli.PicocliRunner -import io.micronaut.context.annotation.Value -import lol.pbu.ZcmiCommand.ZcmiVersionProvider -import picocli.CommandLine -import picocli.CommandLine.Command -import picocli.jansi.graalvm.AnsiConsole - -@Command( - subcommands = [HelpCenterCommand::class], - mixinStandardHelpOptions = true, - name = "Zendesk CLI Management Interface", - versionProvider = ZcmiVersionProvider::class, - description = ["Work seamlessly with Zendesk from the command line."] -) -class ZcmiCommand : BaseCommand(), Runnable { - class ZcmiVersionProvider : CommandLine.IVersionProvider { - @Value("\${micronaut.application.version}") - var zcmiVersionProvider: String? = null - override fun getVersion(): Array { - return arrayOf(zcmiVersionProvider); } - } - - override fun run() { - err("No command specified.") - } - - companion object { - @JvmStatic - fun main(args: Array) { - AnsiConsole.windowsInstall().use { _ -> PicocliRunner.run(ZcmiCommand::class.java, *args) } - } - } -} \ No newline at end of file diff --git a/src/test/groovy/lol/pbu/ZcmiCommandSpec.groovy b/src/test/groovy/lol/pbu/ZcmiCommandSpec.groovy index 87713a6..b78a63c 100644 --- a/src/test/groovy/lol/pbu/ZcmiCommandSpec.groovy +++ b/src/test/groovy/lol/pbu/ZcmiCommandSpec.groovy @@ -19,5 +19,18 @@ class ZcmiCommandSpec extends ZcmiSpec { where: [args, context] << [[['-V'], ['--version']], testContext.getAllContexts()].combinations() } -} + void "invoking with no arguments prints error"(ApplicationContext context) { + when: + def (out, err) = executeCommand(context) + + then: + verifyAll { + out.matches(blank) + err.contains("No Command Given") + } + + where: + context << testContext.getAllContexts() + } +} diff --git a/src/test/groovy/lol/pbu/ZcmiSpec.groovy b/src/test/groovy/lol/pbu/ZcmiSpec.groovy index e7461c3..84eb5f5 100644 --- a/src/test/groovy/lol/pbu/ZcmiSpec.groovy +++ b/src/test/groovy/lol/pbu/ZcmiSpec.groovy @@ -21,6 +21,9 @@ class ZcmiSpec extends Specification { @Shared def gradleProperties + @Shared + String ansi + def setupSpec() { testContext = new ZcmiTestContext( createContext(), @@ -32,7 +35,7 @@ class ZcmiSpec extends Specification { new File('gradle.properties').withInputStream { stream -> gradleProperties.load(stream) } - String ansi = "\\u001B\\[[;\\d]*m" + ansi = "\\u001B\\[[;\\d]*m" blank = Pattern.compile "^\\s*\$" success = Pattern.compile("^${ansi}\\w+${ansi}\\s*\$") } @@ -41,6 +44,10 @@ class ZcmiSpec extends Specification { testContext.close() } + def errorPattern = { String errorMessage -> + return Pattern.quote("$ansi$errorMessage$ansi") + } + /** * Executes a command with the given arguments and a specific application context, * capturing stdout and stderr.