diff --git a/.github/workflows/docs.yml b/.github/workflows/docs.yml index ca8eceaeb..5dad15ffd 100644 --- a/.github/workflows/docs.yml +++ b/.github/workflows/docs.yml @@ -31,6 +31,9 @@ jobs: with: fetch-depth: 0 + - name: Create stub changelog.md file + run: echo "# Changelog" > docs/pages/kotlinx-rpc/topics/changelog.md + - name: Build docs using Writerside Docker builder uses: JetBrains/writerside-github-action@v4 with: @@ -99,6 +102,13 @@ jobs: - name: Update sitemap.xml run: chmod +x updateSitemap.sh && ./updateSitemap.sh __docs_publication_dir/sitemap.xml __docs_publication_dir/api + - name: Run Changelog Generator + run: ./gradlew updateDocsChangelog + + - name: Move Changelog.md to the publication directory + run: + mv docs/pages/kotlinx-rpc/topics/changelog.md __docs_publication_dir/changelog.md + - name: Setup Pages uses: actions/configure-pages@v5 diff --git a/CHANGELOG.md b/CHANGELOG.md index 762a96098..ab546d692 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -5,8 +5,6 @@ This release enforces ERROR as a default reporting level for APIs that are forbidden by the strict mode. You can still change the level manually, but in `0.8.0` strict mode will be enforced irreversibly. -## What's Changed - ### Breaking Changes 🔴 * Change strict mode to level ERROR by default by @Mr3zee in https://github.com/Kotlin/kotlinx-rpc/pull/338 diff --git a/build.gradle.kts b/build.gradle.kts index c82f21ac3..aa587897e 100644 --- a/build.gradle.kts +++ b/build.gradle.kts @@ -8,6 +8,7 @@ import util.configureApiValidation import util.configureNpm import util.configureProjectReport import util.registerDumpPlatformTableTask +import util.registerChangelogTask import util.libs import util.registerVerifyPlatformTableTask import java.time.Year @@ -83,6 +84,7 @@ configureApiValidation() registerDumpPlatformTableTask() registerVerifyPlatformTableTask() +registerChangelogTask() val kotlinVersion = rootProject.libs.versions.kotlin.lang.get() val kotlinCompiler = rootProject.libs.versions.kotlin.compiler.get() diff --git a/docs/pages/.gitignore b/docs/pages/.gitignore index d455b18d1..af4afceb2 100644 --- a/docs/pages/.gitignore +++ b/docs/pages/.gitignore @@ -1 +1,2 @@ api/** +kotlinx-rpc/topics/changelog.md diff --git a/docs/pages/kotlinx-rpc/rpc.tree b/docs/pages/kotlinx-rpc/rpc.tree index 409749758..5e0c85269 100644 --- a/docs/pages/kotlinx-rpc/rpc.tree +++ b/docs/pages/kotlinx-rpc/rpc.tree @@ -49,5 +49,6 @@ + diff --git a/gradle-conventions/common/src/main/kotlin/util/changelog.kt b/gradle-conventions/common/src/main/kotlin/util/changelog.kt new file mode 100644 index 000000000..8ef63c472 --- /dev/null +++ b/gradle-conventions/common/src/main/kotlin/util/changelog.kt @@ -0,0 +1,131 @@ +/* + * Copyright 2023-2025 JetBrains s.r.o and contributors. Use of this source code is governed by the Apache 2.0 license. + */ + +package util + +import org.gradle.api.DefaultTask +import org.gradle.api.GradleException +import org.gradle.api.Project +import org.gradle.api.provider.Property +import org.gradle.api.tasks.InputFile +import org.gradle.api.tasks.OutputFile +import org.gradle.api.tasks.TaskAction +import org.gradle.kotlin.dsl.register +import java.io.File +import kotlin.io.path.createDirectories +import kotlin.io.path.createFile +import kotlin.io.path.exists +import kotlin.io.path.readLines +import kotlin.io.path.writeLines + +private val ROOT_CHANGELOG_PATH = File("CHANGELOG.md") +private val DOCS_CHANGELOG_PATH = File("docs/pages/kotlinx-rpc/topics/changelog.md") + +private val PULL_REGEX = "https://github.com/Kotlin/kotlinx-rpc/pull/(\\d+)".toRegex() +private val COMPARE_REGEX = "https://github.com/Kotlin/kotlinx-rpc/compare/([+.\\w_-]+)".toRegex() +private val USERNAME_REGEX = "@([\\w_-]+)".toRegex() +private val WHITESPACE = "\\s+".toRegex() + +abstract class UpdateDocsChangelog : DefaultTask() { + @get:InputFile + abstract val input: Property + + @get:OutputFile + abstract val output: Property + + @TaskAction + fun update() { + val inputPath = input.get().toPath() + val outputPath = output.get().toPath() + + if (!inputPath.exists()) { + throw GradleException("fatal error: input file $inputPath does not exist") + } + + var currentRelease = "" + val fullChangelogLines = mutableListOf() + val lines = inputPath.readLines(Charsets.UTF_8).flatMap { line -> + val updated = line + .replace(PULL_REGEX) { + "[#${it.groupValues[1]}](${it.groupValues[0]})" + } + .replace(COMPARE_REGEX) { + "[${it.groupValues[1]}](${it.groupValues[0]})" + } + .replace(USERNAME_REGEX) { + "[${it.groupValues[0]}](https://github.com/${it.groupValues[1]})" + }.let { + if (it.startsWith("#")) { + "#$it" + } else { + it + } + } + + if (updated.startsWith("## ")) { + currentRelease = updated + .drop(3) + .replace(".", "_") + } + + when { + updated.startsWith("###") -> { + val name = updated + .dropWhile { it == '#' } + .dropLastWhile { !it.isDigit() && !it.isLetter() } + .trim() + .replace(WHITESPACE, "_") + + listOf("$updated {id=${name}_$currentRelease}") + } + + updated.startsWith("**Full Changelog**:") -> { + fullChangelogLines.add(updated) + emptyList() + } + + else -> listOf(updated) + } + } + + val result = mutableListOf() + + var i = 0 + var fci = 0 + while (i < lines.size) { + val line = lines[i] + result.add(line) + + if (line.startsWith("## ")) { + result.add(lines[i + 1]) + result.add("") + result.add(fullChangelogLines[fci++]) + i++ + } + + i++ + } + + if (!outputPath.exists()) { + outputPath.parent.createDirectories() + outputPath.createFile() + } + + val header = listOf( + "# Changelog", + "", + "This page contains all changes throughout releases of the library.", + "", + ) + + outputPath.writeLines(header + result) + } +} + +fun Project.registerChangelogTask() { + tasks.register("updateDocsChangelog") { + input.set(ROOT_CHANGELOG_PATH) + output.set(DOCS_CHANGELOG_PATH) + } +}