Repository: apollographql/apollo-kotlin
Pull request #4825 (2023-04-03 09:13:01)

Use Kotlin 1.8.20

netlify[bot]: ### <span aria-hidden="true">👷</span> Deploy Preview for *apollo-android-docs* processing.


|  Name | Link |
|:-:|------------------------|
|<span aria-hidden="true">🔨</span> Latest commit | 2dee7c993e53b0183600f7928dc8876dfcd9ecf9 |
|<span aria-hidden="true">🔍</span> Latest deploy log | https://app.netlify.com/sites/apollo-android-docs/deploys/642a989fcfa6400008fb0ea3 |

Patch:
diff --git a/.github/workflows/publish-ij-plugin-snapshot.yml b/.github/workflows/publish-ij-plugin-snapshot.yml
index 8e52f91b66..0d198cc83a 100644
--- a/.github/workflows/publish-ij-plugin-snapshot.yml
+++ b/.github/workflows/publish-ij-plugin-snapshot.yml
@@ -25,6 +25,7 @@ jobs:
           ./scripts/increment-ij-plugin-snapshot.main.kts
         env:
           COM_APOLLOGRAPHQL_IJ_PLUGIN_SNAPSHOT: true
+          GH_TOKEN: ${{ github.token }}
 
       - name: Publish snapshot to Repsy
         run: ./gradlew --no-build-cache :intellij-plugin:publishAllPublicationsToRepsyIjPluginSnapshots
diff --git a/CHANGELOG.md b/CHANGELOG.md
index d6d49e567b..80b6ef5e81 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -1,6 +1,155 @@
 Change Log
 ==========
 
+# Version 3.8.0
+
+_2023-03-28_
+
+This release adds two new artifacts that contain Jetpack compose extensions amongst other fixes.
+
+## 💙️ External contributors
+
+Many thanks to @slinstacart and @hbmartin for their contributions to this release!
+
+## ✨ [New] Jetpack compose extension (#4802)
+
+You can now use the `apollo-compose-support` artifact: 
+
+```kotlin
+// build.gradle.kts
+dependencies {
+  implementation("com.apollographql.apollo3:apollo-compose-support")
+}
+```
+
+This artifact contains the `toState()` and `watchAsState()` extensions:
+
+```kotlin
+/**
+ * A stateful composable that retrieves your data
+ */
+@OptIn(ApolloExperimental::class)
+@Composable
+fun LaunchDetails(launchId: String) {
+    val response by apolloClient.query(LaunchDetailsQuery(launchId)).toState()
+    val r = response
+    when {
+        r == null -> Loading() // no response yet
+        r.exception != null -> ErrorMessage("Oh no... A network error happened: ${r.exception!!.message}")
+        r.hasErrors() -> ErrorMessage("Oh no... A GraphQL error happened ${r.errors[0].message}.")
+        else -> LaunchDetails(r.data!!, navigateToLogin)
+    }
+}
+
+/**
+ * A stateless composable that displays your data
+ */
+@Composable
+private fun LaunchDetails(
+        data: LaunchDetailsQuery.Data,
+) {
+  // Your UI code goes here
+}
+```
+
+If you are working with paginated data, you can also add `apollo-compose-paging-support` to your dependencies:
+
+```kotlin
+// build.gradle.kts
+dependencies {
+  implementation("com.apollographql.apollo3:apollo-compose-paging-support")
+}
+```
+
+This artifact contains a helper function to create `androidx.pagin.Pager` instances ([androix documentation](https://developer.android.com/reference/androidx/paging/Pager)):
+
+```kotlin
+@OptIn(ApolloExperimental::class)
+@Composable
+fun LaunchList(onLaunchClick: (launchId: String) -> Unit) {
+  val lazyPagingItems = rememberAndCollectPager<LaunchListQuery.Data, LaunchListQuery.Launch>(
+          config = PagingConfig(pageSize = 10),
+          appendCall = { response, loadSize ->
+            if (response?.data?.launches?.hasMore == false) {
+              // No more pages
+              null
+            } else {
+              // Compute the next call from the current response
+              apolloClient.query(
+                      LaunchListQuery(
+                              cursor = Optional.present(response?.data?.launches?.cursor),
+                              pageSize = Optional.present(loadSize)
+                      )
+              )
+            }
+          },
+          getItems = { response ->
+            // Compute the items to be added to the page from the current response
+            if (response.hasErrors()) {
+              Result.failure(ApolloException(response.errors!![0].message))
+            } else {
+              Result.success(response.data!!.launches.launches.filterNotNull())
+            }
+          },
+  )
+  
+  // Use your paging items:
+  if (lazyPagingItems.loadState.refresh is LoadState.Loading) {
+    Loading()
+  } else {
+    LazyColumn {
+      items(lazyPagingItems) { launch ->
+        // Your UI code goes here
+      }
+      item {
+        when (val append = lazyPagingItems.loadState.append) {
+          is LoadState.Error -> // Add error indicator here 
+          LoadState.Loading -> // Add loading indicator here
+        }
+      }
+    }
+  }
+}
+```
+
+As always, feedback is very welcome. Let us know what you think of the feature by
+either [opening an issue on our GitHub repo](https://github.com/apollographql/apollo-android/issues)
+, [joining the community](http://community.apollographql.com/new-topic?category=Help&tags=mobile,client)
+or [stopping by our channel in the KotlinLang Slack](https://app.slack.com/client/T09229ZC6/C01A6KM1SBZ)(get your
+invite [here](https://slack.kotl.in/)).
+
+## ✨ [New] Gradle plugin: run codegen after gradle sync
+
+If you import a new project or run a Gradle sync, your GraphQL models are now automatically generated so that the IDE can find the symbols and your files do not show red underlines. This takes into account Gradle up-to-date checks and it should be pretty fast. If you want to opt-out, you can do so with `generateSourcesDuringGradleSync.set(false)`:
+
+```kotlin
+apollo {
+  // Enable automatic generation of models during Gradle sync (default)
+  generateSourcesDuringGradleSync.set(true)
+
+  // Or disable automatic generation of models to save on your Gradle sync times
+  generateSourcesDuringGradleSync.set(false)
+
+  service("api") {
+    // Your  GraphQL configuration
+  }
+}
+```
+
+## 👷‍ All changes
+
+* Allow to add HTTP headers on top of ApolloClient ones (#4754)
+* Kotlin 1.8 (#4776)
+* Move cache creation outside the main thread (#4781)
+* Cache: ignore hardcoded @include(if: false) directives (#4795)
+* Add % to reserved characters to encode in URL (#4804)
+* First drop of experimental Compose support libraries (#4783)
+* Consider variable default values with @skip/@include/@defer (#4785)
+* Gradle plugin: run codegen after gradle sync (#4796)
+* Allow custom SqlDriver (#4806)
+* Multipart subscriptions (#4768, #4807, #4738)
+* GQLNode.print for type extensions (#4814)
+
 # Version 3.7.5
 
 _2023-03-14_
diff --git a/docs/source/essentials/custom-scalars.mdx b/docs/source/essentials/custom-scalars.mdx
index ac4ceb96cc..15492e71f9 100644
--- a/docs/source/essentials/custom-scalars.mdx
+++ b/docs/source/essentials/custom-scalars.mdx
@@ -138,6 +138,8 @@ In addition, the `com.apollographql.apollo3:apollo-adapters` artifact provides t
 | `com.apollographql.apollo3.adapter.JavaLocalDateAdapter`        | For `java.time.LocalDate` ISO8601 dates                                                             |
 | `com.apollographql.apollo3.adapter.KotlinxLocalDateTimeAdapter` | For `kotlinx.datetime.LocalDateTime` ISO8601 dates                                                  |
 | `com.apollographql.apollo3.adapter.JavaLocalDateTimeAdapter`    | For `java.time.LocalDateTime` ISO8601 dates                                                         |
+| `com.apollographql.apollo3.adapter.KotlinxLocalTimeAdapter`     | For `kotlinx.datetime.LocalTime` ISO8601 dates                                                      |
+| `com.apollographql.apollo3.adapter.JavaLocalTimeAdapter`        | For `java.time.LocalTime` ISO8601 dates                                                         |
 | `com.apollographql.apollo3.adapter.JavaOffsetDateTimeAdapter`   | For `java.time.OffsetDateTime` ISO8601 dates                                                        |
 | `com.apollographql.apollo3.adapter.DateAdapter`                 | For `java.util.Date` ISO8601 dates                                                                  |
 | `com.apollographql.apollo3.adapter.BigDecimalAdapter`           | For a Multiplatform `com.apollographql.apollo3.adapter.BigDecimal` class holding big decimal values |
diff --git a/gradle/libraries.toml b/gradle/libraries.toml
index ed7809711e..275b9d711b 100644
--- a/gradle/libraries.toml
+++ b/gradle/libraries.toml
@@ -20,13 +20,13 @@ javaPoet = "1.13.0"
 jetbrains-annotations = "23.0.0"
 junit = "4.13.2"
 kotlin-plugin-min = "1.6.0"
-kotlin-plugin = "1.8.20-RC2"
-kotlin-plugin-max = "1.8.20-RC2"
+kotlin-plugin = "1.8.20"
+kotlin-plugin-max = "1.8.20"
 kotlin-stdlib = "1.6.21"
 kotlinx-coroutines = "1.6.4"
 kotlinx-datetime = "0.4.0"
 kotlinx-serialization-runtime = "1.4.1"
-ksp = "1.8.20-RC-1.0.9"
+ksp = "1.8.20-1.0.10"
 ktor = "2.2.2"
 okhttp = "4.9.3"
 rx-android = "2.0.1"
diff --git a/libraries/apollo-adapters/README.md b/libraries/apollo-adapters/README.md
index 63ed566a47..251463099e 100644
--- a/libraries/apollo-adapters/README.md
+++ b/libraries/apollo-adapters/README.md
@@ -10,6 +10,8 @@ A few convenient adapters:
 | `com.apollographql.apollo3.adapter.JavaLocalDateAdapter`        | For `java.time.LocalDate` ISO8601 dates                                                             |
 | `com.apollographql.apollo3.adapter.KotlinxLocalDateTimeAdapter` | For `kotlinx.datetime.LocalDateTime` ISO8601 dates                                                  |
 | `com.apollographql.apollo3.adapter.JavaLocalDateTimeAdapter`    | For `java.time.LocalDateTime` ISO8601 dates                                                         |
+| `com.apollographql.apollo3.adapter.KotlinxLocalTimeAdapter`     | For `kotlinx.datetime.LocalTime` ISO8601 dates                                                      |
+| `com.apollographql.apollo3.adapter.JavaLocalTimeAdapter`        | For `java.time.LocalTime` ISO8601 dates                                                             |
 | `com.apollographql.apollo3.adapter.JavaOffsetDateTimeAdapter`   | For `java.time.OffsetDateTime` ISO8601 dates                                                        |
 | `com.apollographql.apollo3.adapter.DateAdapter`                 | For `java.util.Date` ISO8601 dates                                                                  |
 | `com.apollographql.apollo3.adapter.BigDecimalAdapter`           | For a Multiplatform `com.apollographql.apollo3.adapter.BigDecimal` class holding big decimal values |
diff --git a/libraries/apollo-adapters/api/apollo-adapters.api b/libraries/apollo-adapters/api/apollo-adapters.api
index 1326ce7181..897d551f07 100644
--- a/libraries/apollo-adapters/api/apollo-adapters.api
+++ b/libraries/apollo-adapters/api/apollo-adapters.api
@@ -42,6 +42,14 @@ public final class com/apollographql/apollo3/adapter/JavaLocalDateTimeAdapter :
 	public fun toJson (Lcom/apollographql/apollo3/api/json/JsonWriter;Lcom/apollographql/apollo3/api/CustomScalarAdapters;Ljava/time/LocalDateTime;)V
 }
 
+public final class com/apollographql/apollo3/adapter/JavaLocalTimeAdapter : com/apollographql/apollo3/api/Adapter {
+	public static final field INSTANCE Lcom/apollographql/apollo3/adapter/JavaLocalTimeAdapter;
+	public synthetic fun fromJson (Lcom/apollographql/apollo3/api/json/JsonReader;Lcom/apollographql/apollo3/api/CustomScalarAdapters;)Ljava/lang/Object;
+	public fun fromJson (Lcom/apollographql/apollo3/api/json/JsonReader;Lcom/apollographql/apollo3/api/CustomScalarAdapters;)Ljava/time/LocalTime;
+	public synthetic fun toJson (Lcom/apollographql/apollo3/api/json/JsonWriter;Lcom/apollographql/apollo3/api/CustomScalarAdapters;Ljava/lang/Object;)V
+	public fun toJson (Lcom/apollographql/apollo3/api/json/JsonWriter;Lcom/apollographql/apollo3/api/CustomScalarAdapters;Ljava/time/LocalTime;)V
+}
+
 public final class com/apollographql/apollo3/adapter/JavaOffsetDateTimeAdapter : com/apollographql/apollo3/api/Adapter {
 	public static final field INSTANCE Lcom/apollographql/apollo3/adapter/JavaOffsetDateTimeAdapter;
 	public synthetic fun fromJson (Lcom/apollographql/apollo3/api/json/JsonReader;Lcom/apollographql/apollo3/api/CustomScalarAdapters;)Ljava/lang/Object;
@@ -74,3 +82,11 @@ public final class com/apollographql/apollo3/adapter/KotlinxLocalDateTimeAdapter
 	public fun toJson (Lcom/apollographql/apollo3/api/json/JsonWriter;Lcom/apollographql/apollo3/api/CustomScalarAdapters;Lkotlinx/datetime/LocalDateTime;)V
 }
 
+public final class com/apollographql/apollo3/adapter/KotlinxLocalTimeAdapter : com/apollographql/apollo3/api/Adapter {
+	public static final field INSTANCE Lcom/apollographql/apollo3/adapter/KotlinxLocalTimeAdapter;
+	public synthetic fun fromJson (Lcom/apollographql/apollo3/api/json/JsonReader;Lcom/apollographql/apollo3/api/CustomScalarAdapters;)Ljava/lang/Object;
+	public fun fromJson (Lcom/apollographql/apollo3/api/json/JsonReader;Lcom/apollographql/apollo3/api/CustomScalarAdapters;)Lkotlinx/datetime/LocalTime;
+	public synthetic fun toJson (Lcom/apollographql/apollo3/api/json/JsonWriter;Lcom/apollographql/apollo3/api/CustomScalarAdapters;Ljava/lang/Object;)V
+	public fun toJson (Lcom/apollographql/apollo3/api/json/JsonWriter;Lcom/apollographql/apollo3/api/CustomScalarAdapters;Lkotlinx/datetime/LocalTime;)V
+}
+
diff --git a/libraries/apollo-adapters/src/commonMain/kotlin/com/apollographql/apollo3/adapter/KotlinxTimeAdapters.kt b/libraries/apollo-adapters/src/commonMain/kotlin/com/apollographql/apollo3/adapter/KotlinxTimeAdapters.kt
index 731d2dfbc4..41f75c5e45 100644
--- a/libraries/apollo-adapters/src/commonMain/kotlin/com/apollographql/apollo3/adapter/KotlinxTimeAdapters.kt
+++ b/libraries/apollo-adapters/src/commonMain/kotlin/com/apollographql/apollo3/adapter/KotlinxTimeAdapters.kt
@@ -7,6 +7,7 @@ import com.apollographql.apollo3.api.json.JsonWriter
 import kotlinx.datetime.Instant
 import kotlinx.datetime.LocalDate
 import kotlinx.datetime.LocalDateTime
+import kotlinx.datetime.LocalTime
 
 /**
  * An [Adapter] that converts an ISO 8601 String like "2010-06-01T22:19:44.475Z" to/from
@@ -55,3 +56,19 @@ object KotlinxLocalDateAdapter : Adapter<LocalDate> {
     writer.value(value.toString())
   }
 }
+
+/**
+ * An [Adapter] that converts an ISO 8601 String like "14:35:00" to/from
+ * a [kotlinx.datetime.LocalDate]
+ *
+ * It requires Android Gradle plugin 4.0 or newer and [core library desugaring](https://developer.android.com/studio/write/java8-support#library-desugaring).
+ */
+object KotlinxLocalTimeAdapter : Adapter<LocalTime> {
+  override fun fromJson(reader: JsonReader, customScalarAdapters: CustomScalarAdapters): LocalTime {
+    return LocalTime.parse(reader.nextString()!!)
+  }
+
+  override fun toJson(writer: JsonWriter, customScalarAdapters: CustomScalarAdapters, value: LocalTime) {
+    writer.value(value.toString())
+  }
+}
diff --git a/libraries/apollo-adapters/src/jvmMain/kotlin/com/apollographql/apollo3/adapter/JavaTimeAdapters.kt b/libraries/apollo-adapters/src/jvmMain/kotlin/com/apollographql/apollo3/adapter/JavaTimeAdapters.kt
index 476e317557..94531fdbb9 100644
--- a/libraries/apollo-adapters/src/jvmMain/kotlin/com/apollographql/apollo3/adapter/JavaTimeAdapters.kt
+++ b/libraries/apollo-adapters/src/jvmMain/kotlin/com/apollographql/apollo3/adapter/JavaTimeAdapters.kt
@@ -7,6 +7,7 @@ import com.apollographql.apollo3.api.json.JsonWriter
 import java.time.Instant
 import java.time.LocalDate
 import java.time.LocalDateTime
+import java.time.LocalTime
 import java.time.OffsetDateTime
 import java.time.format.DateTimeFormatter
 
@@ -85,3 +86,21 @@ object JavaOffsetDateTimeAdapter : Adapter<OffsetDateTime> {
     writer.value(value.format(DateTimeFormatter.ISO_OFFSET_DATE_TIME))
   }
 }
+
+/**
+ * An [Adapter] that converts a time to/from [java.time.LocalTime]
+ *
+ * Examples:
+ * - "14:35:00"
+ *
+ * It requires Android Gradle plugin 4.0 or newer and [core library desugaring](https://developer.android.com/studio/write/java8-support#library-desugaring).
+ */
+object JavaLocalTimeAdapter : Adapter<LocalTime> {
+  override fun fromJson(reader: JsonReader, customScalarAdapters: CustomScalarAdapters): LocalTime {
+    return LocalTime.parse(reader.nextString())
+  }
+
+  override fun toJson(writer: JsonWriter, customScalarAdapters: CustomScalarAdapters, value: LocalTime) {
+    writer.value(value.toString())
+  }
+}
diff --git a/libraries/apollo-cli/src/main/kotlin/com/apollographql/apollo3/cli/PublishSchemaCommand.kt b/libraries/apollo-cli/src/main/kotlin/com/apollographql/apollo3/cli/PublishSchemaCommand.kt
index 43476fdde8..4494f472c0 100644
--- a/libraries/apollo-cli/src/main/kotlin/com/apollographql/apollo3/cli/PublishSchemaCommand.kt
+++ b/libraries/apollo-cli/src/main/kotlin/com/apollographql/apollo3/cli/PublishSchemaCommand.kt
@@ -20,7 +20,10 @@ internal class PublishSchemaCommand: CliktCommand() {
         key = key,
         graph = graph,
         variant = graphVariant,
-        sdl = File(schema).readText()
+        sdl = File(schema).readText(),
+        subgraph = subgraph,
+        revision = revision,
+        headers = mapOf("apollographql-client-name" to "apollo-cli")
     )
   }
 }
diff --git a/libraries/apollo-compose-paging-support/build.gradle.kts b/libraries/apollo-compose-paging-support/build.gradle.kts
index aa782cc2f9..08400a3c6b 100644
--- a/libraries/apollo-compose-paging-support/build.gradle.kts
+++ b/libraries/apollo-compose-paging-support/build.gradle.kts
@@ -40,7 +40,7 @@ tasks.withType(KotlinCompile::class.java).configureEach {
   kotlinOptions {
     freeCompilerArgs += listOf(
         "-P",
-        "plugin:androidx.compose.compiler.plugins.kotlin:suppressKotlinVersionCompatibilityCheck=1.8.20-RC2"
+        "plugin:androidx.compose.compiler.plugins.kotlin:suppressKotlinVersionCompatibilityCheck=1.8.20"
     )
   }
 }
diff --git a/libraries/apollo-compose-support/build.gradle.kts b/libraries/apollo-compose-support/build.gradle.kts
index 6b45dc5e78..e643c90956 100644
--- a/libraries/apollo-compose-support/build.gradle.kts
+++ b/libraries/apollo-compose-support/build.gradle.kts
@@ -35,7 +35,7 @@ tasks.withType(KotlinCompile::class.java).configureEach {
   kotlinOptions {
     freeCompilerArgs += listOf(
         "-P",
-        "plugin:androidx.compose.compiler.plugins.kotlin:suppressKotlinVersionCompatibilityCheck=1.8.20-RC2"
+        "plugin:androidx.compose.compiler.plugins.kotlin:suppressKotlinVersionCompatibilityCheck=1.8.20"
     )
   }
 }
diff --git a/libraries/apollo-tooling/src/main/kotlin/com/apollographql/apollo3/tooling/RegisterOperations.kt b/libraries/apollo-tooling/src/main/kotlin/com/apollographql/apollo3/tooling/RegisterOperations.kt
index 41c34df4da..c7eba0a75a 100644
--- a/libraries/apollo-tooling/src/main/kotlin/com/apollographql/apollo3/tooling/RegisterOperations.kt
+++ b/libraries/apollo-tooling/src/main/kotlin/com/apollographql/apollo3/tooling/RegisterOperations.kt
@@ -29,6 +29,8 @@ import com.apollographql.apollo3.ast.transform
 import com.apollographql.apollo3.compiler.APOLLO_VERSION
 import com.apollographql.apollo3.compiler.OperationIdGenerator
 import com.apollographql.apollo3.compiler.operationoutput.OperationOutput
+import com.apollographql.apollo3.exception.ApolloException
+import com.apollographql.apollo3.exception.ApolloHttpException
 import com.apollographql.apollo3.tooling.platformapi.internal.RegisterOperationsMutation
 import com.apollographql.apollo3.tooling.platformapi.internal.type.RegisteredClientIdentityInput
 import com.apollographql.apollo3.tooling.platformapi.internal.type.RegisteredOperationInput
@@ -240,30 +242,34 @@ object RegisterOperations {
   ) {
     val apolloClient = ApolloClient.Builder()
         .serverUrl("https://graphql.api.apollographql.com/api/graphql")
+        .httpExposeErrorBody(true)
         .build()
 
-    val response = runBlocking {
-      apolloClient.mutation(
-          RegisterOperationsMutation(
-              id = graphID,
-              clientIdentity = RegisteredClientIdentityInput(
-                  name = "apollo-kotlin",
-                  identifier = "apollo-kotlin",
-                  version = Optional.present(APOLLO_VERSION),
-              ),
-              operations = operationOutput.entries.map {
-                val document = it.value.source
-                RegisteredOperationInput(
-                    signature = document.safelistingHash(),
-                    document = Optional.present(document)
-                )
-              },
-              manifestVersion = 2,
-              graphVariant = Optional.present(graphVariant)
-          )
-      )
-          .addHttpHeader("x-api-key", key)
-          .execute()
+    val call = apolloClient.mutation(
+        RegisterOperationsMutation(
+            id = graphID,
+            clientIdentity = RegisteredClientIdentityInput(
+                name = "apollo-kotlin",
+                identifier = "apollo-kotlin",
+                version = Optional.present(APOLLO_VERSION),
+            ),
+            operations = operationOutput.entries.map {
+              val document = it.value.source
+              RegisteredOperationInput(
+                  signature = document.safelistingHash(),
+                  document = Optional.present(document)
+              )
+            },
+            manifestVersion = 2,
+            graphVariant = Optional.present(graphVariant)
+        )
+    )
+        .addHttpHeader("x-api-key", key)
+    val response = try {
+      runBlocking { call.execute() }
+    } catch (e: ApolloHttpException) {
+      val body = e.body?.use { it.readUtf8() } ?: ""
+      throw ApolloException("Cannot push operations: (code: ${e.statusCode})\n$body", e)
     }
     check(!response.hasErrors()) {
       "Cannot push operations: ${response.errors!!.joinToString { it.message }}"
diff --git a/libraries/apollo-tooling/src/main/kotlin/com/apollographql/apollo3/tooling/SchemaUploader.kt b/libraries/apollo-tooling/src/main/kotlin/com/apollographql/apollo3/tooling/SchemaUploader.kt
index 35a3f3b995..f6e437d1f9 100644
--- a/libraries/apollo-tooling/src/main/kotlin/com/apollographql/apollo3/tooling/SchemaUploader.kt
+++ b/libraries/apollo-tooling/src/main/kotlin/com/apollographql/apollo3/tooling/SchemaUploader.kt
@@ -3,6 +3,9 @@ package com.apollographql.apollo3.tooling
 import com.apollographql.apollo3.ApolloClient
 import com.apollographql.apollo3.annotations.ApolloExperimental
 import com.apollographql.apollo3.api.http.HttpHeader
+import com.apollographql.apollo3.compiler.APOLLO_VERSION
+import com.apollographql.apollo3.exception.ApolloException
+import com.apollographql.apollo3.exception.ApolloHttpException
 import com.apollographql.apollo3.tooling.platformapi.public.PublishMonolithSchemaMutation
 import com.apollographql.apollo3.tooling.platformapi.public.PublishSubgraphSchemaMutation
 import kotlinx.coroutines.runBlocking
@@ -23,14 +26,34 @@ object SchemaUploader {
     }
     val apolloClient = ApolloClient.Builder()
         .serverUrl("https://api.apollographql.com/graphql")
+        .httpExposeErrorBody(true)
         .build()
 
     val graphID = graph ?: key.getGraph() ?: error("graph not found")
 
+    val allHeaders: Map<String, String> = mapOf(
+        "x-api-key" to key,
+        "apollographql-client-name" to "apollo-tooling",
+        "apollographql-client-version" to APOLLO_VERSION
+    ) + headers
     if (subgraph == null) {
-      publishMonolithSchema(apolloClient, graphID, variant, sdl, headers, key)
+      publishMonolithSchema(
+          apolloClient = apolloClient,
+          graphID = graphID,
+          variant = variant,
+          sdl = sdl,
+          headers = allHeaders
+      )
     } else {
-      publishSubgraphSchema(apolloClient, graphID, variant, sdl, subgraph, revision, headers, key)
+      publishSubgraphSchema(
+          apolloClient = apolloClient,
+          graphID = graphID,
+          variant = variant,
+          sdl = sdl,
+          subgraph = subgraph,
+          revision = revision,
+          headers = allHeaders
+      )
     }
   }
 
@@ -40,19 +63,20 @@ object SchemaUploader {
       variant: String,
       sdl: String,
       headers: Map<String, String>,
-      key: String,
   ) {
-    val response = runBlocking {
-      apolloClient.mutation(
-          PublishMonolithSchemaMutation(
-              graphID = graphID,
-              variant = variant,
-              schemaDocument = sdl,
-          )
-      )
-          .httpHeaders(headers.map { HttpHeader(it.key, it.value) })
-          .addHttpHeader("x-api-key", key)
-          .execute()
+    val call = apolloClient.mutation(
+        PublishMonolithSchemaMutation(
+            graphID = graphID,
+            variant = variant,
+            schemaDocument = sdl,
+        )
+    )
+        .httpHeaders(headers.map { HttpHeader(it.key, it.value) })
+    val response = try {
+      runBlocking { call.execute() }
+    } catch (e: ApolloHttpException) {
+      val body = e.body?.use { it.readUtf8() } ?: ""
+      throw ApolloException("Cannot upload schema: (code: ${e.statusCode})\n$body", e)
     }
     check(!response.hasErrors()) {
       "Cannot upload schema: ${response.errors!!.joinToString { it.message }}"
@@ -73,21 +97,22 @@ object SchemaUploader {
       subgraph: String,
       revision: String?,
       headers: Map<String, String>,
-      key: String,
   ) {
-    val response = runBlocking {
-      apolloClient.mutation(
-          PublishSubgraphSchemaMutation(
-              graphID = graphID,
-              variant = variant,
-              schemaDocument = sdl,
-              subgraph = subgraph,
-              revision = revision!!,
-          )
-      )
-          .httpHeaders(headers.map { HttpHeader(it.key, it.value) })
-          .addHttpHeader("x-api-key", key)
-          .execute()
+    val call = apolloClient.mutation(
+        PublishSubgraphSchemaMutation(
+            graphID = graphID,
+            variant = variant,
+            schemaDocument = sdl,
+            subgraph = subgraph,
+            revision = revision!!,
+        )
+    )
+        .httpHeaders(headers.map { HttpHeader(it.key, it.value) })
+    val response = try {
+      runBlocking { call.execute() }
+    } catch (e: ApolloHttpException) {
+      val body = e.body?.use { it.readUtf8() } ?: ""
+      throw ApolloException("Cannot upload schema: (code: ${e.statusCode})\n$body", e)
     }
     check(!response.hasErrors()) {
       "Cannot upload schema: ${response.errors!!.joinToString { it.message }}"
diff --git a/scripts/release.main.kts b/scripts/release.main.kts
index d54938a9a9..6dbd3fcb9b 100755
--- a/scripts/release.main.kts
+++ b/scripts/release.main.kts
@@ -19,12 +19,15 @@ check(getCurrentVersion().endsWith("-SNAPSHOT")) {
 }
 
 check(args.size == 1) {
-  "tag.main.kts [version to tag]"
+  "release.main.kts [version to release]"
 }
 val versionToRelease = args[0]
 val nextSnapshot = getNextSnapshot(versionToRelease)
 
 val startBranch = runCommand("git", "symbolic-ref", "--short", "HEAD")
+check(startBranch == "main" || startBranch.startsWith("release-")) {
+  "You must be on the main branch or a release branch to make a release"
+}
 
 while (true) {
   println("Current version is '${getCurrentVersion()}'.")
@@ -46,7 +49,7 @@ setCurrentVersion(versionToRelease)
 setVersionInDocs(versionToRelease, nextSnapshot)
 runCommand("git", "commit", "-a", "-m", "release $versionToRelease")
 runCommand("git", "push", "origin", releaseBranchName)
-runCommand("gh", "pr", "create", "--fill")
+runCommand("gh", "pr", "create", "--base", startBranch, "--fill")
 
 println("Press enter to merge the release PR")
 readLine()
@@ -70,7 +73,7 @@ runCommand("git", "checkout", "-b", bumpVersionBranchName)
 setCurrentVersion(nextSnapshot)
 runCommand("git", "commit", "-a", "-m", "version is now $nextSnapshot")
 runCommand("git", "push", "origin", bumpVersionBranchName)
-runCommand("gh", "pr", "create", "--fill")
+runCommand("gh", "pr", "create", "--base", startBranch, "--fill")
 
 println("Press enter to merge the bump version PR")
 readLine()


Test patch:
diff --git a/libraries/apollo-adapters/src/jvmTest/kotlin/JavaTimeAdaptersTest.kt b/libraries/apollo-adapters/src/jvmTest/kotlin/JavaTimeAdaptersTest.kt
index c062f13c69..6cde061186 100644
--- a/libraries/apollo-adapters/src/jvmTest/kotlin/JavaTimeAdaptersTest.kt
+++ b/libraries/apollo-adapters/src/jvmTest/kotlin/JavaTimeAdaptersTest.kt
@@ -2,10 +2,12 @@ import com.apollographql.apollo3.adapter.DateAdapter
 import com.apollographql.apollo3.adapter.JavaInstantAdapter
 import com.apollographql.apollo3.adapter.JavaLocalDateAdapter
 import com.apollographql.apollo3.adapter.JavaLocalDateTimeAdapter
+import com.apollographql.apollo3.adapter.JavaLocalTimeAdapter
 import com.apollographql.apollo3.adapter.JavaOffsetDateTimeAdapter
 import com.apollographql.apollo3.adapter.KotlinxInstantAdapter
 import com.apollographql.apollo3.adapter.KotlinxLocalDateAdapter
 import com.apollographql.apollo3.adapter.KotlinxLocalDateTimeAdapter
+import com.apollographql.apollo3.adapter.KotlinxLocalTimeAdapter
 import com.apollographql.apollo3.api.Adapter
 import com.apollographql.apollo3.api.CustomScalarAdapters
 import com.apollographql.apollo3.api.json.BufferedSourceJsonReader
@@ -83,6 +85,15 @@ class JavaTimeAdaptersTest {
     assertEquals("2010-06-01", JavaLocalDateAdapter.toJson(localDate))
   }
 
+  @Test
+  fun localTime() {
+    val localTime = JavaLocalTimeAdapter.fromJson("14:35:20")
+    assertEquals(14, localTime.hour)
+    assertEquals(35, localTime.minute)
+    assertEquals(20, localTime.second)
+    assertEquals("14:35:20", JavaLocalTimeAdapter.toJson(localTime))
+  }
+
   @Test
   fun kotlinxInstant() {
     var instant = KotlinxInstantAdapter.fromJson("2010-06-01T22:19:44.475Z")
@@ -109,4 +120,13 @@ class JavaTimeAdaptersTest {
     assertEquals("2010-06-01", KotlinxLocalDateAdapter.toJson(localDate))
   }
 
+  @Test
+  fun kotlinxLocalTime() {
+    val localTime = KotlinxLocalTimeAdapter.fromJson("14:35:20")
+    assertEquals(14, localTime.hour)
+    assertEquals(35, localTime.minute)
+    assertEquals(20, localTime.second)
+    assertEquals("14:35:20", KotlinxLocalTimeAdapter.toJson(localTime))
+  }
+
 }