diff --git a/packages/kilo-jetbrains/AGENTS.md b/packages/kilo-jetbrains/AGENTS.md index 7d7b12d3bf..797a62741d 100644 --- a/packages/kilo-jetbrains/AGENTS.md +++ b/packages/kilo-jetbrains/AGENTS.md @@ -67,8 +67,9 @@ ## Build -- **Full build**: `bun run build` from `packages/kilo-jetbrains/` (builds CLI + Gradle plugin). -- **Gradle only**: `./gradlew buildPlugin` from `packages/kilo-jetbrains/` (requires CLI binaries already present). +- **Typecheck**: `bun run typecheck` or `./gradlew typecheck` from `packages/kilo-jetbrains/` — compiles all Kotlin sources including the generated API client. Does NOT require CLI binaries. +- **Full build**: `bun run build` from `packages/kilo-jetbrains/` (prepares CLI binaries + runs Gradle `buildPlugin`). +- **Gradle only**: `./gradlew buildPlugin` from `packages/kilo-jetbrains/` (requires CLI binaries already present in `backend/build/generated/cli/`; run `bun run build --prepare-cli` first). - **Via Turbo**: `bun turbo build --filter=@kilocode/kilo-jetbrains` from repo root. - **Run in sandbox**: `./gradlew runIde` — launches sandboxed IntelliJ with the plugin. Does NOT build CLI binaries. diff --git a/packages/kilo-jetbrains/backend/build.gradle.kts b/packages/kilo-jetbrains/backend/build.gradle.kts index 9baa58b410..3b0bce6c07 100644 --- a/packages/kilo-jetbrains/backend/build.gradle.kts +++ b/packages/kilo-jetbrains/backend/build.gradle.kts @@ -88,37 +88,12 @@ val requiredPlatforms = listOf( "windows-arm64", ) -val localCli by tasks.registering(PrepareLocalCliTask::class) { - description = "Prepare local CLI binary for JetBrains dev" - val os = providers.systemProperty("os.name").map { - val name = it.lowercase() - if (name.contains("mac")) return@map "darwin" - if (name.contains("win")) return@map "windows" - if (name.contains("linux")) return@map "linux" - throw GradleException("Unsupported host OS: $it") - } - val arch = providers.systemProperty("os.arch").map { - val name = it.lowercase() - if (name == "aarch64" || name == "arm64") return@map "arm64" - if (name == "x86_64" || name == "amd64") return@map "x64" - throw GradleException("Unsupported host arch: $it") - } - script.set(rootProject.layout.projectDirectory.file("script/build.ts")) - root.set(rootProject.layout.projectDirectory) - out.set(cliDir) - platform.set(os.zip(arch) { a, b -> "$a-$b" }) - exe.set(platform.map { if (it.startsWith("windows")) "kilo.exe" else "kilo" }) -} - val prod = production val checkCli by tasks.registering(CheckCliTask::class) { - description = "Verify CLI binaries exist before building" + description = "Verify CLI binaries exist before packaging" dir.set(cliDir) this.production.set(prod) platforms.set(requiredPlatforms) - if (!prod.get()) { - dependsOn(localCli) - } } tasks.processResources { diff --git a/packages/kilo-jetbrains/build-tasks/src/main/kotlin/CheckCliTask.kt b/packages/kilo-jetbrains/build-tasks/src/main/kotlin/CheckCliTask.kt index c6f58ed97e..dffe15e516 100644 --- a/packages/kilo-jetbrains/build-tasks/src/main/kotlin/CheckCliTask.kt +++ b/packages/kilo-jetbrains/build-tasks/src/main/kotlin/CheckCliTask.kt @@ -5,6 +5,7 @@ import org.gradle.api.provider.ListProperty import org.gradle.api.provider.Property import org.gradle.api.tasks.Input import org.gradle.api.tasks.InputDirectory +import org.gradle.api.tasks.Optional import org.gradle.api.tasks.PathSensitive import org.gradle.api.tasks.PathSensitivity import org.gradle.api.tasks.TaskAction @@ -14,9 +15,14 @@ import java.io.File * Verify that CLI binaries exist before packaging the plugin. * In production mode, all platform binaries must be present. * In dev mode, only the current platform binary is required. + * + * CLI binaries must be prepared separately before packaging: + * Local: bun run build (from packages/kilo-jetbrains/) + * Production: bun run build:production */ abstract class CheckCliTask : DefaultTask() { @get:InputDirectory + @get:Optional @get:PathSensitive(PathSensitivity.RELATIVE) abstract val dir: DirectoryProperty diff --git a/packages/kilo-jetbrains/build-tasks/src/main/kotlin/PrepareLocalCliTask.kt b/packages/kilo-jetbrains/build-tasks/src/main/kotlin/PrepareLocalCliTask.kt deleted file mode 100644 index 121b64fa06..0000000000 --- a/packages/kilo-jetbrains/build-tasks/src/main/kotlin/PrepareLocalCliTask.kt +++ /dev/null @@ -1,74 +0,0 @@ -import org.gradle.api.DefaultTask -import org.gradle.api.file.DirectoryProperty -import org.gradle.api.file.RegularFileProperty -import org.gradle.api.provider.Property -import org.gradle.api.tasks.Input -import org.gradle.api.tasks.InputFile -import org.gradle.api.tasks.Internal -import org.gradle.api.tasks.OutputDirectory -import org.gradle.api.tasks.TaskAction -import org.gradle.process.ExecOperations -import java.io.File -import javax.inject.Inject - -abstract class PrepareLocalCliTask : DefaultTask() { - @get:InputFile - abstract val script: RegularFileProperty - - @get:Internal - abstract val root: DirectoryProperty - - @get:OutputDirectory - abstract val out: DirectoryProperty - - @get:Input - abstract val platform: Property - - @get:Input - abstract val exe: Property - - @get:Inject - abstract val exec: ExecOperations - - @TaskAction - fun run() { - val bin = out.file("${platform.get()}/${exe.get()}").get().asFile - if (bin.exists()) return - exec.exec { - workingDir = root.get().asFile - commandLine(findBun(), "script/build.ts", "--prepare-cli") - } - } - - /** - * Resolve the absolute path to `bun`. The Gradle daemon's PATH is often - * stripped down and doesn't include Homebrew or user-local bin dirs. - * Probe common install locations so the build works without manual PATH setup. - */ - private fun findBun(): String { - // 1. Already on PATH? - val which = runCatching { - ProcessBuilder("which", "bun") - .redirectErrorStream(true) - .start() - .inputStream.bufferedReader().readLine()?.trim() - }.getOrNull() - if (which != null && File(which).isFile) return which - - // 2. Common install locations - val home = System.getProperty("user.home") - val candidates = listOf( - "$home/.bun/bin/bun", - "/opt/homebrew/bin/bun", - "/usr/local/bin/bun", - "$home/.nvm/current/bin/bun", - ) - for (path in candidates) { - val f = File(path) - if (f.isFile && f.canExecute()) return f.absolutePath - } - - // 3. Fall back — let the OS resolve it (will fail with a clear message) - return "bun" - } -} diff --git a/packages/kilo-jetbrains/build.gradle.kts b/packages/kilo-jetbrains/build.gradle.kts index 975f360bae..a7307adb57 100644 --- a/packages/kilo-jetbrains/build.gradle.kts +++ b/packages/kilo-jetbrains/build.gradle.kts @@ -127,6 +127,18 @@ tasks { } } +// Compile-only typecheck: verifies Kotlin compiles (including generated API client) +// without running processResources, CLI binary prep, or buildPlugin. +tasks.register("typecheck") { + dependsOn( + ":shared:compileKotlin", + ":frontend:compileKotlin", + ":backend:compileKotlin", + ":frontend:compileTestKotlin", + ":backend:compileTestKotlin", + ) +} + tasks.named("runIde") { dependsOn(":backend:processResources") jvmArgumentProviders += CommandLineArgumentProvider { diff --git a/packages/kilo-jetbrains/package.json b/packages/kilo-jetbrains/package.json index 6710c3d254..58a20c3394 100644 --- a/packages/kilo-jetbrains/package.json +++ b/packages/kilo-jetbrains/package.json @@ -4,7 +4,7 @@ "scripts": { "build": "bun script/build.ts", "build:production": "bun script/build.ts --production", - "typecheck": "bun run build", + "typecheck": "./gradlew typecheck", "test": "./gradlew test", "test:ci": "bun script/test-ci.ts" } diff --git a/packages/kilo-jetbrains/script/test-ci.ts b/packages/kilo-jetbrains/script/test-ci.ts index 73618a5314..f5c3cdf8d7 100644 --- a/packages/kilo-jetbrains/script/test-ci.ts +++ b/packages/kilo-jetbrains/script/test-ci.ts @@ -7,8 +7,8 @@ * then collects per-module JUnit XML results into .artifacts/unit/junit.xml * so mikepenz/action-junit-report can find them at the standard path. * - * Always exits 0 — test failures are reported via the JUnit uploader, - * not by failing the CI job itself. + * Exits with the Gradle exit code after writing the aggregate report so that + * test failures fail the CI job. */ import { $ } from "bun" @@ -16,8 +16,9 @@ import { join } from "node:path" import { mkdirSync, readdirSync, readFileSync, writeFileSync, existsSync } from "node:fs" const root = join(import.meta.dir, "..") +const gradlew = process.platform === "win32" ? "gradlew.bat" : "./gradlew" -await $`./gradlew test --continue`.cwd(root).nothrow() +const result = await $`${gradlew} test --continue`.cwd(root).nothrow() const modules = [".", "shared", "frontend", "backend"] const suites: string[] = [] @@ -27,7 +28,11 @@ for (const mod of modules) { if (!existsSync(dir)) continue for (const f of readdirSync(dir)) { if (!f.endsWith(".xml")) continue - suites.push(readFileSync(join(dir, f), "utf8")) + // Strip leading XML declaration so it does not appear as a nested + // declaration inside the wrapper, which would produce + // malformed XML and fail the JUnit report uploader. + const xml = readFileSync(join(dir, f), "utf8").replace(/^\s*<\?xml[^>]*\?>\s*/u, "") + suites.push(xml) } } @@ -36,3 +41,5 @@ mkdirSync(join(root, ".artifacts", "unit"), { recursive: true }) writeFileSync(out, `\n\n${suites.join("\n")}\n\n`) console.log(`[jetbrains-test] collected ${suites.length} suite(s) -> ${out}`) + +process.exit(result.exitCode) diff --git a/turbo.json b/turbo.json index 70e6eb7c06..cbf33fd23a 100644 --- a/turbo.json +++ b/turbo.json @@ -10,6 +10,10 @@ "dependsOn": [], "outputs": ["dist/**"] }, + "@kilocode/cli#build": { + "dependsOn": ["@kilocode/sdk#build"], + "outputs": ["dist/**"] + }, "@kilocode/cli#test": { "dependsOn": ["^build"], "outputs": [], @@ -30,7 +34,6 @@ "outputs": ["build/distributions/**"] }, "@kilocode/kilo-jetbrains#typecheck": { - "dependsOn": ["@kilocode/cli#build"], "outputs": [] }, "@kilocode/kilo-jetbrains#test:ci": {