diff --git a/.build/build.gradle.kts b/.build/build.gradle.kts index e0e12f6..84c5356 100644 --- a/.build/build.gradle.kts +++ b/.build/build.gradle.kts @@ -3,6 +3,7 @@ import documentation.generateDiagramsFromPlantUml import documentation.generateEndpointOverviewDocumentFromJson import documentation.generateEventsOverviewDocumentFromJson import documentation.generateOverviewDiagramsFromJson +import documentation.tasks.generateComponentDescription tasks.register("generateFiles") { val srcFolder = File(project.rootDir, "src") @@ -15,3 +16,12 @@ tasks.register("generateFiles") { generateEventsOverviewDocumentFromJson(srcFolder, rootFolder) } } + +tasks.register("combineParts") { + val sourceFolder = File(project.rootDir, "tmp/parts") + val targetFolder = File(project.rootDir, "src/json/components") + val componentId = project.property("componentId") as String + doLast { + generateComponentDescription(sourceFolder, targetFolder, componentId) + } +} diff --git a/.build/buildSrc/src/main/kotlin/documentation/tasks/combine-parts.kt b/.build/buildSrc/src/main/kotlin/documentation/tasks/combine-parts.kt new file mode 100644 index 0000000..881a795 --- /dev/null +++ b/.build/buildSrc/src/main/kotlin/documentation/tasks/combine-parts.kt @@ -0,0 +1,89 @@ +package documentation.tasks + +import com.fasterxml.jackson.annotation.JsonInclude.Include +import com.fasterxml.jackson.databind.DeserializationFeature +import com.fasterxml.jackson.databind.SerializationFeature +import com.fasterxml.jackson.module.kotlin.jacksonObjectMapper +import com.fasterxml.jackson.module.kotlin.readValue +import documentation.model.Application +import documentation.model.Dependency +import documentation.model.Dependent +import documentation.model.Event +import documentation.model.HttpEndpoint +import java.io.File +import kotlin.reflect.KClass + +private val objectMapper = jacksonObjectMapper() + .setSerializationInclusion(Include.NON_EMPTY) + .disable(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES) + .enable(DeserializationFeature.READ_UNKNOWN_ENUM_VALUES_AS_NULL) + .enable(SerializationFeature.INDENT_OUTPUT) + +fun generateComponentDescription(sourceFolder: File, targetFolder: File, applicationId: String) { + val baseApplicationDescription = loadBaseApplicationDescription(sourceFolder, applicationId) + val dependents = loadDependents(sourceFolder) + val dependencies = loadDependencies(sourceFolder) + val events = loadEvents(sourceFolder) + + val applicationDescription = baseApplicationDescription + .copy(dependents = dependents, dependencies = dependencies, events = events) + + val file = File(targetFolder, applicationDescription.id + ".json") + objectMapper.writeValue(file, applicationDescription) +} + +private fun loadBaseApplicationDescription(sourceFolder: File, applicationId: String): Application { + val file = File(sourceFolder, "$applicationId.json") + check(file.isFile) { "File not found: $file" } + return objectMapper.readValue(file) +} + +private fun loadDependents(sourceFolder: File): List = + listJsonFilesInFolder(File(sourceFolder, "dependents")) + .map { file -> loadDependent(file) } + +private fun loadDependent(file: File): Dependent { + return objectMapper.readValue(file) +} + +private fun loadDependencies(sourceFolder: File): List = + listJsonFilesInFolder(File(sourceFolder, "dependencies")) + .map { file -> loadDependency(file) } + +private fun loadDependency(file: File): Dependency { + val dependency = objectMapper.readValue(file) + + var httpEndpoints = emptyList() + + val httpEndpointsFile = File(file.parentFile, "http-endpoints/${dependency.id}.jsonl") + if (httpEndpointsFile.isFile) { + httpEndpoints = loadFromJsonListFile(httpEndpointsFile, HttpEndpoint::class) + .sortedWith(compareBy(HttpEndpoint::path, HttpEndpoint::method)) + .distinct() + } + + return dependency.copy(httpEndpoints = httpEndpoints) +} + +private fun loadEvents(sourceFolder: File): List = + listJsonFilesInFolder(File(sourceFolder, "events")) + .map { file -> loadEvent(file) } + +private fun loadEvent(file: File): Event { + return objectMapper.readValue(file) +} + +private fun listJsonFilesInFolder(folder: File): List = + if (folder.isDirectory) { + folder.listFiles()!! + .filter { it.isFile } + .filter { it.extension == "json" } + } else { + emptyList() + } + +private fun loadFromJsonListFile(file: File, clazz: KClass): List = + file.readLines() + .filter(String::isNotBlank) + .map(String::trim) + .map { objectMapper.readValue(it, clazz.java) } diff --git a/.github/actions/update-component/action.yml b/.github/actions/update-component/action.yml new file mode 100644 index 0000000..09705f6 --- /dev/null +++ b/.github/actions/update-component/action.yml @@ -0,0 +1,51 @@ +name: "Update Component" +description: "Downloads the parts of a component's architecture documentation, combines them and updates the existing file." + +inputs: + component-id: + description: "The ID of the component." + required: true + github-token: + description: "A GitHub token that can be used to write to the repositories content." + required: true + +runs: + using: composite + steps: + - name: "Checkout" + uses: actions/checkout@v4 + with: + repository: automatic-architecture-documentation/documentation + token: ${{ inputs.github-token }} + - name: "Setup Java" + uses: actions/setup-java@v4 + with: + java-version: "17" + distribution: "temurin" + - name: "Setup Gradle" + uses: gradle/gradle-build-action@v3 + with: + gradle-version: wrapper + - name: "Download Architecture Documentation Parts" + uses: actions/download-artifact@v4 + with: + name: architecture-documentation-parts + path: .build/tmp/parts + - name: "Setup Gradle Wrapper" + working-directory: .build + shell: bash + run: chmod +x ./gradlew + - name: "Combine Parts" + working-directory: .build + shell: bash + run: './gradlew --no-daemon combineParts -PcomponentId=${{ inputs.component-id }}' + - name: "Commit & Push Changes" + shell: bash + run: | + git config --global user.name "Botty" + git config --global user.email "bot@example.com" + git add .build/src/json/components/${{ inputs.component-id }}.json + git diff --quiet --exit-code --cached || { + git commit -m "updated ${{ inputs.component-id }}.json" + git push origin HEAD:master + }