├── .github ├── FUNDING.yml ├── dependabot.yml └── workflows │ ├── ci.yml │ ├── danger.yml │ ├── docker_manual.yml │ ├── publish_release.yml │ └── release_sdk.yml ├── .gitignore ├── CHANGELOG.md ├── CODEOWNERS ├── Dangerfile.df.kts ├── Dangerfile_ci.df.kts ├── Dockerfile ├── LICENSE ├── Makefile ├── README.md ├── action.yml ├── build.gradle ├── configurations.gradle ├── danger-kotlin-kts ├── build.gradle ├── src │ └── main │ │ └── kotlin │ │ └── systems │ │ └── danger │ │ └── kts │ │ └── DangerFileScriptDefinition.kt └── version.gradle ├── danger-kotlin-library ├── build.gradle ├── dependencies.gradle ├── gradle │ └── wrapper │ │ └── gradle-wrapper.properties ├── gradlew ├── gradlew.bat ├── src │ ├── main │ │ └── kotlin │ │ │ └── systems │ │ │ └── danger │ │ │ └── kotlin │ │ │ ├── KtxBitBucketServer.kt │ │ │ ├── KtxDangerDSL.kt │ │ │ ├── KtxGit.kt │ │ │ ├── KtxGitHub.kt │ │ │ ├── KtxGitLab.kt │ │ │ ├── MainDangerRunner.kt │ │ │ ├── MainPlugins.kt │ │ │ ├── MainScript.kt │ │ │ ├── json │ │ │ └── JsonParser.kt │ │ │ ├── models │ │ │ ├── bitbucket │ │ │ │ ├── BitBucketCloud.kt │ │ │ │ ├── BitBucketMetadata.kt │ │ │ │ └── BitBucketServer.kt │ │ │ ├── danger │ │ │ │ ├── DangerDSL.kt │ │ │ │ ├── DangerResults.kt │ │ │ │ └── Utils.kt │ │ │ ├── git │ │ │ │ └── Git.kt │ │ │ ├── github │ │ │ │ └── GitHub.kt │ │ │ ├── gitlab │ │ │ │ └── GitLab.kt │ │ │ └── serializers │ │ │ │ ├── DateSerializer.kt │ │ │ │ └── ViolationSerializer.kt │ │ │ └── tools │ │ │ └── shell │ │ │ └── ShellExecutor.kt │ └── test │ │ ├── kotlin │ │ └── systems │ │ │ └── danger │ │ │ └── kotlin │ │ │ ├── KtxGitLabTest.kt │ │ │ ├── KtxGitTest.kt │ │ │ ├── UtilsTests.kt │ │ │ ├── models │ │ │ ├── bitbucket │ │ │ │ ├── BitBucketCloudParsingTests.kt │ │ │ │ └── BitBucketServerParsingTests.kt │ │ │ ├── git │ │ │ │ └── GitParsingTests.kt │ │ │ ├── github │ │ │ │ └── GitHubParsingTests.kt │ │ │ └── gitlab │ │ │ │ ├── GitLabParsingTests.kt │ │ │ │ └── GitLabPipelineStatusTest.kt │ │ │ └── utils │ │ │ └── TestUtils.kt │ │ └── resources │ │ ├── bitbucketCloudDangerJSON.json │ │ ├── bitbucketServerDangerJSON.json │ │ ├── githubDangerJSON.json │ │ ├── githubWithClosedMilestoneDangerJSON.json │ │ ├── githubWithSomeNullsAttributeDangerJSON.json │ │ ├── gitlabDangerJSON.json │ │ └── gitlabWithCancelledPipelineDangerJSON.json └── version.gradle ├── danger-kotlin-sample-plugin ├── build.gradle ├── gradle │ └── wrapper │ │ └── gradle-wrapper.properties ├── gradlew ├── gradlew.bat ├── settings.gradle └── src │ └── main │ └── kotlin │ └── systems │ └── danger │ └── samples │ └── plugin │ └── SamplePlugin.kt ├── danger-kotlin-sdk ├── build.gradle ├── gradle │ └── wrapper │ │ └── gradle-wrapper.properties ├── gradlew ├── gradlew.bat ├── maven-publish.gradle ├── src │ └── main │ │ └── kotlin │ │ └── systems │ │ └── danger │ │ └── kotlin │ │ └── sdk │ │ └── DangerKotlinAPI.kt └── version.gradle ├── danger-kotlin ├── build.gradle.kts ├── gradle.properties ├── gradle │ └── wrapper │ │ └── gradle-wrapper.properties └── src │ └── runnerMain │ └── kotlin │ ├── Main.kt │ └── systems.danger │ ├── DangerKotlin.kt │ ├── Log.kt │ └── cmd │ ├── Cmd.kt │ ├── Command.kt │ ├── dangerfile │ ├── DangerFile.kt │ └── DangerFileBridge.kt │ └── dangerjs │ ├── DangerJS.kt │ └── DangerJSBridge.kt ├── danger-plugin-installer ├── build.gradle └── src │ └── main │ └── kotlin │ └── systems │ └── danger │ └── kotlin │ └── plugininstaller │ └── PluginInstaller.kt ├── dependencyVersions.gradle ├── docs ├── guides │ └── about_the_dangerfile.html.md ├── tutorials │ ├── architecture.html.md │ ├── fast_feedback.html.md │ └── plugin_development.html.md └── usage │ ├── bitbucket.html.md │ ├── culture.html.md │ └── gitlab.html.md ├── github-action ├── Dockerfile └── entrypoint.sh ├── gradle.properties ├── gradle └── wrapper │ ├── gradle-wrapper.jar │ └── gradle-wrapper.properties ├── gradlew ├── gradlew.bat ├── scripts ├── install.sh └── release_changelog.sh ├── secrets.gradle ├── settings.gradle └── shadow.gradle /.github/FUNDING.yml: -------------------------------------------------------------------------------- 1 | github: [f-meloni, gianluz] 2 | -------------------------------------------------------------------------------- /.github/dependabot.yml: -------------------------------------------------------------------------------- 1 | version: 2 2 | 3 | updates: 4 | - package-ecosystem: "github-actions" 5 | directory: "/" 6 | schedule: 7 | interval: "daily" 8 | -------------------------------------------------------------------------------- /.github/workflows/ci.yml: -------------------------------------------------------------------------------- 1 | name: CI 2 | 3 | on: pull_request 4 | 5 | jobs: 6 | build: 7 | strategy: 8 | fail-fast: false 9 | matrix: 10 | os: [ubuntu-latest, macos-latest] 11 | 12 | runs-on: ${{ matrix.os }} 13 | 14 | steps: 15 | - uses: actions/checkout@v4 16 | 17 | - uses: sdkman/sdkman-action@master 18 | with: 19 | candidate: gradle 20 | version: 8.10.2 21 | 22 | - name: Install Kotlin 23 | run: | 24 | curl -o kotlin-compiler.zip -L https://github.com/JetBrains/kotlin/releases/download/v2.0.21/kotlin-compiler-2.0.21.zip 25 | 26 | if [[ "$OSTYPE" != "darwin"* ]] 27 | then 28 | sudo chmod -R a+rwx /usr/local/ 29 | 30 | unzip -d /usr/local/bin kotlin-compiler.zip 31 | echo "/usr/local/bin/kotlinc/bin" >> $GITHUB_PATH 32 | rm -rf kotlin-compiler.zip 33 | fi 34 | 35 | - uses: actions/setup-node@v4 36 | with: 37 | node-version: '22.10.0' 38 | 39 | - name: Install Danger JS 40 | run: npm install -g danger 41 | 42 | - name: Install Danger Kotlin 43 | run: sudo make install 44 | 45 | - name: Run tests 46 | run: sudo ./gradlew danger-kotlin-library:test 47 | 48 | - name: Install Plugin Installer 49 | run: sudo ./gradlew danger-plugin-installer:publishToMavenLocal 50 | 51 | - name: Build and Install Sample Plugin 52 | working-directory: ./danger-kotlin-sample-plugin 53 | run: | 54 | sudo gradle wrapper 55 | sudo ./gradlew build 56 | sudo ./gradlew installDangerPlugin 57 | 58 | - name: Run Danger-Kotlin 59 | run: DEBUG='*' danger-kotlin ci --dangerfile Dangerfile_ci.df.kts 60 | env: 61 | GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} 62 | -------------------------------------------------------------------------------- /.github/workflows/danger.yml: -------------------------------------------------------------------------------- 1 | name: Danger Action 2 | 3 | on: [pull_request] 4 | 5 | jobs: 6 | build: 7 | runs-on: ubuntu-latest 8 | name: "Run Danger" 9 | steps: 10 | - uses: actions/checkout@v4 11 | - name: Danger 12 | uses: danger/kotlin@master 13 | with: 14 | args: "--id DangerKotlinAction" 15 | env: 16 | GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} 17 | -------------------------------------------------------------------------------- /.github/workflows/docker_manual.yml: -------------------------------------------------------------------------------- 1 | name: Build Docker (Manual) 2 | 3 | on: 4 | workflow_dispatch : 5 | inputs: 6 | docker-version: 7 | description: "Docker Version" 8 | danger-js-version: 9 | description: "Danger JS release version" 10 | default: "12.3.3" 11 | danger-kotlin-version: 12 | description: "Danger Kotlin release version" 13 | kotlin-version: 14 | description: "Kotlin Version" 15 | default: "2.0.21" 16 | 17 | jobs: 18 | docker-build-push: 19 | name: Build and Push Docker image 20 | permissions: 21 | contents: read 22 | packages: write 23 | runs-on: ubuntu-latest 24 | steps: 25 | - uses: actions/checkout@v4 26 | 27 | - name: Docker Login 28 | run: echo $PACKAGES_WRITE_TOKEN | docker login ghcr.io -u $USERNAME --password-stdin 29 | env: 30 | PACKAGES_WRITE_TOKEN: ${{ secrets.GITHUB_TOKEN }} 31 | USERNAME: ${{ github.actor }} 32 | 33 | - name: Docker Build 34 | run: docker build -t ghcr.io/danger/danger-kotlin:$VERSION --build-arg="KOTLINC_VERSION=$KOTLINC_VERSION" --build-arg="DANGER_KOTLIN_VERSION=$DANGER_KOTLIN_VERSION" --build-arg="DANGER_JS_VERSION=$DANGER_JS_VERSION" . 35 | env: 36 | VERSION: ${{ github.event.inputs.docker-version }} 37 | KOTLINC_VERSION: ${{ github.event.inputs.kotlin-version }} 38 | DANGER_KOTLIN_VERSION: ${{ github.event.inputs.danger-kotlin-version }} 39 | DANGER_JS_VERSION: ${{ github.event.inputs.danger-js-version }} 40 | 41 | - name: Deploy 42 | run: docker push ghcr.io/danger/danger-kotlin:$VERSION 43 | env: 44 | VERSION: ${{ github.event.inputs.docker-version }} 45 | -------------------------------------------------------------------------------- /.github/workflows/publish_release.yml: -------------------------------------------------------------------------------- 1 | name: Release distribution 2 | 3 | on: 4 | release: 5 | types: [ created ] 6 | 7 | jobs: 8 | dangerKotlinLibrary-shadowJar: 9 | name: Build Library 10 | runs-on: ubuntu-latest 11 | steps: 12 | - uses: actions/checkout@v4 13 | 14 | - name: Set up JDK 23 15 | uses: actions/setup-java@v4 16 | with: 17 | distribution: "temurin" 18 | java-version: 23 19 | cache: gradle 20 | - name: danger-kotlin-library:shadowJar 21 | run: ./gradlew danger-kotlin-library:shadowJar 22 | - name: Upload Artifact lib 23 | uses: actions/upload-artifact@v4 24 | with: 25 | name: lib 26 | path: danger-kotlin-library/build/libs/danger-kotlin.jar 27 | 28 | dangerKotlin-build-and-distribute: 29 | name: Build and Attach binary to release 30 | needs: [ dangerKotlinLibrary-shadowJar ] 31 | strategy: 32 | fail-fast: false 33 | matrix: 34 | target: [ { os: ubuntu-latest, compiler: linuxX64 }, { os: macos-latest, compiler: macosX64 }, { os: macos-latest, compiler: macosArm64 } ] 35 | 36 | runs-on: ${{ matrix.target.os }} 37 | 38 | steps: 39 | - uses: actions/checkout@v4 40 | - name: Download Artifact lib 41 | uses: actions/download-artifact@v4 42 | with: 43 | name: lib 44 | 45 | - name: Set up JDK 23 46 | uses: actions/setup-java@v4 47 | with: 48 | distribution: "temurin" 49 | java-version: 23 50 | cache: gradle 51 | - name: danger-kotlin:build -PtargetOS="${{ matrix.target.compiler }}" 52 | run: ./gradlew danger-kotlin:build -PtargetOS="${{ matrix.target.compiler }}" 53 | - name: Prepare distribution package 54 | run: | 55 | mkdir -p lib/danger 56 | mkdir -p bin 57 | mv "danger-kotlin.jar" "lib/danger" 58 | mv "danger-kotlin/build/bin/runner/releaseExecutable/danger-kotlin.kexe" "bin/danger-kotlin" 59 | chmod +x bin/danger-kotlin 60 | - name: Tar files 61 | run: | 62 | tar -cvf danger-kotlin-${{ matrix.target.compiler }}.tar bin lib 63 | shasum -a 256 danger-kotlin-${{ matrix.target.compiler }}.tar 64 | - name: Get release information 65 | id: get_release 66 | uses: bruceadams/get-release@v1.3.2 67 | env: 68 | GITHUB_TOKEN: ${{ github.token }} 69 | - name: Upload artifacts to release 70 | uses: actions/upload-release-asset@v1.0.2 71 | env: 72 | GITHUB_TOKEN: ${{ github.token }} 73 | with: 74 | upload_url: ${{ steps.get_release.outputs.upload_url }} 75 | asset_path: ./danger-kotlin-${{ matrix.target.compiler }}.tar 76 | asset_name: danger-kotlin-${{ matrix.target.compiler }}.tar 77 | asset_content_type: application/x-tar 78 | 79 | docker-build-push: 80 | name: Build and Push Docker image 81 | permissions: 82 | contents: read 83 | packages: write 84 | runs-on: ubuntu-latest 85 | needs: [ dangerKotlin-build-and-distribute ] 86 | steps: 87 | - uses: actions/checkout@v4 88 | 89 | - name: Get release information 90 | id: get_release 91 | uses: bruceadams/get-release@v1.3.2 92 | env: 93 | GITHUB_TOKEN: ${{ github.token }} 94 | 95 | - name: Docker Login 96 | run: echo $PACKAGES_WRITE_TOKEN | docker login ghcr.io -u $USERNAME --password-stdin 97 | env: 98 | PACKAGES_WRITE_TOKEN: ${{ secrets.GITHUB_TOKEN }} 99 | USERNAME: ${{ github.actor }} 100 | 101 | - name: Docker Build 102 | run: docker build -t ghcr.io/danger/danger-kotlin:$VERSION . 103 | env: 104 | VERSION: ${{ steps.get_release.outputs.tag_name }} 105 | 106 | - name: Deploy 107 | run: docker push ghcr.io/danger/danger-kotlin:$VERSION 108 | env: 109 | VERSION: ${{ steps.get_release.outputs.tag_name }} 110 | -------------------------------------------------------------------------------- /.github/workflows/release_sdk.yml: -------------------------------------------------------------------------------- 1 | name: Release SDK 2 | 3 | on: 4 | push: 5 | tags: 6 | - 'sdk_*' 7 | 8 | jobs: 9 | release: 10 | runs-on: ubuntu-latest 11 | steps: 12 | - name: Check out Git repository 13 | uses: actions/checkout@v4 14 | - name: Install Java and Maven 15 | uses: actions/setup-java@v4 16 | with: 17 | java-version: 1.8 18 | - name: Install GNUPG2 & Import Key 19 | env: 20 | GPG_PRIVATE_KEY: ${{ secrets.GPG_PRIVATE_KEY }} 21 | GPG_KEY_ID: ${{ secrets.GPG_KEY_ID }} 22 | run: | 23 | sudo apt-get install gnupg2 -y 24 | gpg2 --version 25 | echo "$GPG_PRIVATE_KEY" > secret.key 26 | echo "Importing GPG Key..." 27 | gpg2 --import --batch secret.key &> /dev/null 28 | echo "[ OK ] GPG Key imported." 29 | echo "Sending public key to the server..." 30 | gpg2 --keyserver hkps://keys.openpgp.org --send-keys $GPG_KEY_ID 31 | echo "[ OK ] Public key was sent to the server." 32 | rm secret.key 33 | - name: Deploy & Release 34 | env: 35 | SONATYPE_USER: ${{ secrets.SONATYPE_USER }} 36 | SONATYPE_PASS: ${{ secrets.SONATYPE_PASS }} 37 | GPG_KEY_ID: ${{ secrets.GPG_KEY_ID }} 38 | GPG_PASSPHRASE: ${{ secrets.GPG_PASSPHRASE }} 39 | run: | 40 | gradle wrapper 41 | ./gradlew :danger-kotlin-sdk:publish 42 | ./gradlew closeAndReleaseRepository 43 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Idea 2 | .idea/ 3 | *.iml 4 | *.iws 5 | *.ipr 6 | /*/out 7 | local.properties 8 | 9 | #Gradle 10 | .gradle/ 11 | build/ 12 | 13 | #OSx 14 | .DS_Store 15 | 16 | # CMake 17 | cmake-build-debug/ 18 | cmake-build-release/ 19 | 20 | # JIRA plugin 21 | atlassian-ide-plugin.xml 22 | 23 | # Crashlytics plugin (for Android Studio and IntelliJ) 24 | com_crashlytics_export_strings.xml 25 | crashlytics.properties 26 | crashlytics-build.properties 27 | fabric.properties 28 | 29 | # Compiled class file 30 | *.class 31 | 32 | # Log file 33 | *.log 34 | 35 | # BlueJ files 36 | *.ctxt 37 | 38 | # Mobile Tools for Java (J2ME) 39 | .mtj.tmp/ 40 | 41 | # Package Files # 42 | *.jar 43 | !gradle/wrapper/gradle-wrapper.jar 44 | 45 | *.war 46 | *.nar 47 | *.ear 48 | *.zip 49 | *.tar.gz 50 | *.rar 51 | 52 | # virtual machine crash logs, see http://www.java.com/en/download/help/error_hotspot.xml 53 | hs_err_pid* 54 | 55 | # Danger Temporary Files # 56 | danger_out.json 57 | -------------------------------------------------------------------------------- /CHANGELOG.md: -------------------------------------------------------------------------------- 1 | 13 | ## Master 14 | - Update README.md with guidance to enable auto-complete in Android Studio [@gianluz] - [#242](https://github.com/danger/kotlin/pull/242) 15 | - Update install script with Kotlin compiler 1.7.0 [@gianluz] - [#241](https://github.com/danger/kotlin/pull/241) 16 | - Add accessors for Danger reports [@417-72KI] - [#245](https://github.com/danger/kotlin/pull/245) 17 | 18 | # 1.2.0 19 | - Update `Kotlin` to `1.7.0` and added support for Apple Silicon Chipset [@gianluz] - [#231](https://github.com/danger/kotlin/pull/231) 20 | - Make user property nullable for cases when non BB user did a commit [@vchernyshov] 21 | - Make GitLab approvals_before_merge variable nullable [#227](https://github.com/danger/kotlin/pull/227) 22 | 23 | # 1.1.0 24 | 25 | - Add support of BitBucketCloud [@vchernyshov] - [#214](https://github.com/danger/kotlin/pull/214) 26 | - Make `force_remove_source_branch` nullable in GitLab Merge request entity [@davidbilik] - [#197](https://github.com/danger/kotlin/pull/197) 27 | - Make `lastReviewedCommit` nullable on BitBucket Server [@f-meloni] - [#211](https://github.com/danger/kotlin/pull/211) 28 | - Update `GitLabMergeRequest` model: add `squash` field [@sonulen] - [#212](https://github.com/danger/kotlin/pull/212) 29 | - Add Gitlab extensions for url's: to project, to file diff, to current version of file [@sonulen] - [#212](https://github.com/danger/kotlin/pull/212) 30 | - Upgrade action to use node14 [@eygraber] - [#215](https://github.com/danger/kotlin/pull/215) 31 | 32 | # 1.0.0-beta4, 1.0.0 33 | 34 | - Create the Danger main instance only once [@f-meloni] - [#185](https://github.com/danger/kotlin/pull/185) 35 | 36 | # 1.0.0-beta3 37 | 38 | - Coroutines compatibility [@gianluz] - [#177](https://github.com/danger/kotlin/pull/177) 39 | - Improving error message for when a DangerPlugin was not registered [@rojanthomas] - [#181](https://github.com/danger/kotlin/pull/181) 40 | - Fix body parameter in github models [@tegorov] - [#175](https://github.com/danger/kotlin/pull/175) 41 | 42 | # 1.0.0-beta2 43 | 44 | - Update kotlinx-datetime to 0.1.1 [@f-meloni] - [@gianluz] - [#167](https://github.com/danger/kotlin/pull/167) 45 | - Support GitLab different time zones on the JSON [@f-meloni] - [#169](https://github.com/danger/kotlin/pull/169) 46 | - Update Kotlin to 1.5.0 [@gianluz] - [#171](https://github.com/danger/kotlin/pull/171) 47 | 48 | # 1.0.0-beta 49 | 50 | - Support --help parameter [@f-meloni] - [#153](https://github.com/danger/kotlin/pull/155) 51 | - Update Kotlin to 1.4.10 [@gianluz] - [#140](https://github.com/danger/kotlin/pull/140) 52 | - Migrate from moshi to kotlinx serialization [@gianluz] - [#141](https://github.com/danger/kotlin/pull/141) 53 | - Fix incorrect url in install.sh script and in Dockerfile [@davidbilik] - [#144](https://github.com/danger/kotlin/pull/144) 54 | - Road to 1.0 - Refactor project structure [@gianluz] - [#142](https://github.com/danger/kotlin/pull/142) 55 | - Handle danger-js custom paths with parameter `--danger-js-path` [@f-meloni] - [#153](https://github.com/danger/kotlin/pull/153) 56 | - Update Kotlin to 1.4.20 [@gianluz] - [#148](https://github.com/danger/kotlin/pull/148) 57 | - Fix gitlab defaults following kotlinx serialisation [@gianluz] - [#146](https://github.com/danger/kotlin/pull/146) 58 | - Road to 1.0 - Migrate from java.util.Date to kotlinx.datetime [@gianluz] - [#147](https://github.com/danger/kotlin/pull/147) 59 | - Fix typo in Github Milestone serialization [@doodeec] - [#151](https://github.com/danger/kotlin/pull/151) 60 | - Use fixed commit of danger/kotlin repository in install.sh script [@davidbilik]- [#152](https://github.com/danger/kotlin/pull/152) 61 | - Update Kotlin to 1.4.31 [@gianluz] - [#160](https://github.com/danger/kotlin/pull/160) 62 | - Library resolver and plugin installer gradle plugin [@gianluz] - [#158](https://github.com/danger/kotlin/pull/158) 63 | 64 | # 0.7.1 65 | 66 | - Make milestone description optional [@f-meloni] - [#136](https://github.com/danger/kotlin/pull/136) 67 | - Optimise Dockerfile layers to make Danger-Kotlin faster when the image is pulled on CI [@f-meloni] - [#129](https://github.com/danger/kotlin/pull/129) 68 | - Add action.yml to make it possible to run locally through [act](https://github.com/nektos/act) [@mariusgreve] - [#135](https://github.com/danger/kotlin/pull/135) 69 | 70 | # 0.7.0 71 | 72 | - Add logger [@f-meloni] - [#126](https://github.com/danger/kotlin/pull/126) 73 | - Fix DangerKotlinScriptDefinition [@gianluz] - [#121](https://github.com/danger/kotlin/pull/121) 74 | - Update Kotlin to 1.4.0 [@uzzu][] - [#116](https://github.com/danger/kotlin/pull/116) 75 | - Fix crash at milestone.dueOn [@anton46][] - [#108](https://github.com/danger/kotlin/pull/119) 76 | 77 | # 0.6.1 78 | 79 | - Fix crash on milestone.closedAt [@anton46][] - [#108](https://github.com/danger/kotlin/pull/112) 80 | - Add abstraction for executing shell commands via `ShellExecutor` [@davidbilik][] - [#105](https://github.com/danger/kotlin/pull/105) 81 | 82 | # 0.6.0 83 | 84 | - Fix to allow for large GitHub id values [@brentwatson][] - [#108](https://github.com/danger/kotlin/pull/108) 85 | - Fix invalid parsing of changes in diff [@davidbilik][] - [#106](https://github.com/danger/kotlin/pull/106) 86 | - Add extensions for changed lines in Git [@davidbilik][] - [#102](https://github.com/danger/kotlin/pull/102) 87 | - Add exec function [@f-meloni][] - [#97](https://github.com/danger/kotlin/pull/97) 88 | - Add readFile function [@f-meloni][] - [#93](https://github.com/danger/kotlin/pull/93) 89 | - Github exposing user avatar [@gianluz] - [#96](https://github.com/danger/kotlin/pull/96) 90 | 91 | [@f-meloni]: https://github.com/f-meloni 92 | [@gianluz]: https://github.com/gianluz 93 | [@davidbilik]: https://github.com/davidbilik 94 | [@brentwatson]: https://github.com/brentwatson 95 | [@anton46]: https://github.com/anton46 96 | [@uzzu]: https://github.com/uzzu 97 | [@mariusgreve]: https://github.com/mariusgreve 98 | [@tegorov]: https://github.com/tegorov 99 | [@rojanthomas]: https://github.com/rojanthomas 100 | [@eygraber]: https://github.com/eygraber 101 | [@417-72KI]: https://github.com/417-72KI 102 | -------------------------------------------------------------------------------- /CODEOWNERS: -------------------------------------------------------------------------------- 1 | * @f-meloni @gianluz @vacxe 2 | -------------------------------------------------------------------------------- /Dangerfile.df.kts: -------------------------------------------------------------------------------- 1 | @file:Repository("https://repo.maven.apache.org/maven2/") 2 | @file:DependsOn("org.apache.commons:commons-text:1.6") 3 | 4 | import systems.danger.kotlin.* 5 | 6 | danger(args) { 7 | // Empty dangerFile 8 | } 9 | -------------------------------------------------------------------------------- /Dangerfile_ci.df.kts: -------------------------------------------------------------------------------- 1 | // Dangerfile.df.kts 2 | /* 3 | * Use external dependencies using the following annotations: 4 | */ 5 | @file:Repository("https://repo.maven.apache.org/maven2/") 6 | @file:DependsOn("org.apache.commons:commons-text:1.6") 7 | 8 | //Testing plugin 9 | @file:DependsOn("danger-kotlin-sample-plugin-sample.jar") 10 | @file:OptIn(kotlin.time.ExperimentalTime::class) 11 | 12 | import kotlinx.coroutines.async 13 | import kotlinx.coroutines.delay 14 | import kotlinx.coroutines.runBlocking 15 | import kotlinx.datetime.Clock 16 | import org.apache.commons.text.WordUtils 17 | import systems.danger.kotlin.* 18 | import systems.danger.kotlin.models.danger.DangerDSL 19 | import systems.danger.samples.plugin.SamplePlugin 20 | 21 | register plugin SamplePlugin 22 | 23 | danger(args) { 24 | val allSourceFiles = git.modifiedFiles + git.createdFiles 25 | val changelogChanged = allSourceFiles.contains("CHANGELOG.md") 26 | val sourceChanges = allSourceFiles.firstOrNull { it.contains("src") } 27 | 28 | SamplePlugin.myCustomCheck() 29 | 30 | onGitHub { 31 | val isTrivial = pullRequest.title.contains("#trivial") 32 | 33 | // Changelog 34 | if (!isTrivial && !changelogChanged && sourceChanges != null) { 35 | warn(WordUtils.capitalize("any changes to library code should be reflected in the Changelog.\n\nPlease consider adding a note there and adhere to the [Changelog Guidelines](https://github.com/Moya/contributors/blob/master/Changelog%20Guidelines.md).")) 36 | } 37 | 38 | // Big PR Check 39 | if ((pullRequest.additions ?: 0) - (pullRequest.deletions ?: 0) > 300) { 40 | warn("Big PR, try to keep changes smaller if you can") 41 | } 42 | 43 | // Work in progress check 44 | if (pullRequest.title.contains("WIP", false)) { 45 | warn("PR is classed as Work in Progress") 46 | } 47 | } 48 | 49 | onGit { 50 | //No Java files check 51 | createdFiles.filter { 52 | it.endsWith(".java") 53 | }.forEach { 54 | // Using apache commons-text dependency to be sure the dependency resolution always works 55 | warn(WordUtils.capitalize("please consider to create new files in Kotlin"), it, 1) 56 | } 57 | } 58 | 59 | // Coroutines checks in parallel test 60 | val before = Clock.System.now() 61 | runBlocking { 62 | async { expensiveCheck("1", 1000) } 63 | async { expensiveCheck("2", 3000) } 64 | async { expensiveCheck("3", 2000) } 65 | async { expensiveCheck("4", 5000) } 66 | } 67 | val after = Clock.System.now() 68 | val runningTime = after.minus(before) 69 | message("Coroutines checks terminated - runningFor $runningTime") 70 | 71 | if ((fails + warnings).isEmpty()) { 72 | message(":rocket: No errors or warnings!") 73 | } 74 | } 75 | 76 | suspend fun DangerDSL.expensiveCheck(name: String, runForMillis: Long) { 77 | // Example expensive check 78 | delay(runForMillis) 79 | message("Coroutine $name terminated in $runForMillis ms") 80 | } 81 | -------------------------------------------------------------------------------- /Dockerfile: -------------------------------------------------------------------------------- 1 | FROM eclipse-temurin:23-jdk 2 | 3 | MAINTAINER Konstantin Aksenov 4 | 5 | LABEL "com.github.actions.name"="Danger Kotlin" 6 | LABEL "com.github.actions.description"="Runs Kotlin Dangerfiles" 7 | LABEL "com.github.actions.icon"="zap" 8 | LABEL "com.github.actions.color"="blue" 9 | 10 | ARG KOTLINC_VERSION="2.0.21" 11 | ARG DANGER_KOTLIN_VERSION="1.3.3" 12 | ARG DANGER_JS_VERSION="12.3.3" 13 | 14 | # Install dependencies 15 | RUN apt-get update 16 | RUN apt-get install -y npm nodejs wget unzip git 17 | 18 | # Install Kotlin compiler 19 | RUN wget -q "https://github.com/JetBrains/kotlin/releases/download/v$KOTLINC_VERSION/kotlin-compiler-$KOTLINC_VERSION.zip" && \ 20 | unzip "kotlin-compiler-$KOTLINC_VERSION.zip" -d /usr/lib && \ 21 | rm "kotlin-compiler-$KOTLINC_VERSION.zip" 22 | ENV PATH $PATH:/usr/lib/kotlinc/bin 23 | 24 | # Install Danger-JS 25 | RUN npm install -g "danger@$DANGER_JS_VERSION" 26 | 27 | # Install Danger-Kotlin 28 | RUN wget -q "https://github.com/danger/kotlin/releases/download/$DANGER_KOTLIN_VERSION/danger-kotlin-linuxX64.tar" && \ 29 | tar -xvf "danger-kotlin-linuxX64.tar" -C /usr/local && \ 30 | rm "danger-kotlin-linuxX64.tar" 31 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2018-2022 Danger 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | 23 | -------------------------------------------------------------------------------- /Makefile: -------------------------------------------------------------------------------- 1 | TOOL_NAME = danger-kotlin 2 | 3 | PREFIX = /usr/local 4 | INSTALL_PATH = $(PREFIX)/bin/$(TOOL_NAME) 5 | BUILD_PATH = danger-kotlin/build/bin/runner/releaseExecutable/$(TOOL_NAME).kexe 6 | LIB_INSTALL_PATH = $(PREFIX)/lib/danger 7 | LIB_FLAT_DIR = $(LIB_INSTALL_PATH)/libs 8 | 9 | install: build 10 | mkdir -p $(PREFIX)/bin 11 | mkdir -p $(LIB_INSTALL_PATH) 12 | mkdir -p $(LIB_FLAT_DIR) 13 | cp -f $(BUILD_PATH) $(INSTALL_PATH) 14 | cp -f danger-kotlin-library/build/libs/danger-kotlin.jar $(LIB_INSTALL_PATH)/danger-kotlin.jar 15 | 16 | build: 17 | ./gradlew build -p danger-plugin-installer 18 | ./gradlew publishToMavenLocal -p danger-plugin-installer 19 | ./gradlew shadowJar -p danger-kotlin-library 20 | ./gradlew build -p danger-kotlin-kts 21 | ./gradlew build -p danger-kotlin 22 | 23 | uninstall: 24 | rm -rf $(INSTALL_PATH) 25 | rm -f $(LIB_INSTALL_PATH)/danger-kotlin.jar 26 | rm -rf $(LIB_FLAT_DIR) 27 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | [![Current 2 | Version](https://img.shields.io/badge/danger%20kotlin-v1.3.3-orange)](https://danger.systems/kotlin/) 3 | [![Maven Central - SDK](https://img.shields.io/maven-central/v/systems.danger/danger-kotlin-sdk.svg?label=danger-kotlin-sdk)](https://search.maven.org/search?q=g:%22systems.danger%22%20AND%20a:%22danger-kotlin-sdk%22) 4 | [![Awesome Kotlin Badge](https://kotlin.link/awesome-kotlin.svg)](https://github.com/KotlinBy/awesome-kotlin) 5 | 6 |

7 |
8 | ⚠️ Stop saying "you forgot to …" in code review in Kotlin 9 |

10 | 11 | # Project status 12 | The project is now on a stable version. 13 | Is possible to generate a working `danger-kotlin` instance from the code that is currently on this repo, or use it via GitHub actions or `brew`. 14 | 15 | ### What it looks like today 16 | You can make a `Dangerfile.df.kts` in your root project that looks through PR metadata, it's fully typed. 17 | 18 | ```kotlin 19 | import systems.danger.kotlin.* 20 | 21 | danger(args) { 22 | 23 | val allSourceFiles = git.modifiedFiles + git.createdFiles 24 | val changelogChanged = allSourceFiles.contains("CHANGELOG.md") 25 | val sourceChanges = allSourceFiles.firstOrNull { it.contains("src") } 26 | 27 | onGitHub { 28 | val isTrivial = pullRequest.title.contains("#trivial") 29 | 30 | // Changelog 31 | if (!isTrivial && !changelogChanged && sourceChanges != null) { 32 | warn(WordUtils.capitalize("any changes to library code should be reflected in the Changelog.\n\nPlease consider adding a note there and adhere to the [Changelog Guidelines](https://github.com/Moya/contributors/blob/master/Changelog%20Guidelines.md).")) 33 | } 34 | 35 | // Big PR Check 36 | if ((pullRequest.additions ?: 0) - (pullRequest.deletions ?: 0) > 300) { 37 | warn("Big PR, try to keep changes smaller if you can") 38 | } 39 | 40 | // Work in progress check 41 | if (pullRequest.title.contains("WIP", false)) { 42 | warn("PR is classed as Work in Progress") 43 | } 44 | } 45 | } 46 | ``` 47 | 48 | ### Setup 49 | 50 | ### Docker 51 | ```sh 52 | docker run --rm -v "${PWD}:/code" -w /code ghcr.io/danger/danger-kotlin:1.3.3 danger-kotlin local 53 | ``` 54 | 55 | #### macOS (ARM) 56 | ```sh 57 | brew install danger/tap/danger-kotlin 58 | ``` 59 | 60 | #### macOS (Intel) 61 | ```sh 62 | brew install danger/tap/danger-kotlin-intel 63 | ``` 64 | 65 | You need to have Xcode installed and not relying on command line tools. 66 | If you're seeing this error when running xcodebuild: 67 | 68 | ```sh 69 | $ xcodebuild -version 70 | xcode-select: error: tool 'xcodebuild' requires Xcode, but active developer directory '/Library/Developer/CommandLineTools' is a command line tools instance 71 | ``` 72 | 73 | You can fix it with: 74 | 75 | ```sh 76 | sudo xcode-select -s /Applications/Xcode.app/Contents/Developer 77 | ``` 78 | 79 | ### Linux 80 | ```sh 81 | bash <(curl -s https://raw.githubusercontent.com/danger/kotlin/master/scripts/install.sh) 82 | source ~/.bash_profile 83 | ``` 84 | 85 | ### GitHub Actions 86 | You can add danger/kotlin to your actions 87 | 88 | Parameters: 89 | * `dangerfile`: Path to danger file, required: `false`, default: `Dangerfile.df.kts` 90 | * `run-mode`: Run mode: `ci`, `local`, `pr`, required: `false` default: `ci` 91 | * `job-id:` Reported CI job ID, required: `false`, default: `danger/kotlin` 92 | * `args`: Extra custom arguments like "--failOnErrors --no-publish-check" and etc, required: `false` 93 | 94 | ```yml 95 | jobs: 96 | build: 97 | runs-on: ubuntu-latest 98 | name: "Run Danger" 99 | steps: 100 | - uses: actions/checkout@v4 101 | - name: Danger 102 | uses: danger/kotlin@1.3.3 103 | env: 104 | GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} 105 | ``` 106 | 107 | Danger a pre built images that you can use with your action: 108 | 109 | https://github.com/orgs/danger/packages/container/package/danger-kotlin 110 | In order to import one of those use the docker:// prefix 111 | 112 | ```yml 113 | jobs: 114 | build: 115 | runs-on: ubuntu-latest 116 | name: "Run Danger" 117 | container: 118 | image: docker://ghcr.io/danger/danger-kotlin:1.3.3 119 | steps: 120 | - uses: actions/checkout@v4 121 | - name: Run Danger 122 | run: danger-kotlin ci --failOnErrors --no-publish-check 123 | env: 124 | GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} 125 | ``` 126 | 127 | ### Autocomplete and Syntax highlighting in IntelliJ IDEA or Android Studio 128 | You can activate the autocomplete following this additional steps: 129 | - Install danger on your local machine 130 | - Go to `Preferences -> Build, Execution, Deployment -> Compiler -> Kotlin Compiler` (`Preferences -> Kotlin Compiler` in Android Studio, Recent Android Studio versions will show this option when you close all project and open the Settings from the initial screen) 131 | - At the bottom you will find a section `Kotlin Scripting` 132 | - Complete the field `Script template classes` with `systems.danger.kts.DangerFileScript` 133 | - Complete the field `Script templates classpath` with `/usr/local/lib/danger/danger-kotlin.jar` 134 | - Go to `Preferences -> Language & Frameworks -> Kotlin -> Kotlin Scripting` 135 | - Make sure the script template `DangerFileScript` is active and above the default `Kotlin Script` 136 | - Apply changes 137 | - If opening the `Dangerfile.df.kts` the autocomplete and syntax highlighting doesn't work, try to reboot your IDE or open the Dangerfile from your IDE as a single file. 138 | - If it still doesn't work, go to `Help -> Edit Custom VM Options...` and add `-Dkotlin.script.classpath=/danger-kotlin.jar` (replace the `` with the absolute path to the JAR). Then restart the IDE. 139 | - You may also need to disable the "K2 mode" (search the settings for it) if you enabled it previously. 140 | 141 | 142 | ### Using external maven dependencies into your Dangerfile 143 | You can use any external dependency by adding the following lines at the top of your `Dangerfile.df.kts` 144 | ```kotlin 145 | @file:Repository("https://repo.maven.apache.org") 146 | @file:DependsOn("groupId:artifactId:version") 147 | ``` 148 | 149 | ### Commands 150 | 151 | - `danger-kotlin ci` - Use this on CI 152 | - `danger-kotlin pr https://github.com/Moya/Harvey/pull/23` - Use this to build your Dangerfile 153 | - `danger-kotlin local` - Use this to run danger against your local changes from master 154 | 155 | # Authors 156 | `danger-kotlin` was developed by [@gianluz][] and [@f-meloni][] 157 | 158 | [@f-meloni]: https://github.com/f-meloni 159 | [@gianluz]: https://github.com/gianluz 160 | -------------------------------------------------------------------------------- /action.yml: -------------------------------------------------------------------------------- 1 | name: 'danger-kotlin' 2 | description: 'Stop saying "you forgot to …" in code review' 3 | author: 'Konstantin Aksenov' 4 | branding: 5 | icon: 'check-square' 6 | color: 'green' 7 | inputs: 8 | dangerfile: 9 | description: 'Path to danger file' 10 | required: false 11 | default: 'Dangerfile.df.kts' 12 | run-mode: 13 | description: 'Run mode: ci, local, pr' 14 | required: false 15 | default: 'ci' 16 | job-id: 17 | description: 'Reported CI job ID' 18 | required: false 19 | default: 'danger/kotlin' 20 | args: 21 | description: 'Extra custom arguments like "--failOnErrors --no-publish-check" and etc' 22 | required: false 23 | 24 | runs: 25 | using: 'docker' 26 | image: 'github-action/Dockerfile' 27 | args: 28 | - ${{ inputs.dangerfile }} 29 | - ${{ inputs.run-mode }} 30 | - ${{ inputs.job-id }} 31 | - ${{ inputs.args }} 32 | -------------------------------------------------------------------------------- /build.gradle: -------------------------------------------------------------------------------- 1 | buildscript { 2 | repositories { 3 | mavenCentral() 4 | gradlePluginPortal() 5 | } 6 | 7 | dependencies { 8 | classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:$kotlinVersion" 9 | classpath "org.jetbrains.kotlin:kotlin-serialization:$kotlinVersion" 10 | classpath 'gradle.plugin.com.github.johnrengelman:shadow:7.1.2' 11 | classpath "io.codearte.gradle.nexus:gradle-nexus-staging-plugin:0.21.2" 12 | } 13 | } 14 | 15 | apply from: file('secrets.gradle') 16 | apply plugin: 'io.codearte.nexus-staging' 17 | 18 | nexusStaging { 19 | packageGroup = "systems.danger" 20 | username = loadSecret("SONATYPE_USER") 21 | password = loadSecret("SONATYPE_PASS") 22 | delayBetweenRetriesInMillis = 5000 23 | } 24 | 25 | allprojects { 26 | repositories { 27 | mavenCentral() 28 | maven { 29 | url = "https://kotlin.bintray.com/kotlinx/" 30 | } 31 | } 32 | } 33 | 34 | -------------------------------------------------------------------------------- /configurations.gradle: -------------------------------------------------------------------------------- 1 | configurations { 2 | implementation.extendsFrom includeJar { 3 | transitive = false 4 | } 5 | api.extendsFrom includeRecursiveJar { 6 | transitive = true 7 | } 8 | } 9 | -------------------------------------------------------------------------------- /danger-kotlin-kts/build.gradle: -------------------------------------------------------------------------------- 1 | buildscript { 2 | repositories { 3 | gradlePluginPortal() 4 | } 5 | dependencies { 6 | classpath 'gradle.plugin.com.github.johnrengelman:shadow:7.1.2' 7 | } 8 | } 9 | 10 | plugins { 11 | id 'org.jetbrains.kotlin.jvm' 12 | id 'maven-publish' 13 | } 14 | 15 | apply plugin: 'com.github.johnrengelman.shadow' 16 | 17 | apply from: file('../dependencyVersions.gradle') 18 | apply from: file('version.gradle') 19 | 20 | dependencies { 21 | implementation "org.jetbrains.kotlin:kotlin-stdlib-jdk8" 22 | implementation "org.jetbrains.kotlin:kotlin-script-runtime:$versionKotlin" 23 | implementation "org.jetbrains.kotlinx:kotlinx-coroutines-core:1.6.2" 24 | 25 | includeJar "org.jetbrains.kotlin:kotlin-main-kts:$versionKotlin" 26 | } 27 | 28 | shadowJar { 29 | getArchiveBaseName().set('danger-kotlin-kts') 30 | } 31 | 32 | compileKotlin { 33 | kotlinOptions.jvmTarget = "1.8" 34 | } 35 | compileTestKotlin { 36 | kotlinOptions.jvmTarget = "1.8" 37 | } 38 | 39 | tasks.withType(JavaCompile).configureEach { 40 | sourceCompatibility = JavaVersion.VERSION_1_8 41 | targetCompatibility = JavaVersion.VERSION_1_8 42 | } 43 | 44 | publishing { 45 | publications { 46 | maven(MavenPublication) { 47 | from components.java 48 | } 49 | } 50 | } 51 | -------------------------------------------------------------------------------- /danger-kotlin-kts/src/main/kotlin/systems/danger/kts/DangerFileScriptDefinition.kt: -------------------------------------------------------------------------------- 1 | package systems.danger.kts 2 | 3 | import org.jetbrains.kotlin.mainKts.* 4 | import java.io.File 5 | import kotlin.script.dependencies.ScriptContents 6 | import kotlin.script.dependencies.ScriptDependenciesResolver 7 | import kotlin.script.experimental.annotations.KotlinScript 8 | import kotlin.script.experimental.api.* 9 | import kotlin.script.experimental.dependencies.* 10 | import kotlin.script.experimental.dependencies.maven.MavenDependenciesResolver 11 | import kotlin.script.experimental.host.FileBasedScriptSource 12 | import kotlin.script.experimental.host.FileScriptSource 13 | import kotlin.script.experimental.impl.internalScriptingRunSuspend 14 | import kotlin.script.experimental.jvm.compat.mapLegacyDiagnosticSeverity 15 | import kotlin.script.experimental.jvm.compat.mapLegacyScriptPosition 16 | import kotlin.script.experimental.jvm.dependenciesFromClassContext 17 | import kotlin.script.experimental.jvm.jvm 18 | import kotlin.script.experimental.jvm.updateClasspath 19 | import kotlin.script.experimental.jvmhost.jsr223.configureProvidedPropertiesFromJsr223Context 20 | import kotlin.script.experimental.jvmhost.jsr223.importAllBindings 21 | import kotlin.script.experimental.jvmhost.jsr223.jsr223 22 | import kotlin.script.experimental.util.filterByAnnotationType 23 | 24 | @Suppress("unused") 25 | @KotlinScript( 26 | fileExtension = "df.kts", 27 | compilationConfiguration = DangerFileScriptDefinition::class, 28 | evaluationConfiguration = MainKtsEvaluationConfiguration::class, 29 | hostConfiguration = MainKtsHostConfiguration::class 30 | ) 31 | abstract class DangerFileScript(val args: Array) 32 | 33 | object DangerFileScriptDefinition : ScriptCompilationConfiguration( 34 | { 35 | defaultImports(DependsOn::class, Repository::class, Import::class, CompilerOptions::class, ScriptFileLocation::class) 36 | jvm { 37 | dependenciesFromClassContext( 38 | DangerFileScriptDefinition::class, 39 | "danger-kotlin", 40 | "kotlin-stdlib", 41 | "kotlin-reflect" 42 | ) 43 | } 44 | refineConfiguration { 45 | onAnnotations( 46 | DependsOn::class, 47 | Repository::class, 48 | Import::class, 49 | CompilerOptions::class, 50 | handler = DangerFileKtsConfigurator() 51 | ) 52 | onAnnotations(ScriptFileLocation::class, handler = ScriptFileLocationCustomConfigurator()) 53 | beforeCompiling(::configureScriptFileLocationPathVariablesForCompilation) 54 | beforeCompiling(::configureProvidedPropertiesFromJsr223Context) 55 | } 56 | ide { 57 | acceptedLocations(ScriptAcceptedLocation.Everywhere) 58 | } 59 | jsr223 { 60 | importAllBindings(true) 61 | } 62 | } 63 | ) 64 | 65 | class DangerFileKtsConfigurator : RefineScriptCompilationConfigurationHandler { 66 | private val externalDependenciesResolvers = setOf( 67 | MavenDependenciesResolver() 68 | ) 69 | private val resolvers = DANGER_DEFAULT_FLAT_DIRS 70 | .map { File(it) } 71 | .filter { it.exists() } 72 | .map { FileSystemDependenciesResolver(it) } + 73 | FileSystemDependenciesResolver() + 74 | externalDependenciesResolvers 75 | 76 | private val resolver = CompoundDependenciesResolver(resolvers) 77 | 78 | override operator fun invoke(context: ScriptConfigurationRefinementContext): ResultWithDiagnostics = 79 | processAnnotations(context) 80 | 81 | fun processAnnotations(context: ScriptConfigurationRefinementContext): ResultWithDiagnostics { 82 | val diagnostics = arrayListOf() 83 | 84 | fun report(severity: ScriptDependenciesResolver.ReportSeverity, message: String, position: ScriptContents.Position?) { 85 | diagnostics.add( 86 | ScriptDiagnostic( 87 | ScriptDiagnostic.unspecifiedError, 88 | message, 89 | mapLegacyDiagnosticSeverity(severity), 90 | context.script.locationId, 91 | mapLegacyScriptPosition(position) 92 | ) 93 | ) 94 | } 95 | 96 | val annotations = context.collectedData?.get(ScriptCollectedData.collectedAnnotations)?.takeIf { it.isNotEmpty() } 97 | ?: return context.compilationConfiguration.asSuccess() 98 | 99 | val scriptBaseDir = (context.script as? FileBasedScriptSource)?.file?.parentFile 100 | val importedSources = linkedMapOf>() 101 | var hasImportErrors = false 102 | annotations.filterByAnnotationType().forEach { scriptAnnotation -> 103 | scriptAnnotation.annotation.paths.forEach { sourceName -> 104 | val file = (scriptBaseDir?.resolve(sourceName) ?: File(sourceName)).normalize() 105 | val keyPath = file.absolutePath 106 | val prevImport = importedSources.put(keyPath, file to sourceName) 107 | if (prevImport != null) { 108 | diagnostics.add( 109 | ScriptDiagnostic( 110 | ScriptDiagnostic.unspecifiedError, "Duplicate imports: \"${prevImport.second}\" and \"$sourceName\"", 111 | sourcePath = context.script.locationId, location = scriptAnnotation.location?.locationInText 112 | ) 113 | ) 114 | hasImportErrors = true 115 | } 116 | } 117 | } 118 | if (hasImportErrors) return ResultWithDiagnostics.Failure(diagnostics) 119 | 120 | val compileOptions = annotations.filterByAnnotationType().flatMap { 121 | it.annotation.options.toList() 122 | } 123 | 124 | val resolveResult = try { 125 | @Suppress("DEPRECATION_ERROR") 126 | internalScriptingRunSuspend { 127 | resolver.resolveFromScriptSourceAnnotations(annotations.filter { it.annotation is DependsOn || it.annotation is Repository }) 128 | } 129 | } catch (e: Throwable) { 130 | diagnostics.add(e.asDiagnostics(path = context.script.locationId)) 131 | ResultWithDiagnostics.Failure(diagnostics) 132 | } 133 | 134 | return resolveResult.onSuccess { resolvedClassPath -> 135 | ScriptCompilationConfiguration(context.compilationConfiguration) { 136 | updateClasspath(resolvedClassPath) 137 | if (importedSources.isNotEmpty()) importScripts.append(importedSources.values.map { FileScriptSource(it.first) }) 138 | if (compileOptions.isNotEmpty()) compilerOptions.append(compileOptions) 139 | }.asSuccess() 140 | } 141 | } 142 | 143 | private companion object { 144 | val DANGER_DEFAULT_FLAT_DIRS = setOf( 145 | "/usr/local", // x86 location 146 | "/opt/local", // Arm 147 | "/opt/homebrew", // Homebrew Arm 148 | "/usr", // Fallback 149 | ).map { 150 | "$it/lib/danger/libs" 151 | } 152 | } 153 | } 154 | -------------------------------------------------------------------------------- /danger-kotlin-kts/version.gradle: -------------------------------------------------------------------------------- 1 | group 'systems.danger' 2 | version '1.3.3' 3 | -------------------------------------------------------------------------------- /danger-kotlin-library/build.gradle: -------------------------------------------------------------------------------- 1 | plugins { 2 | id 'org.jetbrains.kotlin.jvm' 3 | id 'org.jetbrains.kotlin.plugin.serialization' 4 | id 'maven-publish' 5 | } 6 | 7 | apply from: file('dependencies.gradle') 8 | apply from: file('version.gradle') 9 | apply plugin: 'kotlinx-serialization' 10 | 11 | shadowJar { 12 | getArchiveBaseName().set('danger-kotlin') 13 | getArchiveAppendix().set('') 14 | getArchiveClassifier().set('') 15 | getArchiveVersion().set('') 16 | } 17 | 18 | test { 19 | beforeTest { descriptor -> 20 | logger.lifecycle("Running test: " + descriptor) 21 | } 22 | } 23 | 24 | compileKotlin { 25 | kotlinOptions.jvmTarget = "1.8" 26 | } 27 | compileTestKotlin { 28 | kotlinOptions.jvmTarget = "1.8" 29 | } 30 | 31 | tasks.withType(JavaCompile).configureEach { 32 | sourceCompatibility = JavaVersion.VERSION_1_8 33 | targetCompatibility = JavaVersion.VERSION_1_8 34 | } 35 | 36 | publishing { 37 | publications { 38 | maven(MavenPublication) { 39 | from components.java 40 | } 41 | } 42 | } 43 | -------------------------------------------------------------------------------- /danger-kotlin-library/dependencies.gradle: -------------------------------------------------------------------------------- 1 | apply plugin: 'com.github.johnrengelman.shadow' 2 | apply from: file('../dependencyVersions.gradle') 3 | 4 | dependencies { 5 | implementation "org.jetbrains.kotlin:kotlin-stdlib-jdk8" 6 | includeJar project(":danger-kotlin-sdk") 7 | includeJar project(":danger-kotlin-kts") 8 | utilsDependencies() 9 | kotlinDependencies() 10 | testingDependencies() 11 | } 12 | -------------------------------------------------------------------------------- /danger-kotlin-library/gradle/wrapper/gradle-wrapper.properties: -------------------------------------------------------------------------------- 1 | distributionBase=GRADLE_USER_HOME 2 | distributionPath=wrapper/dists 3 | distributionUrl=https\://services.gradle.org/distributions/gradle-8.10.2-bin.zip 4 | zipStoreBase=GRADLE_USER_HOME 5 | zipStorePath=wrapper/dists 6 | -------------------------------------------------------------------------------- /danger-kotlin-library/gradlew.bat: -------------------------------------------------------------------------------- 1 | @rem 2 | @rem Copyright 2015 the original author or authors. 3 | @rem 4 | @rem Licensed under the Apache License, Version 2.0 (the "License"); 5 | @rem you may not use this file except in compliance with the License. 6 | @rem You may obtain a copy of the License at 7 | @rem 8 | @rem https://www.apache.org/licenses/LICENSE-2.0 9 | @rem 10 | @rem Unless required by applicable law or agreed to in writing, software 11 | @rem distributed under the License is distributed on an "AS IS" BASIS, 12 | @rem WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | @rem See the License for the specific language governing permissions and 14 | @rem limitations under the License. 15 | @rem 16 | 17 | @if "%DEBUG%" == "" @echo off 18 | @rem ########################################################################## 19 | @rem 20 | @rem Gradle startup script for Windows 21 | @rem 22 | @rem ########################################################################## 23 | 24 | @rem Set local scope for the variables with windows NT shell 25 | if "%OS%"=="Windows_NT" setlocal 26 | 27 | set DIRNAME=%~dp0 28 | if "%DIRNAME%" == "" set DIRNAME=. 29 | set APP_BASE_NAME=%~n0 30 | set APP_HOME=%DIRNAME% 31 | 32 | @rem Resolve any "." and ".." in APP_HOME to make it shorter. 33 | for %%i in ("%APP_HOME%") do set APP_HOME=%%~fi 34 | 35 | @rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. 36 | set DEFAULT_JVM_OPTS="-Xmx64m" "-Xms64m" 37 | 38 | @rem Find java.exe 39 | if defined JAVA_HOME goto findJavaFromJavaHome 40 | 41 | set JAVA_EXE=java.exe 42 | %JAVA_EXE% -version >NUL 2>&1 43 | if "%ERRORLEVEL%" == "0" goto execute 44 | 45 | echo. 46 | echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. 47 | echo. 48 | echo Please set the JAVA_HOME variable in your environment to match the 49 | echo location of your Java installation. 50 | 51 | goto fail 52 | 53 | :findJavaFromJavaHome 54 | set JAVA_HOME=%JAVA_HOME:"=% 55 | set JAVA_EXE=%JAVA_HOME%/bin/java.exe 56 | 57 | if exist "%JAVA_EXE%" goto execute 58 | 59 | echo. 60 | echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME% 61 | echo. 62 | echo Please set the JAVA_HOME variable in your environment to match the 63 | echo location of your Java installation. 64 | 65 | goto fail 66 | 67 | :execute 68 | @rem Setup the command line 69 | 70 | set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar 71 | 72 | 73 | @rem Execute Gradle 74 | "%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %* 75 | 76 | :end 77 | @rem End local scope for the variables with windows NT shell 78 | if "%ERRORLEVEL%"=="0" goto mainEnd 79 | 80 | :fail 81 | rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of 82 | rem the _cmd.exe /c_ return code! 83 | if not "" == "%GRADLE_EXIT_CONSOLE%" exit 1 84 | exit /b 1 85 | 86 | :mainEnd 87 | if "%OS%"=="Windows_NT" endlocal 88 | 89 | :omega 90 | -------------------------------------------------------------------------------- /danger-kotlin-library/src/main/kotlin/systems/danger/kotlin/KtxBitBucketServer.kt: -------------------------------------------------------------------------------- 1 | package systems.danger.kotlin 2 | 3 | // extensions over [BitBucketServer] object 4 | 5 | // No extensions implemented 6 | -------------------------------------------------------------------------------- /danger-kotlin-library/src/main/kotlin/systems/danger/kotlin/KtxDangerDSL.kt: -------------------------------------------------------------------------------- 1 | package systems.danger.kotlin 2 | 3 | import systems.danger.kotlin.models.bitbucket.BitBucketCloud 4 | import systems.danger.kotlin.models.bitbucket.BitBucketServer 5 | import systems.danger.kotlin.models.danger.DangerDSL 6 | import systems.danger.kotlin.models.git.Git 7 | import systems.danger.kotlin.models.github.GitHub 8 | import systems.danger.kotlin.models.gitlab.GitLab 9 | 10 | // extensions over [DangerDSL] object 11 | 12 | /** 13 | * Execute the block only if danger is running on GitHub 14 | * Example code: 15 | * ``` 16 | * danger(args) { 17 | * onGitHub { 18 | * ... 19 | * } 20 | * } 21 | * ``` 22 | * 23 | * @param onGitHub the block 24 | * @receiver the [GitHub] descriptor 25 | */ 26 | inline fun DangerDSL.onGitHub(onGitHub: GitHub.() -> Unit) { 27 | if (this.onGitHub) { 28 | github.run(onGitHub) 29 | } 30 | } 31 | 32 | /** 33 | * Execute the block only if danger is running on GitLab 34 | * Example code: 35 | * ``` 36 | * danger(args) { 37 | * onGitLab { 38 | * ... 39 | * } 40 | * } 41 | * ``` 42 | * 43 | * @param onGitLab the block 44 | * @receiver the [GitLab] descriptor 45 | */ 46 | inline fun DangerDSL.onGitLab(onGitLab: GitLab.() -> Unit) { 47 | if (this.onGitLab) { 48 | gitlab.run(onGitLab) 49 | } 50 | } 51 | 52 | /** 53 | * Execute the block only if danger is running on BitBucket 54 | * Example code: 55 | * ``` 56 | * danger(args) { 57 | * onBitBucket { 58 | * ... 59 | * } 60 | * } 61 | * ``` 62 | * 63 | * @param onBitBucket the block 64 | * @receiver the [BitBucketServer] descriptor 65 | */ 66 | inline fun DangerDSL.onBitBucket(onBitBucket: BitBucketServer.() -> Unit) { 67 | if (this.onBitBucketServer) { 68 | bitBucketServer.run(onBitBucket) 69 | } 70 | } 71 | 72 | /** 73 | * Execute the block only if danger is running on BitBucketCloud 74 | * Example code: 75 | * ``` 76 | * danger(args) { 77 | * onBitBucketCloud { 78 | * ... 79 | * } 80 | * } 81 | * ``` 82 | * 83 | * @param onBitBucket the block 84 | * @receiver the [BitBucketCloud] descriptor 85 | */ 86 | inline fun DangerDSL.onBitBucketCloud(onBitBucket: BitBucketCloud.() -> Unit) { 87 | if (this.onBitBucketCloud) { 88 | bitBucketCloud.run(onBitBucket) 89 | } 90 | } 91 | 92 | /** 93 | * Execute a [Git] block 94 | * Example code: 95 | * ``` 96 | * danger(args) { 97 | * onGit { 98 | * ... 99 | * } 100 | * } 101 | * ``` 102 | * 103 | * @param onGit the block 104 | * @receiver the [Git] descriptor 105 | */ 106 | inline fun DangerDSL.onGit(onGit: Git.() -> Unit) { 107 | git.run(onGit) 108 | } 109 | -------------------------------------------------------------------------------- /danger-kotlin-library/src/main/kotlin/systems/danger/kotlin/KtxGit.kt: -------------------------------------------------------------------------------- 1 | package systems.danger.kotlin 2 | 3 | import systems.danger.kotlin.models.git.Git 4 | import systems.danger.kotlin.models.git.GitCommit 5 | import systems.danger.kotlin.tools.shell.ShellExecutorFactory 6 | 7 | // extensions over [Git] object 8 | 9 | /** 10 | * Changed lines in this PR 11 | */ 12 | val Git.changedLines: PullRequestChangedLines 13 | get() { 14 | if (headSha == null || baseSha == null) return PullRequestChangedLines(0, 0) 15 | val shellExecutor = ShellExecutorFactory.get() 16 | val commandRawOutput = shellExecutor.execute("git diff --numstat $baseSha $headSha") 17 | val additionDeletionPairs = commandRawOutput.lines() 18 | .filter { it.isNotEmpty() } 19 | .map { line -> 20 | val parts = line.split("\\s+".toRegex()) 21 | (parts[0].toIntOrNull() ?: 0) to (parts[1].toIntOrNull() ?: 0) 22 | } 23 | val additions = additionDeletionPairs.fold(0) { acc, (addition, _) -> acc + addition } 24 | val deletions = additionDeletionPairs.fold(0) { acc, (_, deletion) -> acc + deletion } 25 | val commandRawDiffOutput = shellExecutor.execute("git diff $baseSha $headSha") 26 | return PullRequestChangedLines(additions, deletions, commandRawDiffOutput) 27 | } 28 | 29 | /** 30 | * Number of changed lines 31 | */ 32 | val Git.linesOfCode: Int 33 | get() = additions + deletions 34 | 35 | /** 36 | * Number of added lines 37 | */ 38 | val Git.additions: Int 39 | get() = changedLines.additions 40 | 41 | /** 42 | * Number of deleted lines 43 | */ 44 | val Git.deletions: Int 45 | get() = changedLines.deletions 46 | 47 | /** 48 | * Reference to a SHA of head commit of this PR 49 | */ 50 | val Git.headSha: String? 51 | get() = commits.sortChronologically().lastOrNull()?.sha 52 | 53 | /** 54 | * Reference to a SHA of base commit of this PR 55 | */ 56 | val Git.baseSha: String? 57 | get() = commits.sortChronologically().firstOrNull()?.sha?.let { "$it^1" } 58 | 59 | /** 60 | * Unified diff of this PR 61 | */ 62 | val Git.diff: String? 63 | get() = changedLines.diff 64 | 65 | /** 66 | * Wrapper for number of additions and deletions in currently processed Pull (or Merge) Request 67 | * 68 | * @param additions the number of additions 69 | * @param deletions the number of deletions 70 | * @param diff unified diff of the pr 71 | * @constructor Create empty PullRequestChangedLines 72 | */ 73 | data class PullRequestChangedLines( 74 | val additions: Int, 75 | val deletions: Int, 76 | val diff: String? = null 77 | ) 78 | 79 | private fun List.sortChronologically(): List { 80 | return sortedBy { it.author.date } 81 | } -------------------------------------------------------------------------------- /danger-kotlin-library/src/main/kotlin/systems/danger/kotlin/KtxGitHub.kt: -------------------------------------------------------------------------------- 1 | package systems.danger.kotlin 2 | 3 | // extensions over [GitHub] object 4 | -------------------------------------------------------------------------------- /danger-kotlin-library/src/main/kotlin/systems/danger/kotlin/KtxGitLab.kt: -------------------------------------------------------------------------------- 1 | package systems.danger.kotlin 2 | 3 | import systems.danger.kotlin.models.gitlab.GitLab 4 | import java.security.MessageDigest 5 | 6 | // extensions over [GitLab] object 7 | 8 | val GitLab.projectUrl: String 9 | get() = mergeRequest.webUrl.split("/merge_requests").first() 10 | 11 | fun GitLab.toWebUrl(filePath: String) = "$projectUrl/blob/${mergeRequest.sha}/${filePath}" 12 | 13 | fun GitLab.toWebDiffUrl(filePath: String) = "${mergeRequest.webUrl}/diffs#diff-content-${filePath.sha1}" 14 | 15 | val String.sha1: String 16 | get() = MessageDigest 17 | .getInstance("SHA-1") 18 | .digest(toByteArray()) 19 | .joinToString(separator = "") { "%02x".format(it) } 20 | -------------------------------------------------------------------------------- /danger-kotlin-library/src/main/kotlin/systems/danger/kotlin/MainDangerRunner.kt: -------------------------------------------------------------------------------- 1 | package systems.danger.kotlin 2 | 3 | import systems.danger.kotlin.json.JsonParser 4 | import systems.danger.kotlin.models.danger.ConcurrentDangerResults 5 | import systems.danger.kotlin.models.danger.DSL 6 | import systems.danger.kotlin.models.danger.DangerDSL 7 | import systems.danger.kotlin.models.git.FilePath 8 | import systems.danger.kotlin.sdk.DangerContext 9 | import systems.danger.kotlin.sdk.Violation 10 | import java.io.File 11 | import java.util.concurrent.atomic.AtomicReference 12 | 13 | /** 14 | * Main Danger runner 15 | * 16 | * @constructor Creates the main DangerContext 17 | * 18 | * @param jsonInputFilePath the input json file path (received from danger-js) 19 | * @param jsonOutputPath the output json file path used to publish the danger results on your Pull Request 20 | */ 21 | internal class MainDangerRunner(jsonInputFilePath: FilePath, jsonOutputPath: FilePath) : DangerContext { 22 | private val jsonOutputFile: File = File(jsonOutputPath) 23 | 24 | val danger: DangerDSL = JsonParser.decodeJson(jsonInputFilePath).danger 25 | 26 | private val concurrentDangerResults: ConcurrentDangerResults = ConcurrentDangerResults() 27 | 28 | override val fails: List 29 | get() { 30 | return concurrentDangerResults.fails.get() 31 | } 32 | override val warnings: List 33 | get() { 34 | return concurrentDangerResults.warnings.get() 35 | } 36 | override val messages: List 37 | get() { 38 | return concurrentDangerResults.messages.get() 39 | } 40 | override val markdowns: List 41 | get() { 42 | return concurrentDangerResults.markdowns.get() 43 | } 44 | 45 | 46 | // Collect the registered plugins and initialize with the DangerContext 47 | // then creates an output json file 48 | init { 49 | register.dangerPlugins.forEach { 50 | it.withContext(this) 51 | } 52 | commit() 53 | } 54 | 55 | override fun fail(message: String) { 56 | fail(Violation(message)) 57 | } 58 | 59 | override fun fail(message: String, file: FilePath, line: Int) { 60 | fail(Violation(message, file, line)) 61 | } 62 | 63 | override fun warn(message: String) { 64 | warn(Violation(message)) 65 | } 66 | 67 | override fun warn(message: String, file: FilePath, line: Int) { 68 | warn(Violation(message, file, line)) 69 | } 70 | 71 | override fun message(message: String) { 72 | message(Violation(message)) 73 | } 74 | 75 | override fun message(message: String, file: FilePath, line: Int) { 76 | message(Violation(message, file, line)) 77 | } 78 | 79 | override fun markdown(message: String) { 80 | markdown(Violation(message)) 81 | } 82 | 83 | override fun markdown(message: String, file: FilePath, line: Int) { 84 | markdown(Violation(message, file, line)) 85 | } 86 | 87 | override fun suggest(code: String, file: FilePath, line: Int) { 88 | if (runnerInstance.danger.onGitHub) { 89 | val message = "```suggestion\n $code \n```" 90 | markdown(Violation(message, file, line)) 91 | } else { 92 | val message = "```\n $code \n```" 93 | message(Violation(message)) 94 | } 95 | } 96 | 97 | private fun warn(violation: Violation) { 98 | concurrentDangerResults.warnings.updateWith(violation) 99 | } 100 | 101 | private fun fail(violation: Violation) { 102 | concurrentDangerResults.fails.updateWith(violation) 103 | } 104 | 105 | private fun message(violation: Violation) { 106 | concurrentDangerResults.messages.updateWith(violation) 107 | } 108 | 109 | private fun markdown(violation: Violation) { 110 | concurrentDangerResults.markdowns.updateWith(violation) 111 | } 112 | 113 | private fun AtomicReference>.updateWith(violation: Violation) { 114 | this.getAndUpdate { 115 | it.apply { 116 | add(violation) 117 | } 118 | } 119 | commit() 120 | } 121 | 122 | // commit all the inline violations into the json output file 123 | private fun commit() { 124 | synchronized(this) { 125 | JsonParser.encodeJson(concurrentDangerResults.toDangerResults(), jsonOutputFile) 126 | } 127 | } 128 | } 129 | -------------------------------------------------------------------------------- /danger-kotlin-library/src/main/kotlin/systems/danger/kotlin/MainPlugins.kt: -------------------------------------------------------------------------------- 1 | package systems.danger.kotlin 2 | 3 | import systems.danger.kotlin.sdk.DangerContext 4 | import systems.danger.kotlin.sdk.DangerPlugin 5 | 6 | /** 7 | * Register 8 | * Helps to register a [DangerPlugin] before usage. 9 | * contains the plugins to be registered within [DangerContext] 10 | * 11 | * @constructor Create empty Register helper 12 | */ 13 | object register { 14 | internal var dangerPlugins = mutableListOf() 15 | 16 | /** 17 | * Add a plugin to Danger 18 | * Example code: 19 | * ``` 20 | * register plugin DangerPluginName 21 | * 22 | * // Then 23 | * 24 | * danger(args) { 25 | * ... 26 | * } 27 | * ``` 28 | * 29 | * @param plugin the [DangerPlugin] to be registered 30 | */ 31 | infix fun plugin(plugin: DangerPlugin) { 32 | dangerPlugins.add(plugin) 33 | } 34 | 35 | /** 36 | * Add more than one plugin to Danger 37 | * Example code: 38 | * ``` 39 | * register.plugins(DangerPluginName1, DangerPluginName2, ...) 40 | * 41 | * // Then 42 | * 43 | * danger(args) { 44 | * ... 45 | * } 46 | * ``` 47 | * 48 | * @param pluginArgs the [DangerPlugin]s 49 | */ 50 | fun plugins(vararg pluginArgs: DangerPlugin) { 51 | dangerPlugins.addAll(pluginArgs) 52 | } 53 | } 54 | 55 | /** 56 | * Block that gave another option for registering plugins 57 | * Example code: 58 | * ``` 59 | * register { 60 | * plugin(DangerPlugin1) 61 | * plugin(DangerPlugin2) 62 | * plugins(DangerPlugin3, DangerPlugin4) 63 | * } 64 | * 65 | * // Then 66 | * 67 | * danger(args) { 68 | * ... 69 | * } 70 | * ``` 71 | * 72 | * @param block 73 | * @receiver [register] the registered plugins container 74 | */ 75 | inline fun register(block: register.() -> Unit) = register.run(block) 76 | 77 | /** 78 | * internal util function that assign the [DangerContext] to a target [DangerPlugin] 79 | * 80 | * @param dangerContext the [DangerContext] 81 | */ 82 | internal fun DangerPlugin.withContext(dangerContext: DangerContext) { 83 | registeredContext = dangerContext 84 | } 85 | -------------------------------------------------------------------------------- /danger-kotlin-library/src/main/kotlin/systems/danger/kotlin/MainScript.kt: -------------------------------------------------------------------------------- 1 | package systems.danger.kotlin 2 | 3 | import systems.danger.kotlin.models.danger.DangerDSL 4 | import systems.danger.kotlin.models.git.FilePath 5 | import systems.danger.kotlin.sdk.Violation 6 | 7 | internal var dangerRunner: MainDangerRunner? = null 8 | 9 | internal val runnerInstance: MainDangerRunner 10 | get() { 11 | if(dangerRunner != null) { 12 | return dangerRunner!! 13 | } else { 14 | throw IllegalArgumentException("Danger must be initialised before accessing it") 15 | } 16 | } 17 | 18 | /** 19 | * Syntactic sugar that allows you to work with a [DangerDSL] descriptor in a single Danger block. 20 | * Example code: 21 | * ``` 22 | * danger(args) { 23 | * ... 24 | * } 25 | * ``` 26 | * 27 | * @param args the DangerFile arguments, is always just args 28 | * @param block the [DangerDSL] block 29 | * @receiver the [DangerDSL] descriptor 30 | */ 31 | inline fun danger(args: Array, block: DangerDSL.() -> Unit) = Danger(args).run(block) 32 | 33 | /** 34 | * Create and return a [DangerDSL] descriptor 35 | * Example code: 36 | * ``` 37 | * val danger = Danger(args) 38 | * ``` 39 | * 40 | * @param args 41 | * @return a new [DangerDSL] descriptor 42 | */ 43 | fun Danger(args: Array): DangerDSL { 44 | if (dangerRunner == null) { 45 | val argsCount = args.count() 46 | 47 | val jsonInputFilePath = args[argsCount - 2] 48 | val jsonOutputPath = args[argsCount - 1] 49 | 50 | dangerRunner = MainDangerRunner(jsonInputFilePath, jsonOutputPath) 51 | } 52 | 53 | return dangerRunner!!.danger 54 | } 55 | 56 | /** 57 | * Adds an inline message message to the Danger report 58 | * 59 | * @param message the standard message 60 | */ 61 | fun message(message: String) = 62 | runnerInstance.message(message) 63 | 64 | /** 65 | * Adds an inline message message to the Danger report 66 | * 67 | * @param message the standard message 68 | * @param file the path to the target file 69 | * @param line the line number into the target file 70 | */ 71 | fun message(message: String, file: FilePath, line: Int) = 72 | runnerInstance.message(message, file, line) 73 | 74 | /** 75 | * Adds an inline markdown message to the Danger report 76 | * 77 | * @param message the markdown formatted message 78 | */ 79 | fun markdown(message: String) = 80 | runnerInstance.markdown(message) 81 | 82 | /** 83 | * Adds an inline markdown message to the Danger report 84 | * 85 | * @param message the markdown formatted message 86 | * @param file the path to the target file 87 | * @param line the line number into the target file 88 | */ 89 | fun markdown(message: String, file: FilePath, line: Int) = 90 | runnerInstance.markdown(message, file, line) 91 | 92 | /** 93 | * Adds an inline warning message to the Danger report 94 | * 95 | * @param message the warning message 96 | */ 97 | fun warn(message: String) = 98 | runnerInstance.warn(message) 99 | 100 | /** 101 | * Adds an inline warning message to the Danger report 102 | * 103 | * @param message the warning message 104 | * @param file the path to the target file 105 | * @param line the line number into the target file 106 | */ 107 | fun warn(message: String, file: FilePath, line: Int) = 108 | runnerInstance.warn(message, file, line) 109 | 110 | /** 111 | * Adds an inline fail message to the Danger report 112 | * 113 | * @param message the fail message 114 | */ 115 | fun fail(message: String) = 116 | runnerInstance.fail(message) 117 | 118 | /** 119 | * Adds an inline fail message to the Danger report 120 | * 121 | * @param message the fail message 122 | * @param file the path to the target file 123 | * @param line the line number into the target file 124 | */ 125 | fun fail(message: String, file: FilePath, line: Int) = 126 | runnerInstance.fail(message, file, line) 127 | 128 | /** 129 | * Adds an inline suggested code message to the Danger report 130 | * 131 | * @param code the suggested code 132 | * @param file the path to the target file 133 | * @param line the line number into the target file 134 | */ 135 | fun suggest(code: String, file: FilePath, line: Int) = 136 | runnerInstance.suggest(code, file, line) 137 | 138 | /** Fails on the Danger report */ 139 | val fails: List 140 | get() = runnerInstance.fails.toList() 141 | 142 | /** Warnings on the Danger report */ 143 | val warnings: List 144 | get() = runnerInstance.warnings.toList() 145 | 146 | /** Messages on the Danger report */ 147 | val messages: List 148 | get() = runnerInstance.messages.toList() 149 | 150 | /** Markdowns on the Danger report */ 151 | val markdowns: List 152 | get() = runnerInstance.markdowns.toList() 153 | -------------------------------------------------------------------------------- /danger-kotlin-library/src/main/kotlin/systems/danger/kotlin/json/JsonParser.kt: -------------------------------------------------------------------------------- 1 | package systems.danger.kotlin.json 2 | 3 | import kotlinx.serialization.decodeFromString 4 | import kotlinx.serialization.encodeToString 5 | import kotlinx.serialization.json.Json 6 | import systems.danger.kotlin.models.git.FilePath 7 | import java.io.File 8 | 9 | // internal Json parser to decode and encode jsons from/to danger-js 10 | internal object JsonParser { 11 | 12 | private val decodeJsonParser = Json { 13 | ignoreUnknownKeys = true 14 | isLenient = true 15 | } 16 | 17 | private val encodeJsonParser = Json { 18 | encodeDefaults = true 19 | } 20 | 21 | inline fun decodeJson(path: FilePath): T { 22 | return decodeJsonParser.decodeFromString(path.readText()) 23 | } 24 | 25 | inline fun encodeJson(value: T, outputFile: File) { 26 | val jsonString = encodeJsonParser.encodeToString(value) 27 | outputFile.writeText(jsonString) 28 | } 29 | 30 | private fun FilePath.readText() = File(this).readText() 31 | } 32 | -------------------------------------------------------------------------------- /danger-kotlin-library/src/main/kotlin/systems/danger/kotlin/models/bitbucket/BitBucketCloud.kt: -------------------------------------------------------------------------------- 1 | @file:UseSerializers(DateSerializer::class) 2 | 3 | package systems.danger.kotlin.models.bitbucket 4 | 5 | import kotlinx.datetime.Instant 6 | import kotlinx.serialization.SerialName 7 | import kotlinx.serialization.Serializable 8 | import kotlinx.serialization.UseSerializers 9 | import systems.danger.kotlin.models.serializers.DateSerializer 10 | 11 | @Serializable 12 | data class BitBucketCloud( 13 | val metadata: BitBucketMetadata, 14 | @SerialName("pr") 15 | val pullRequest: PullRequest, 16 | val commits: List, 17 | val comments: List, 18 | val activities: List 19 | ) { 20 | 21 | @Serializable 22 | data class Activity( 23 | val comment: Comment? = null 24 | ) 25 | 26 | @Serializable 27 | data class Comment( 28 | val id: Int, 29 | val content: Content, 30 | @SerialName("created_on") 31 | val createdOn: Instant, 32 | @SerialName("updated_on") 33 | val updatedOn: Instant, 34 | val deleted: Boolean, 35 | val user: User? = null, 36 | ) 37 | 38 | @Serializable 39 | data class Content( 40 | val html: String, 41 | val markup: String, 42 | val raw: String 43 | ) 44 | 45 | @Serializable 46 | data class User( 47 | val uuid: String, 48 | @SerialName("account_id") 49 | val accountId: String? = null, 50 | @SerialName("display_name") 51 | val displayName: String? = null, 52 | val nickname: String? = null 53 | ) 54 | 55 | @Serializable 56 | data class Commit( 57 | val hash: String, 58 | val author: Author, 59 | val date: Instant, 60 | val message: String 61 | ) { 62 | 63 | @Serializable 64 | data class Author( 65 | val raw: String, 66 | val user: User? = null 67 | ) 68 | } 69 | 70 | @Serializable 71 | data class PullRequest( 72 | val id: Int, 73 | val title: String, 74 | val author: User, 75 | val description: String, 76 | @SerialName("close_source_branch") 77 | val closeSourceBranch : Boolean, 78 | @SerialName("created_on") 79 | val createdOn: Instant, 80 | @SerialName("updated_on") 81 | val updatedOn: Instant, 82 | @SerialName("task_count") 83 | val taskCount: Int, 84 | @SerialName("comment_count") 85 | val commentCount: Int, 86 | val participants: List, 87 | val reviewers: List, 88 | val source: MergeRef, 89 | val destination: MergeRef, 90 | val state: State, 91 | val summary: Content, 92 | ) { 93 | @Serializable 94 | enum class State { 95 | OPEN, MERGED, SUPERSEDED, DECLINED 96 | } 97 | 98 | @Serializable 99 | data class Participant( 100 | val approved: Boolean, 101 | val role: Role, 102 | val user: User? = null 103 | ) { 104 | 105 | @Serializable 106 | enum class Role { 107 | REVIEWER, PARTICIPANT 108 | } 109 | } 110 | } 111 | 112 | @Serializable 113 | data class MergeRef( 114 | val branch: Branch, 115 | val commit: Commit, 116 | val repository: Repo 117 | ) { 118 | 119 | @Serializable 120 | data class Branch( 121 | val name: String 122 | ) 123 | 124 | @Serializable 125 | data class Commit( 126 | val hash: String 127 | ) 128 | } 129 | 130 | @Serializable 131 | data class Repo( 132 | val uuid: String, 133 | val name: String, 134 | @SerialName("full_name") 135 | val fullName: String 136 | ) 137 | } -------------------------------------------------------------------------------- /danger-kotlin-library/src/main/kotlin/systems/danger/kotlin/models/bitbucket/BitBucketMetadata.kt: -------------------------------------------------------------------------------- 1 | package systems.danger.kotlin.models.bitbucket 2 | 3 | import kotlinx.serialization.Serializable 4 | 5 | /** 6 | * The pull request and repository metadata 7 | * @property pullRequestId The PR's ID 8 | * @property repoSlug The complete repo slug including project slug. 9 | */ 10 | @Serializable 11 | data class BitBucketMetadata internal constructor( 12 | val pullRequestID: String, 13 | val repoSlug: String 14 | ) -------------------------------------------------------------------------------- /danger-kotlin-library/src/main/kotlin/systems/danger/kotlin/models/danger/DangerDSL.kt: -------------------------------------------------------------------------------- 1 | package systems.danger.kotlin.models.danger 2 | 3 | import kotlinx.serialization.* 4 | import systems.danger.kotlin.models.bitbucket.BitBucketCloud 5 | import systems.danger.kotlin.models.bitbucket.BitBucketServer 6 | import systems.danger.kotlin.models.git.Git 7 | import systems.danger.kotlin.models.github.GitHub 8 | import systems.danger.kotlin.models.gitlab.GitLab 9 | 10 | @Serializable 11 | internal data class DSL( 12 | val danger: DangerDSL 13 | ) 14 | 15 | @Serializable 16 | data class DangerDSL( 17 | @SerialName("github") 18 | private val _github: GitHub? = null, 19 | @SerialName("bitbucket_server") 20 | private val _bitBucketServer: BitBucketServer? = null, 21 | @SerialName("bitbucket_cloud") 22 | private val _bitBucketCloud: BitBucketCloud? = null, 23 | @SerialName("gitlab") 24 | private val _gitlab: GitLab? = null, 25 | val git: Git 26 | ) { 27 | val github: GitHub 28 | get() = _github!! 29 | val bitBucketServer: BitBucketServer 30 | get() = _bitBucketServer!! 31 | val bitBucketCloud: BitBucketCloud 32 | get() = _bitBucketCloud!! 33 | val gitlab: GitLab 34 | get() = _gitlab!! 35 | 36 | val onGitHub 37 | get() = _github != null 38 | val onBitBucketServer 39 | get() = _bitBucketServer != null 40 | val onBitBucketCloud 41 | get() = _bitBucketCloud != null 42 | val onGitLab 43 | get() = _gitlab != null 44 | 45 | val utils: Utils 46 | get() = Utils() 47 | } 48 | -------------------------------------------------------------------------------- /danger-kotlin-library/src/main/kotlin/systems/danger/kotlin/models/danger/DangerResults.kt: -------------------------------------------------------------------------------- 1 | @file:UseSerializers(ViolationSerializer::class) 2 | 3 | package systems.danger.kotlin.models.danger 4 | 5 | import kotlinx.serialization.Serializable 6 | import kotlinx.serialization.UseSerializers 7 | import systems.danger.kotlin.sdk.Violation 8 | import systems.danger.kotlin.models.serializers.ViolationSerializer 9 | import java.util.concurrent.atomic.AtomicReference 10 | 11 | @Serializable 12 | internal data class Meta( 13 | val runtimeName: String = "Danger Kotlin", 14 | val runtimeHref: String = "https://danger.systems" 15 | ) 16 | 17 | @Serializable 18 | internal data class DangerResults( 19 | val fails: List, 20 | val warnings: List, 21 | val messages: List, 22 | val markdowns: List, 23 | val meta: Meta = Meta() 24 | ) 25 | 26 | internal data class ConcurrentDangerResults( 27 | val fails: AtomicReference> = AtomicReference(mutableListOf()), 28 | val warnings: AtomicReference> = AtomicReference(mutableListOf()), 29 | val messages: AtomicReference> = AtomicReference(mutableListOf()), 30 | val markdowns: AtomicReference> = AtomicReference(mutableListOf()) 31 | ) { 32 | fun toDangerResults() = DangerResults( 33 | fails.get(), warnings.get(), messages.get(), markdowns.get() 34 | ) 35 | } 36 | -------------------------------------------------------------------------------- /danger-kotlin-library/src/main/kotlin/systems/danger/kotlin/models/danger/Utils.kt: -------------------------------------------------------------------------------- 1 | package systems.danger.kotlin.models.danger 2 | 3 | import systems.danger.kotlin.tools.shell.ShellExecutorFactory 4 | import java.io.File 5 | 6 | class Utils { 7 | /** 8 | * Let's you go from a file path to the contents of the file with less hassle. 9 | * 10 | * @param path the file reference from git.modified/creasted/deleted etc 11 | * @return the file contents 12 | */ 13 | fun readFile(path: String): String { 14 | return File(path).readText() 15 | } 16 | 17 | /** 18 | * Gives you the ability to cheaply run a command and read the output without having to mess around 19 | * 20 | * @param command The first part of the command 21 | * @param arguments An optional list of arguments to pass in extra 22 | * @return the stdout from the command 23 | */ 24 | fun exec(command: String, arguments: List = emptyList()): String { 25 | return ShellExecutorFactory.get().execute(command, arguments) 26 | } 27 | } 28 | -------------------------------------------------------------------------------- /danger-kotlin-library/src/main/kotlin/systems/danger/kotlin/models/git/Git.kt: -------------------------------------------------------------------------------- 1 | package systems.danger.kotlin.models.git 2 | 3 | import kotlinx.serialization.* 4 | 5 | typealias FilePath = String 6 | 7 | /** 8 | * The git specific metadata for a pull request 9 | * 10 | * @property modifiedFiles Modified file paths relative to the git root. 11 | * @property createdFiles Newly created file paths relative to the git root. 12 | * @property deletedFiles Removed file paths relative to the git root. 13 | */ 14 | @Serializable 15 | data class Git( 16 | @SerialName("modified_files") val modifiedFiles: List, 17 | @SerialName("created_files") val createdFiles: List, 18 | @SerialName("deleted_files") val deletedFiles: List, 19 | @SerialName("commits") val commits: List 20 | ) 21 | 22 | /** 23 | * A platform agnostic reference to a git commit. 24 | * 25 | * @property sha The SHA for the commit. 26 | * @property author Who wrote the commit. 27 | * @property committer Who shipped the code. 28 | * @property message The message for the commit. 29 | * @property parents SHAs for the commit's parents. 30 | * @property url The URL for the commit. 31 | */ 32 | @Serializable 33 | data class GitCommit( 34 | val sha: String? = null, 35 | val author: GitCommitAuthor, 36 | val committer: GitCommitAuthor, 37 | val message: String, 38 | val parents: List = listOf(), 39 | val url: String 40 | ) 41 | 42 | /** 43 | * A platform agnostic reference to a git commit. 44 | * 45 | * @property name The display name for the author. 46 | * @property email The email for the author. 47 | * @property date The ISO8601 date string for the commit. 48 | */ 49 | @Serializable 50 | data class GitCommitAuthor( 51 | val name: String, 52 | val email: String, 53 | val date: String 54 | ) 55 | -------------------------------------------------------------------------------- /danger-kotlin-library/src/main/kotlin/systems/danger/kotlin/models/gitlab/GitLab.kt: -------------------------------------------------------------------------------- 1 | @file:UseSerializers(DateSerializer::class) 2 | 3 | package systems.danger.kotlin.models.gitlab 4 | 5 | import kotlinx.datetime.Instant 6 | import kotlinx.serialization.SerialName 7 | import kotlinx.serialization.Serializable 8 | import kotlinx.serialization.UseSerializers 9 | import systems.danger.kotlin.models.serializers.DateSerializer 10 | 11 | @Serializable 12 | data class GitLab( 13 | @SerialName("mr") 14 | val mergeRequest: GitLabMergeRequest, 15 | val metadata: GitLabMetadata 16 | ) 17 | 18 | @Serializable 19 | data class GitLabDiffRefs( 20 | @SerialName("base_sha") 21 | val baseSha: String, 22 | @SerialName("head_sha") 23 | val headSha: String, 24 | @SerialName("start_sha") 25 | val startSha: String 26 | ) 27 | 28 | @Serializable 29 | data class GitLabUserMergeData( 30 | @SerialName("can_merge") 31 | val canMerge: Boolean 32 | ) 33 | 34 | @Serializable 35 | data class GitLabMergeRequest( 36 | @SerialName("allow_collaboration") 37 | val allowCollaboration: Boolean = false, 38 | @SerialName("allow_maintainer_to_push") 39 | val allowMaintainerToPush: Boolean = false, 40 | @SerialName("approvals_before_merge") 41 | val approvalsBeforeMerge: Int? = 0, 42 | val assignee: GitLabUser? = null, 43 | val author: GitLabUser, 44 | @SerialName("changes_count") 45 | val changesCount: String, 46 | @SerialName("closed_at") 47 | val closedAt: Instant? = null, 48 | @SerialName("closed_by") 49 | val closedBy: GitLabUser? = null, 50 | val description: String? = null, 51 | @SerialName("diff_refs") 52 | val diffRefs: GitLabDiffRefs, 53 | val downvotes: Int, 54 | @SerialName("first_deployed_to_production_at") 55 | val firstDeployedToProductionAt: Instant? = null, 56 | @SerialName("force_remove_source_branch") 57 | val forceRemoveSourceBranch: Boolean? = null, 58 | val id: Int, 59 | val iid: Int, 60 | @SerialName("latest_build_finished_at") 61 | 62 | val latestBuildFinishedAt: Instant? = null, 63 | @SerialName("latest_build_started_at") 64 | 65 | val latestBuildStartedAt: Instant? = null, 66 | val labels: List, 67 | @SerialName("merge_commit_sha") 68 | val mergeCommitSha: String? = null, 69 | @SerialName("merged_at") 70 | val mergedAt: Instant? = null, 71 | @SerialName("merged_by") 72 | val mergedBy: GitLabUser? = null, 73 | @SerialName("merge_when_pipeline_succeeds") 74 | val mergeOnPipelineSuccess: Boolean, 75 | val milestone: GitLabMilestone? = null, 76 | @SerialName("head_pipeline") 77 | val pipeline: GitLabPipeline, 78 | @SerialName("project_id") 79 | val projectId: String, 80 | val sha: String, 81 | @SerialName("should_remove_source_branch") 82 | val shouldRemoveSourceBranch: Boolean? = false, 83 | @SerialName("source_branch") 84 | val sourceBranch: String, 85 | @SerialName("source_project_id") 86 | val sourceProjectId: String, 87 | val state: GitLabMergeRequestState, 88 | val subscribed: Boolean, 89 | @SerialName("target_branch") 90 | val targetBranch: String, 91 | @SerialName("target_project_id") 92 | val targetProjectId: String, 93 | val timeStats: GitLabMergeRequestTimeStats? = null, 94 | val title: String, 95 | val upvotes: Int, 96 | @SerialName("user") 97 | private val userMergeData: GitLabUserMergeData, 98 | @SerialName("user_notes_count") 99 | val userNotesCount: Int, 100 | @SerialName("web_url") 101 | val webUrl: String, 102 | @SerialName("work_in_progress") 103 | val workInProgress: Boolean, 104 | val squash: Boolean, 105 | ) { 106 | val canMerge: Boolean 107 | get() = this.userMergeData.canMerge 108 | } 109 | 110 | @Serializable 111 | data class GitLabMergeRequestTimeStats( 112 | @SerialName("human_time_estimate") 113 | val humanTimeEstimate: Int = 0, 114 | @SerialName("human_time_spent") 115 | val humanTimeSpent: Int = 0, 116 | @SerialName("time_estimate") 117 | val timeEstimate: Int, 118 | @SerialName("total_time_spent") 119 | val totalTimeSpent: Int 120 | ) 121 | 122 | @Serializable 123 | data class GitLabMetadata( 124 | val pullRequestID: String, 125 | val repoSlug: String 126 | ) 127 | 128 | @Serializable 129 | enum class GitLabMergeRequestState { 130 | @SerialName("closed") 131 | CLOSED, 132 | 133 | @SerialName("locked") 134 | LOCKED, 135 | 136 | @SerialName("merged") 137 | MERGED, 138 | 139 | @SerialName("opened") 140 | OPENED 141 | } 142 | 143 | @Serializable 144 | data class GitLabMilestone( 145 | @SerialName("created_at") 146 | val createdAt: Instant, 147 | val description: String, 148 | @SerialName("due_date") 149 | val dueDate: Instant, 150 | val id: Int, 151 | val iid: Int, 152 | @SerialName("project_id") 153 | val projectID: Int, 154 | @SerialName("start_date") 155 | val startDate: Instant, 156 | val state: GitLabMilestoneState, 157 | val title: String, 158 | @SerialName("updated_at") 159 | val updatedAt: Instant, 160 | @SerialName("web_url") 161 | val webUrl: String 162 | ) 163 | 164 | @Serializable 165 | enum class GitLabMilestoneState { 166 | @SerialName("active") 167 | ACTIVE, 168 | 169 | @SerialName("closed") 170 | CLOSED 171 | } 172 | 173 | @Serializable 174 | data class GitLabPipeline( 175 | val id: Int, 176 | val ref: String, 177 | val sha: String, 178 | val status: GitLabPipelineStatus, 179 | @SerialName("web_url") 180 | val webUrl: String 181 | ) 182 | 183 | @Serializable 184 | enum class GitLabPipelineStatus { 185 | @SerialName("canceled") 186 | CANCELLED, 187 | 188 | @SerialName("failed") 189 | FAILED, 190 | 191 | @SerialName("pending") 192 | PENDING, 193 | 194 | @SerialName("running") 195 | RUNNING, 196 | 197 | @SerialName("skipped") 198 | SKIPPED, 199 | 200 | @SerialName("success") 201 | SUCCESS 202 | } 203 | 204 | @Serializable 205 | data class GitLabUser( 206 | @SerialName("avatar_url") 207 | val avatarUrl: String? = null, 208 | val id: Int, 209 | val name: String, 210 | val state: GitLabUserState, 211 | val username: String, 212 | @SerialName("web_url") 213 | val webUrl: String 214 | ) 215 | 216 | @Serializable 217 | enum class GitLabUserState { 218 | @SerialName("active") 219 | ACTIVE, 220 | 221 | @SerialName("blocked") 222 | BLOCKED 223 | } 224 | -------------------------------------------------------------------------------- /danger-kotlin-library/src/main/kotlin/systems/danger/kotlin/models/serializers/DateSerializer.kt: -------------------------------------------------------------------------------- 1 | package systems.danger.kotlin.models.serializers 2 | 3 | import kotlinx.datetime.Instant 4 | import kotlinx.serialization.ExperimentalSerializationApi 5 | import kotlinx.serialization.KSerializer 6 | import kotlinx.serialization.Serializer 7 | import kotlinx.serialization.descriptors.PrimitiveKind 8 | import kotlinx.serialization.descriptors.PrimitiveSerialDescriptor 9 | import kotlinx.serialization.descriptors.SerialDescriptor 10 | import kotlinx.serialization.encoding.Decoder 11 | import kotlinx.serialization.encoding.Encoder 12 | import java.text.SimpleDateFormat 13 | import java.util.* 14 | 15 | @ExperimentalSerializationApi 16 | @Serializer(forClass = DateSerializer::class) 17 | object DateSerializer : KSerializer { 18 | 19 | override val descriptor: SerialDescriptor 20 | get() = PrimitiveSerialDescriptor("kotlinx.datetime.Instant", PrimitiveKind.STRING) 21 | 22 | override fun serialize(encoder: Encoder, value: Instant) { 23 | // Implementation not needed for now 24 | } 25 | 26 | override fun deserialize(decoder: Decoder): Instant { 27 | val value = decoder.decodeString() 28 | 29 | return try { 30 | Instant.parse(value) 31 | } catch(e: Throwable) { 32 | Instant.fromEpochMilliseconds(ISO8601DateFormat().parse(value).toInstant().toEpochMilli()) 33 | } 34 | } 35 | } 36 | 37 | class ISO8601DateFormat { 38 | fun parse(string: String?): Date { 39 | val formatter = SimpleDateFormat( 40 | "yyyy-MM-dd'T'HH:mm:ss.SSSXXX" 41 | ).apply { 42 | timeZone = TimeZone.getTimeZone("UTC") 43 | } 44 | 45 | return formatter.parse(string.toString()) 46 | } 47 | 48 | } 49 | -------------------------------------------------------------------------------- /danger-kotlin-library/src/main/kotlin/systems/danger/kotlin/models/serializers/ViolationSerializer.kt: -------------------------------------------------------------------------------- 1 | package systems.danger.kotlin.models.serializers 2 | 3 | import kotlinx.serialization.ExperimentalSerializationApi 4 | import kotlinx.serialization.KSerializer 5 | import kotlinx.serialization.Serializer 6 | import kotlinx.serialization.descriptors.SerialDescriptor 7 | import kotlinx.serialization.descriptors.buildClassSerialDescriptor 8 | import kotlinx.serialization.descriptors.element 9 | import kotlinx.serialization.encoding.* 10 | import systems.danger.kotlin.sdk.Violation 11 | 12 | @ExperimentalSerializationApi 13 | @Serializer(forClass = ViolationSerializer::class) 14 | object ViolationSerializer : KSerializer { 15 | override val descriptor: SerialDescriptor 16 | get() = buildClassSerialDescriptor( 17 | "systems.danger.kotlin.sdk.Violation" 18 | ) { 19 | element("message") 20 | element("file") 21 | element("line") 22 | } 23 | 24 | override fun deserialize(decoder: Decoder): Violation { 25 | return decoder.decodeStructure(descriptor) { 26 | lateinit var message: String 27 | var file: String? = null 28 | var line: Int? = null 29 | while (true) { 30 | when (val index = decodeElementIndex(descriptor)) { 31 | 0 -> message = decodeStringElement(descriptor, 0) 32 | 1 -> file = decodeStringElement(descriptor, 1) 33 | 2 -> line = decodeIntElement(descriptor, 2) 34 | CompositeDecoder.DECODE_DONE -> break 35 | else -> error("Unexpected index: $index") 36 | } 37 | } 38 | Violation(message, file, line) 39 | } 40 | } 41 | 42 | override fun serialize(encoder: Encoder, value: Violation) { 43 | encoder.encodeStructure(descriptor) { 44 | encodeStringElement(descriptor, 0, value.message) 45 | value.file?.let { 46 | encodeStringElement(descriptor, 1, it) 47 | } 48 | value.line?.let { 49 | encodeIntElement(descriptor, 2, it) 50 | } 51 | } 52 | } 53 | 54 | } 55 | -------------------------------------------------------------------------------- /danger-kotlin-library/src/main/kotlin/systems/danger/kotlin/tools/shell/ShellExecutor.kt: -------------------------------------------------------------------------------- 1 | package systems.danger.kotlin.tools.shell 2 | 3 | /** 4 | * ShellExecutor 5 | */ 6 | interface ShellExecutor { 7 | 8 | /** 9 | * Execute [command] with optional list of arguments and return output from 10 | * stdout 11 | * 12 | * @param command the shell command 13 | * @param arguments the list of arguments to be passed to [command] 14 | * @return the stdout output 15 | */ 16 | fun execute(command: String, arguments: List = emptyList()): String 17 | } 18 | 19 | // Internal implementation for ShellExecutor 20 | internal class ShellExecutorImpl : ShellExecutor { 21 | 22 | override fun execute(command: String, arguments: List): String { 23 | val commandWithArgs = command + if (arguments.isNotEmpty()) " " + arguments.joinToString(" ") else "" 24 | 25 | val process = Runtime.getRuntime().exec(arrayOf("/bin/bash", "-c", commandWithArgs)) 26 | process.waitFor() 27 | 28 | return process.inputStream.bufferedReader().readText() 29 | } 30 | } 31 | 32 | /** 33 | * ShellExecutorFactory 34 | * 35 | * @constructor Creates a ShellExecutorFactory 36 | */ 37 | object ShellExecutorFactory { 38 | 39 | private var shellExecutor: ShellExecutor = ShellExecutorImpl() 40 | 41 | // Useful for testing 42 | internal fun set(executor: ShellExecutor) { 43 | shellExecutor = executor 44 | } 45 | 46 | /** 47 | * Get the danger [ShellExecutor] 48 | */ 49 | fun get() = shellExecutor 50 | } 51 | -------------------------------------------------------------------------------- /danger-kotlin-library/src/test/kotlin/systems/danger/kotlin/KtxGitLabTest.kt: -------------------------------------------------------------------------------- 1 | package systems.danger.kotlin 2 | 3 | import kotlinx.serialization.decodeFromString 4 | import org.junit.Assert.assertEquals 5 | import org.junit.Test 6 | import systems.danger.kotlin.models.danger.DSL 7 | import systems.danger.kotlin.models.gitlab.GitLab 8 | import systems.danger.kotlin.utils.TestUtils 9 | 10 | /** 11 | * Tests for [GitLab] extensions 12 | */ 13 | internal class GitLabKtxTest { 14 | 15 | companion object { 16 | private const val filePath = "static/source/swift/guides/getting_started.html.slim" 17 | private const val projectUrl = "https://gitlab.com/danger-systems/danger.systems" 18 | private const val diffUrl = "$projectUrl/merge_requests/182/diffs#diff-content-6b0f4e17f37f5ea51f169e6e1eaa04a0151b17d1" 19 | private const val blobUrl = "$projectUrl/blob/621bc3348549e51c5bd6ea9f094913e9e4667c7b/$filePath" 20 | } 21 | 22 | private val dsl: DSL 23 | get() = TestUtils.Json.decodeFromString(TestUtils.JSONFiles.gitlabJSON) 24 | 25 | private val gitLab: GitLab 26 | get() = dsl.danger.gitlab 27 | 28 | @Test 29 | fun testProjectUrlAreCorrect() = assertEquals(projectUrl, gitLab.projectUrl) 30 | 31 | @Test 32 | fun testWebUrlAreCorrect() = assertEquals(blobUrl, gitLab.toWebUrl(filePath)) 33 | 34 | @Test 35 | fun testWebDiffUrlWithAnchorAreCorrect() = assertEquals(diffUrl, gitLab.toWebDiffUrl(filePath)) 36 | } 37 | -------------------------------------------------------------------------------- /danger-kotlin-library/src/test/kotlin/systems/danger/kotlin/KtxGitTest.kt: -------------------------------------------------------------------------------- 1 | package systems.danger.kotlin 2 | 3 | import io.mockk.every 4 | import io.mockk.mockk 5 | import org.junit.Assert.assertEquals 6 | import org.junit.Before 7 | import org.junit.Test 8 | import systems.danger.kotlin.models.git.Git 9 | import systems.danger.kotlin.models.git.GitCommit 10 | import systems.danger.kotlin.models.git.GitCommitAuthor 11 | import systems.danger.kotlin.tools.shell.ShellExecutorFactory 12 | 13 | /** 14 | * Tests for [Git] extensions 15 | */ 16 | internal class GitKtxTest { 17 | 18 | companion object { 19 | private val diffCommandOutput = """ 20 | 0 1 features/search/build.gradle 21 | 3 10 features/search/src/main/java/com/sampleapp/search/di/RepositoryModule.kt 22 | 2 4 features/search/src/main/java/com/sampleapp/search/model/ApiInteractor.kt 23 | 2 4 features/search/src/main/java/com/sampleapp/search/model/RecommendedPublishersRepository.kt 24 | 1 3 features/search/src/main/java/com/sampleapp/search/model/RecommendedTopicsRepository.kt 25 | """.trimIndent() 26 | } 27 | 28 | private val basicGit = Git( 29 | modifiedFiles = emptyList(), 30 | createdFiles = emptyList(), 31 | deletedFiles = emptyList(), 32 | commits = listOf( 33 | GitCommit( 34 | sha = "commit1", 35 | author = GitCommitAuthor("John Doe", "john@doe.com", "2024-11-28T13:41:53Z"), 36 | committer = GitCommitAuthor("John Doe", "john@doe.com", "2024-12-04T09:15:23Z"), 37 | message = "Random message", 38 | parents = listOf(), 39 | url = "" 40 | ), 41 | GitCommit( 42 | sha = "commit2", 43 | author = GitCommitAuthor("John Doe", "john@doe.com", "2024-11-28T13:54:45Z"), 44 | committer = GitCommitAuthor("John Doe", "john@doe.com", "2024-12-04T09:15:23Z"), 45 | message = "Random message", 46 | parents = listOf(), 47 | url = "" 48 | ) 49 | ) 50 | ) 51 | 52 | private val expectedResult = PullRequestChangedLines(8, 22, diffCommandOutput) 53 | 54 | @Before 55 | fun setup() { 56 | ShellExecutorFactory.set(mockk { 57 | every { execute(any(), any()) } returns diffCommandOutput 58 | }) 59 | } 60 | 61 | @Test 62 | fun testItCorrectlyParseDiff() { 63 | assertEquals(expectedResult, basicGit.changedLines) 64 | } 65 | 66 | @Test 67 | fun testAdditionsAreCorrect() { 68 | assertEquals(expectedResult.additions, basicGit.additions) 69 | } 70 | 71 | @Test 72 | fun testDeletionsAreCorrect() { 73 | assertEquals(expectedResult.deletions, basicGit.deletions) 74 | } 75 | 76 | @Test 77 | fun testLinesOfCodeAreCorrect() { 78 | assertEquals(expectedResult.deletions + expectedResult.additions, basicGit.linesOfCode) 79 | } 80 | 81 | @Test 82 | fun testNoChangedLinesForNoCommits() { 83 | val gitWOCommits = basicGit.copy(commits = emptyList()) 84 | assertEquals(PullRequestChangedLines(0, 0), gitWOCommits.changedLines) 85 | } 86 | 87 | @Test 88 | fun testBaseShaIsCorrect() { 89 | assertEquals("commit1^1", basicGit.baseSha) 90 | } 91 | 92 | @Test 93 | fun testHeadShaIsCorrect() { 94 | assertEquals("commit2", basicGit.headSha) 95 | } 96 | } -------------------------------------------------------------------------------- /danger-kotlin-library/src/test/kotlin/systems/danger/kotlin/UtilsTests.kt: -------------------------------------------------------------------------------- 1 | package systems.danger.kotlin 2 | 3 | import kotlinx.serialization.decodeFromString 4 | import org.junit.Assert 5 | import org.junit.Test 6 | import systems.danger.kotlin.models.danger.DSL 7 | import systems.danger.kotlin.utils.TestUtils.JSONFiles 8 | import systems.danger.kotlin.utils.TestUtils 9 | import java.io.File 10 | 11 | class UtilsTests { 12 | private val dsl: DSL = TestUtils.Json.decodeFromString(JSONFiles.githubDangerJSON) 13 | 14 | @Test 15 | fun testReadText() { 16 | val testFile = File("testFile") 17 | testFile.writeText("Test") 18 | 19 | Assert.assertEquals("Test", dsl.danger.utils.readFile("testFile")) 20 | 21 | testFile.delete() 22 | } 23 | } -------------------------------------------------------------------------------- /danger-kotlin-library/src/test/kotlin/systems/danger/kotlin/models/bitbucket/BitBucketCloudParsingTests.kt: -------------------------------------------------------------------------------- 1 | package systems.danger.kotlin.models.bitbucket 2 | 3 | import kotlinx.datetime.Instant 4 | import kotlinx.serialization.decodeFromString 5 | import org.junit.Assert.assertEquals 6 | import org.junit.Test 7 | import systems.danger.kotlin.models.danger.DSL 8 | import systems.danger.kotlin.utils.TestUtils 9 | import systems.danger.kotlin.utils.TestUtils.JSONFiles 10 | 11 | class BitBucketCloudParsingTests { 12 | 13 | private val dsl: DSL = TestUtils.Json.decodeFromString(JSONFiles.dangerBitBucketCloudJSON) 14 | 15 | private val bitBucketCloud: BitBucketCloud = dsl.danger.bitBucketCloud 16 | 17 | @Test 18 | fun testItParsesTheBitBucketPullRequest() { 19 | with(bitBucketCloud.pullRequest) { 20 | val expectedUser = BitBucketCloud.User( 21 | uuid = "test", 22 | accountId = "test", 23 | displayName = "Foo Bar", 24 | nickname = "foo.bar" 25 | ) 26 | assertEquals(expectedUser, author) 27 | 28 | val expectedDestination = BitBucketCloud.MergeRef( 29 | branch = BitBucketCloud.MergeRef.Branch( 30 | name = "destination", 31 | ), 32 | commit = BitBucketCloud.MergeRef.Commit( 33 | hash = "test" 34 | ), 35 | repository = BitBucketCloud.Repo( 36 | uuid = "test", 37 | name = "test", 38 | fullName = "test" 39 | ) 40 | ) 41 | assertEquals(expectedDestination, destination) 42 | 43 | val expectedSource = BitBucketCloud.MergeRef( 44 | branch = BitBucketCloud.MergeRef.Branch( 45 | name = "source", 46 | ), 47 | commit = BitBucketCloud.MergeRef.Commit( 48 | hash = "test" 49 | ), 50 | repository = BitBucketCloud.Repo( 51 | uuid = "test", 52 | name = "test", 53 | fullName = "test" 54 | ) 55 | ) 56 | assertEquals(expectedSource, source) 57 | 58 | val expectedParticipant = BitBucketCloud.PullRequest.Participant( 59 | approved = false, 60 | role = BitBucketCloud.PullRequest.Participant.Role.REVIEWER, 61 | user = BitBucketCloud.User( 62 | uuid = "danger", 63 | accountId = "danger", 64 | displayName = "Danger", 65 | nickname = "danger" 66 | ) 67 | ) 68 | assertEquals(1, participants.count()) 69 | assertEquals(expectedParticipant, participants[0]) 70 | 71 | assertEquals(Instant.parse("2022-04-07T15:19:56.209142+00:00"), createdOn) 72 | assertEquals(Instant.parse("2022-04-09T12:51:23.384574+00:00"), updatedOn) 73 | assertEquals(BitBucketCloud.PullRequest.State.OPEN, state) 74 | assertEquals("test", title) 75 | 76 | val expectedReviewer = BitBucketCloud.User( 77 | uuid = "danger", 78 | accountId = "danger", 79 | displayName = "Danger", 80 | nickname = "danger" 81 | ) 82 | assertEquals(1, reviewers.count()) 83 | assertEquals(expectedReviewer, reviewers.first()) 84 | } 85 | } 86 | 87 | @Test 88 | fun testItParsesTheBitBucketCommits() { 89 | with(bitBucketCloud.commits) { 90 | val expectedCommit = BitBucketCloud.Commit( 91 | hash = "test", 92 | author = BitBucketCloud.Commit.Author( 93 | raw = "test", 94 | user = BitBucketCloud.User( 95 | uuid = "test", 96 | accountId = "test", 97 | displayName = "Foo Bar", 98 | nickname = "foo.bar" 99 | ) 100 | ), 101 | date = Instant.parse("2022-04-07T15:18:09+00:00"), 102 | message = "test" 103 | ) 104 | assertEquals(1, count()) 105 | assertEquals(expectedCommit, first()) 106 | } 107 | } 108 | 109 | @Test 110 | fun testItParsesTheBitBucketComments() { 111 | with(bitBucketCloud.comments) { 112 | val expectedComment = BitBucketCloud.Comment( 113 | id = 1, 114 | content = BitBucketCloud.Content( 115 | html = "test", 116 | markup = "markdown", 117 | raw = "test" 118 | ), 119 | createdOn = Instant.parse("2022-04-07T15:25:25.184212+00:00"), 120 | updatedOn = Instant.parse("2022-04-07T15:25:25.184261+00:00"), 121 | deleted = false, 122 | user = BitBucketCloud.User( 123 | uuid = "danger", 124 | accountId = "danger", 125 | displayName = "Danger", 126 | nickname = "danger" 127 | ) 128 | ) 129 | 130 | assertEquals(1, count()) 131 | assertEquals(expectedComment, first()) 132 | } 133 | } 134 | 135 | @Test 136 | fun testItParsesTheBitBucketMetadata() { 137 | with(bitBucketCloud.metadata) { 138 | assertEquals("artsy/emission", repoSlug) 139 | assertEquals("327", pullRequestID) 140 | } 141 | } 142 | 143 | @Test 144 | fun testItParsesTheBitBucketActivities() { 145 | with(bitBucketCloud.activities) { 146 | val expectedActivity = BitBucketCloud.Activity( 147 | comment = BitBucketCloud.Comment( 148 | id = 1, 149 | content = BitBucketCloud.Content( 150 | html = "test", 151 | markup = "markdown", 152 | raw = "test" 153 | ), 154 | createdOn = Instant.parse("2022-04-07T15:25:25.184212+00:00"), 155 | updatedOn = Instant.parse("2022-04-07T15:25:25.184261+00:00"), 156 | deleted = false, 157 | user = BitBucketCloud.User( 158 | uuid = "danger", 159 | accountId = "danger", 160 | displayName = "Danger", 161 | nickname = "danger" 162 | ) 163 | ) 164 | ) 165 | 166 | assertEquals(1, count()) 167 | assertEquals(expectedActivity, first()) 168 | } 169 | } 170 | 171 | @Test 172 | fun testOnBitBucketCloudIsTrue() { 173 | assertEquals(true, dsl.danger.onBitBucketCloud) 174 | } 175 | 176 | @Test 177 | fun testOnBitBucketServerIsFalse() { 178 | assertEquals(false, dsl.danger.onBitBucketServer) 179 | } 180 | 181 | @Test 182 | fun testOnGitHubIsFalse() { 183 | assertEquals(false, dsl.danger.onGitHub) 184 | } 185 | 186 | @Test 187 | fun testOnGitLabIsFalse() { 188 | assertEquals(false, dsl.danger.onGitLab) 189 | } 190 | } 191 | -------------------------------------------------------------------------------- /danger-kotlin-library/src/test/kotlin/systems/danger/kotlin/models/bitbucket/BitBucketServerParsingTests.kt: -------------------------------------------------------------------------------- 1 | package systems.danger.kotlin.models.bitbucket 2 | 3 | import kotlinx.serialization.decodeFromString 4 | import org.junit.Assert.assertEquals 5 | import org.junit.Test 6 | import systems.danger.kotlin.models.danger.DSL 7 | import systems.danger.kotlin.utils.TestUtils.JSONFiles 8 | import systems.danger.kotlin.utils.TestUtils 9 | 10 | class BitBucketServerParsingTests { 11 | 12 | private val dsl: DSL = TestUtils.Json.decodeFromString(JSONFiles.dangerBitBucketServerJSON) 13 | 14 | private val bitBucketServer: BitBucketServer = dsl.danger.bitBucketServer 15 | 16 | @Test 17 | fun testItParsesTheBitBucketPullRequest() { 18 | with(bitBucketServer.pullRequest) { 19 | val expectedUser = BitBucketServerUser( 20 | null, 21 | "test", 22 | null, 23 | "user@email.com", 24 | true, 25 | null, 26 | BitBucketServerUser.Type.NORMAL 27 | ) 28 | assertEquals(expectedUser, author?.user) 29 | 30 | val expectedProject = 31 | BitBucketServerProject(1, "PROJ", "Project", false, "NORMAL") 32 | val expectedRepo = BitBucketServerRepo( 33 | "Repo", 34 | "repo", 35 | "git", 36 | false, 37 | true, 38 | expectedProject 39 | ) 40 | val expectedHead = BitBucketServerMergeRef( 41 | "refs/heads/foo", 42 | "foo", 43 | "d6725486c38d46a33e76f622cf24b9a388c8d13d", 44 | expectedRepo 45 | ) 46 | assertEquals(expectedHead, toRef) 47 | 48 | val expectedBase = BitBucketServerMergeRef( 49 | "refs/heads/master", 50 | "master", 51 | "8942a1f75e4c95df836f19ef681d20a87da2ee20", 52 | expectedRepo 53 | ) 54 | assertEquals(expectedBase, fromRef) 55 | 56 | val expectedPartecipant = BitBucketServerUser( 57 | 2, 58 | "danger", 59 | "DangerCI", 60 | "user@email.com", 61 | true, 62 | "danger", 63 | BitBucketServerUser.Type.NORMAL 64 | ) 65 | assertEquals(1, participants?.count()) 66 | assertEquals(expectedPartecipant, participants?.get(0)?.user) 67 | 68 | assertEquals(false, closed) 69 | assertEquals(1518863923273, createdAt) 70 | assertEquals(false, isLocked) 71 | assertEquals(true, open) 72 | assertEquals(BitBucketServerPR.State.OPEN, state) 73 | assertEquals("Pull request title", title) 74 | 75 | val expectedReviewerUser = BitBucketServerUser( 76 | 2, 77 | "danger", 78 | "DangerCI", 79 | "foo@bar.com", 80 | true, 81 | "danger", 82 | BitBucketServerUser.Type.NORMAL 83 | ) 84 | val expectedReviewer = BitBucketServerReviewer( 85 | expectedReviewerUser, 86 | true, 87 | "8942a1f75e4c95df836f19ef681d20a87da2ee20" 88 | ) 89 | assertEquals(1, reviewers?.count()) 90 | assertEquals(expectedReviewer, reviewers?.get(0)) 91 | } 92 | } 93 | 94 | @Test 95 | fun testItParsesTheBitBucketCommits() { 96 | with(bitBucketServer.commits) { 97 | val expectedUser = BitBucketServerUser( 98 | 2, 99 | "danger", 100 | "DangerCI", 101 | "foo@bar.com", 102 | true, 103 | "danger", 104 | BitBucketServerUser.Type.NORMAL 105 | ) 106 | val expectedParent = BitBucketServerCommitParent( 107 | "c62ada76533a2de045d4c6062988ba84df140729", 108 | "c62ada76533" 109 | ) 110 | val expectedCommit = BitBucketServerCommit( 111 | "d6725486c38d46a33e76f622cf24b9a388c8d13d", 112 | "d6725486c38", 113 | expectedUser, 114 | 1519442341000, 115 | expectedUser, 116 | 1519442341000, 117 | "Modify and remove files", 118 | listOf(expectedParent) 119 | ) 120 | assertEquals(expectedCommit, first()) 121 | assertEquals(2, count()) 122 | } 123 | } 124 | 125 | @Test 126 | fun testItParsesTheBitBucketComments() { 127 | with(bitBucketServer.comments) { 128 | val expectedUser = BitBucketServerUser( 129 | 2, 130 | "danger", 131 | "DangerCI", 132 | "foo@bar.com", 133 | true, 134 | "danger", 135 | BitBucketServerUser.Type.NORMAL 136 | ) 137 | val commentText = "test" 138 | val expectedProperty = 139 | BitBucketServerCommentInnerProperties(1, listOf()) 140 | val expectedCommentDetail = BitBucketServerCommentDetail( 141 | 10, 142 | 23, 143 | commentText, 144 | expectedUser, 145 | 1518939353345, 146 | 1519449132488, 147 | listOf(), 148 | expectedProperty, 149 | listOf() 150 | ) 151 | val expectedComment = BitBucketServerComment( 152 | 52, 153 | 1518939353345, 154 | expectedUser, 155 | "COMMENTED", 156 | null, 157 | null, 158 | null, 159 | null, 160 | "ADDED", 161 | expectedCommentDetail 162 | ) 163 | 164 | assertEquals(expectedComment.toString(), this[1].toString()) 165 | assertEquals(7, count()) 166 | } 167 | } 168 | 169 | @Test 170 | fun testItParsesTheBitBucketMetadata() { 171 | with(bitBucketServer.metadata) { 172 | assertEquals("artsy/emission", repoSlug) 173 | assertEquals("327", pullRequestID) 174 | } 175 | } 176 | 177 | @Test 178 | fun testItParsesTheBitBucketActivities() { 179 | with(bitBucketServer.activities) { 180 | val expectedUser = BitBucketServerUser( 181 | 1, 182 | "test", 183 | "test", 184 | "foo@bar.com", 185 | true, 186 | "test", 187 | BitBucketServerUser.Type.NORMAL 188 | ) 189 | val expectedActivity = BitBucketServerActivity( 190 | 61, 191 | 1519442356495, 192 | expectedUser, 193 | "RESCOPED", 194 | null 195 | ) 196 | 197 | assertEquals(expectedActivity, first()) 198 | assertEquals(7, count()) 199 | } 200 | } 201 | 202 | @Test 203 | fun testOnBitBucketServerIsTrue() { 204 | assertEquals(true, dsl.danger.onBitBucketServer) 205 | } 206 | 207 | @Test 208 | fun testOnBitBucketCloudIsFalse() { 209 | assertEquals(false, dsl.danger.onBitBucketCloud) 210 | } 211 | 212 | @Test 213 | fun testOnGitHubIsFalse() { 214 | assertEquals(false, dsl.danger.onGitHub) 215 | } 216 | 217 | @Test 218 | fun testOnGitLabIsFalse() { 219 | assertEquals(false, dsl.danger.onGitLab) 220 | } 221 | } 222 | -------------------------------------------------------------------------------- /danger-kotlin-library/src/test/kotlin/systems/danger/kotlin/models/git/GitParsingTests.kt: -------------------------------------------------------------------------------- 1 | package systems.danger.kotlin.models.git 2 | 3 | import kotlinx.serialization.decodeFromString 4 | import org.junit.Assert.* 5 | import org.junit.Test 6 | import systems.danger.kotlin.models.danger.DSL 7 | import systems.danger.kotlin.utils.TestUtils.JSONFiles 8 | import systems.danger.kotlin.utils.TestUtils 9 | 10 | class GitParsingTests { 11 | 12 | private val dsl: DSL = TestUtils.Json.decodeFromString(JSONFiles.githubDangerJSON) 13 | 14 | @Test 15 | fun testItParsesCorrectlyTheGitFiles() { 16 | val git = dsl.danger.git 17 | 18 | assertTrue(expectedModifiedFiles.containsAll(git.modifiedFiles.toList())) 19 | assertTrue(git.createdFiles[0] == ".ruby-version") 20 | } 21 | 22 | private val expectedModifiedFiles = listOf( 23 | ".travis.yml", 24 | "Kiosk.xcodeproj/project.pbxproj", 25 | "Kiosk/App/Logger.swift", 26 | "Kiosk/App/Networking/NetworkLogger.swift", 27 | "Kiosk/App/Networking/Networking.swift", 28 | "Kiosk/App/Networking/XAppToken.swift", 29 | "Kiosk/Auction Listings/ListingsViewModel.swift", 30 | "Kiosk/HelperFunctions.swift", 31 | "Kiosk/Images.xcassets/AppIcon.appiconset/Contents.json", 32 | "KioskTests/Bid Fulfillment/ConfirmYourBidArtsyLoginViewControllerTests.swift", 33 | "KioskTests/Bid Fulfillment/ConfirmYourBidEnterYourEmailViewControllerTests.swift", 34 | "KioskTests/Bid Fulfillment/LoadingViewControllerTests.swift", 35 | "KioskTests/Bid Fulfillment/RegistrationEmailViewControllerTests.swift", 36 | "KioskTests/Bid Fulfillment/RegistrationPasswordViewModelTests.swift", 37 | "KioskTests/Bid Fulfillment/SwipeCreditCardViewControllerTests.swift", 38 | "KioskTests/ListingsViewControllerTests.swift", 39 | "KioskTests/Models/SaleArtworkTests.swift", 40 | "KioskTests/ReferenceImages/ListingsViewControllerTests/when_displaying_stubbed_contents__with_artworks_not_for_sale__a_listings_controller__alphabetical@2x.png", 41 | "KioskTests/ReferenceImages/ListingsViewControllerTests/when_displaying_stubbed_contents__with_artworks_not_for_sale__a_listings_controller__grid@2x.png", 42 | "KioskTests/ReferenceImages/ListingsViewControllerTests/when_displaying_stubbed_contents__with_artworks_not_for_sale__a_listings_controller__highest_bid@2x.png", 43 | "KioskTests/ReferenceImages/ListingsViewControllerTests/when_displaying_stubbed_contents__with_artworks_not_for_sale__a_listings_controller__least_bids@2x.png", 44 | "KioskTests/ReferenceImages/ListingsViewControllerTests/when_displaying_stubbed_contents__with_artworks_not_for_sale__a_listings_controller__lowest_bid@2x.png", 45 | "KioskTests/ReferenceImages/ListingsViewControllerTests/when_displaying_stubbed_contents__with_artworks_not_for_sale__a_listings_controller__most_bids@2x.png", 46 | "KioskTests/ReferenceImages/ListingsViewControllerTests/when_displaying_stubbed_contents__with_lot_numbers__a_listings_controller__alphabetical@2x.png", 47 | "KioskTests/ReferenceImages/ListingsViewControllerTests/when_displaying_stubbed_contents__with_lot_numbers__a_listings_controller__grid@2x.png", 48 | "KioskTests/ReferenceImages/ListingsViewControllerTests/when_displaying_stubbed_contents__with_lot_numbers__a_listings_controller__highest_bid@2x.png", 49 | "KioskTests/ReferenceImages/ListingsViewControllerTests/when_displaying_stubbed_contents__with_lot_numbers__a_listings_controller__least_bids@2x.png", 50 | "KioskTests/ReferenceImages/ListingsViewControllerTests/when_displaying_stubbed_contents__with_lot_numbers__a_listings_controller__lowest_bid@2x.png", 51 | "KioskTests/ReferenceImages/ListingsViewControllerTests/when_displaying_stubbed_contents__with_lot_numbers__a_listings_controller__most_bids@2x.png", 52 | "KioskTests/ReferenceImages/ListingsViewControllerTests/when_displaying_stubbed_contents__without_lot_numbers__a_listings_controller__alphabetical@2x.png", 53 | "KioskTests/ReferenceImages/ListingsViewControllerTests/when_displaying_stubbed_contents__without_lot_numbers__a_listings_controller__grid@2x.png", 54 | "KioskTests/ReferenceImages/ListingsViewControllerTests/when_displaying_stubbed_contents__without_lot_numbers__a_listings_controller__highest_bid@2x.png", 55 | "KioskTests/ReferenceImages/ListingsViewControllerTests/when_displaying_stubbed_contents__without_lot_numbers__a_listings_controller__least_bids@2x.png", 56 | "KioskTests/ReferenceImages/ListingsViewControllerTests/when_displaying_stubbed_contents__without_lot_numbers__a_listings_controller__lowest_bid@2x.png", 57 | "KioskTests/ReferenceImages/ListingsViewControllerTests/when_displaying_stubbed_contents__without_lot_numbers__a_listings_controller__most_bids@2x.png", 58 | "KioskTests/ReferenceImages/LoadingViewControllerTests/default__placing_a_bid@2x.png", 59 | "KioskTests/ReferenceImages/LoadingViewControllerTests/ending__placing_bid_error_due_to_outbid@2x.png", 60 | "KioskTests/ReferenceImages/LoadingViewControllerTests/ending__placing_bid_succeeded_but_not_resolved@2x.png", 61 | "KioskTests/ReferenceImages/LoadingViewControllerTests/ending__placing_bid_success_highest@2x.png", 62 | "KioskTests/ReferenceImages/LoadingViewControllerTests/ending__placing_bid_success_not_highest@2x.png", 63 | "KioskTests/ReferenceImages/LoadingViewControllerTests/ending__registering_user_not_resolved@2x.png", 64 | "KioskTests/ReferenceImages/LoadingViewControllerTests/ending__registering_user_success@2x.png", 65 | "KioskTests/ReferenceImages/LoadingViewControllerTests/errors__correctly_placing_a_bid@2x.png", 66 | "KioskTests/ReferenceImages/RegistrationEmailViewControllerTests/looks_right_by_default@2x.png", 67 | "KioskTests/ReferenceImages/RegistrationEmailViewControllerTests/looks_right_with_existing_email@2x.png", 68 | "KioskTests/ReferenceImages/RegistrationMobileViewControllerTests/looks_right_by_default@2x.png", 69 | "KioskTests/ReferenceImages/RegistrationMobileViewControllerTests/looks_right_with_existing_mobile@2x.png", 70 | "KioskTests/ReferenceImages/RegistrationPasswordViewControllerTests/looks_right_with_a_valid_password@2x.png", 71 | "KioskTests/ReferenceImages/RegistrationPasswordViewControllerTests/looks_right_with_an_invalid_password@2x.png", 72 | "KioskTests/ReferenceImages/RegistrationPostalZipViewControllerTests/looks_right_by_default@2x.png", 73 | "KioskTests/ReferenceImages/RegistrationPostalZipViewControllerTests/looks_right_with_existing_postal_code@2x.png", 74 | "KioskTests/ReferenceImages/YourBiddingDetailsViewControllerTests/displays_bidder_number_and_PIN@2x.png", 75 | "KioskTests/XAppTokenSpec.swift", 76 | "Podfile", 77 | "Podfile.lock" 78 | ) 79 | } 80 | -------------------------------------------------------------------------------- /danger-kotlin-library/src/test/kotlin/systems/danger/kotlin/models/gitlab/GitLabParsingTests.kt: -------------------------------------------------------------------------------- 1 | package systems.danger.kotlin.models.gitlab 2 | 3 | import kotlinx.datetime.Instant 4 | import kotlinx.serialization.decodeFromString 5 | import org.junit.Assert.* 6 | import org.junit.Test 7 | import systems.danger.kotlin.models.danger.DSL 8 | import systems.danger.kotlin.utils.TestUtils.JSONFiles 9 | import systems.danger.kotlin.utils.TestUtils 10 | 11 | class GitLabParsingTests { 12 | private val dsl: DSL 13 | get() = TestUtils.Json.decodeFromString(JSONFiles.gitlabJSON) 14 | private val gitLab: GitLab 15 | get() = dsl.danger.gitlab 16 | 17 | @Test 18 | fun testItParsesTheGitLabMergeRequest() { 19 | with(gitLab.mergeRequest) { 20 | assertEquals(false, allowCollaboration) 21 | assertEquals(false, allowMaintainerToPush) 22 | assertEquals(1, approvalsBeforeMerge) 23 | val orta = GitLabUser( 24 | "https://secure.gravatar.com/avatar/f116cb3be23153ec08b94e8bd4dbcfeb?s=80&d=identicon", 377669, "Orta", 25 | GitLabUserState.ACTIVE, "orta", "https://gitlab.com/orta" 26 | ) 27 | assertEquals(orta, assignee) 28 | val fmeloni = GitLabUser( 29 | "https://secure.gravatar.com/avatar/3d90e967de2beab6d44cfadbb4976b87?s=80&d=identicon", 30 | 3331525, 31 | "Franco Meloni", 32 | GitLabUserState.ACTIVE, 33 | "f-meloni", 34 | "https://gitlab.com/f-meloni" 35 | ) 36 | assertEquals(fmeloni, author) 37 | assertEquals(false, canMerge) 38 | assertEquals("1", changesCount) 39 | assertEquals(null, closedAt) 40 | assertEquals(null, closedBy) 41 | assertEquals("Updating it to avoid problems like https://github.com/danger/swift/issues/221", description) 42 | val expectedDiffRefs = GitLabDiffRefs( 43 | "ef28580bb2a00d985bffe4a4ce3fe09fdb12283f", 44 | "621bc3348549e51c5bd6ea9f094913e9e4667c7b", 45 | "ef28580bb2a00d985bffe4a4ce3fe09fdb12283f" 46 | ) 47 | assertEquals(expectedDiffRefs, diffRefs) 48 | assertEquals(0, downvotes) 49 | assertEquals(Instant.fromEpochMilliseconds(1554942622492), firstDeployedToProductionAt) 50 | assertEquals(true, forceRemoveSourceBranch) 51 | assertEquals(27469633, id) 52 | assertEquals(182, iid) 53 | assertEquals(Instant.fromEpochMilliseconds(1554942802492), latestBuildFinishedAt) 54 | assertEquals(Instant.fromEpochMilliseconds(1619786100103), latestBuildStartedAt) 55 | assertEquals(listOf(), labels) 56 | assertEquals("377a24fb7a0f30364f089f7bca67752a8b61f477", mergeCommitSha) 57 | assertEquals(Instant.fromEpochMilliseconds(1554943042492), mergedAt) 58 | assertEquals(orta, mergedBy) 59 | assertEquals(false, mergeOnPipelineSuccess) 60 | val expectedMilestone = GitLabMilestone( 61 | Instant.fromEpochMilliseconds(1554933465346), 62 | "Test Description", 63 | Instant.fromEpochMilliseconds(1560124800000), 64 | 1, 65 | 2, 66 | 1000, 67 | Instant.fromEpochMilliseconds(1554933465346), 68 | GitLabMilestoneState.CLOSED, 69 | "Test Milestone", 70 | Instant.fromEpochMilliseconds(1554933465346), 71 | "https://gitlab.com/milestone" 72 | ) 73 | assertEquals(expectedMilestone, milestone) 74 | val expectedPipeline = GitLabPipeline( 75 | 50, 76 | "ef28580bb2a00d985bffe4a4ce3fe09fdb12283f", 77 | "621bc3348549e51c5bd6ea9f094913e9e4667c7b", 78 | GitLabPipelineStatus.SUCCESS, 79 | "https://gitlab.com/danger-systems/danger.systems/pipeline/621bc3348549e51c5bd6ea9f094913e9e4667c7b" 80 | ) 81 | assertEquals(expectedPipeline, pipeline) 82 | assertEquals("1620437", projectId) 83 | assertEquals("621bc3348549e51c5bd6ea9f094913e9e4667c7b", sha) 84 | assertEquals(null, shouldRemoveSourceBranch) 85 | assertEquals("patch-2", sourceBranch) 86 | assertEquals("10132593", sourceProjectId) 87 | assertEquals(GitLabMergeRequestState.MERGED, state) 88 | assertEquals(false, subscribed) 89 | assertEquals("master", targetBranch) 90 | assertEquals("1620437", targetProjectId) 91 | assertEquals(null, timeStats) 92 | assertEquals("Update getting_started.html.slim", title) 93 | assertEquals(0, upvotes) 94 | assertEquals(0, userNotesCount) 95 | assertEquals("https://gitlab.com/danger-systems/danger.systems/merge_requests/182", webUrl) 96 | assertEquals(false, workInProgress) 97 | assertEquals(false, squash) 98 | } 99 | } 100 | 101 | @Test 102 | fun testItParsesTheGitLabMetadata() { 103 | with(gitLab.metadata) { 104 | assertEquals("182", pullRequestID) 105 | assertEquals("danger-systems/danger.systems", repoSlug) 106 | } 107 | } 108 | 109 | @Test 110 | fun testOnGitHubIsFalse() { 111 | assertEquals(false, dsl.danger.onGitHub) 112 | } 113 | 114 | @Test 115 | fun testOnBitBucketCloudIsFalse() { 116 | assertEquals(false, dsl.danger.onBitBucketCloud) 117 | } 118 | 119 | @Test 120 | fun testOnBitBucketServerIsFalse() { 121 | assertEquals(false, dsl.danger.onBitBucketServer) 122 | } 123 | 124 | @Test 125 | fun testOnGitLabIsTrue() { 126 | assertEquals(true, dsl.danger.onGitLab) 127 | } 128 | } -------------------------------------------------------------------------------- /danger-kotlin-library/src/test/kotlin/systems/danger/kotlin/models/gitlab/GitLabPipelineStatusTest.kt: -------------------------------------------------------------------------------- 1 | package systems.danger.kotlin.models.gitlab 2 | 3 | import kotlinx.serialization.decodeFromString 4 | import org.junit.Assert.assertEquals 5 | import org.junit.Test 6 | import systems.danger.kotlin.models.danger.DSL 7 | import systems.danger.kotlin.utils.TestUtils 8 | import systems.danger.kotlin.utils.TestUtils.JSONFiles 9 | 10 | class GitLabPipelineStatusTest { 11 | 12 | @Test 13 | fun testItParsesGitLabCancelledPipeline() { 14 | val dsl: DSL = TestUtils.Json.decodeFromString(JSONFiles.gitlabWithCancelledPipelineJSON) 15 | val gitLab = dsl.danger.gitlab 16 | assertEquals(GitLabPipelineStatus.CANCELLED, gitLab.mergeRequest.pipeline.status) 17 | } 18 | } -------------------------------------------------------------------------------- /danger-kotlin-library/src/test/kotlin/systems/danger/kotlin/utils/TestUtils.kt: -------------------------------------------------------------------------------- 1 | package systems.danger.kotlin.utils 2 | 3 | import kotlinx.serialization.json.Json 4 | 5 | object TestUtils { 6 | 7 | val Json: Json by lazy { 8 | Json { 9 | isLenient = true 10 | ignoreUnknownKeys = true 11 | } 12 | } 13 | 14 | object JSONFiles { 15 | val githubDangerJSON by lazy { 16 | loadJSON("githubDangerJSON.json") 17 | } 18 | 19 | val githubWithSomeNullsAttributeDangerJSON by lazy { 20 | loadJSON("githubWithSomeNullsAttributeDangerJSON.json") 21 | } 22 | 23 | val githubWithClosedMilestoneDangerJSON by lazy { 24 | loadJSON("githubWithClosedMilestoneDangerJSON.json") 25 | } 26 | 27 | val dangerBitBucketServerJSON by lazy { 28 | loadJSON("bitbucketServerDangerJSON.json") 29 | } 30 | 31 | val dangerBitBucketCloudJSON by lazy { 32 | loadJSON("bitbucketCloudDangerJSON.json") 33 | } 34 | 35 | val gitlabJSON by lazy { 36 | loadJSON("gitlabDangerJSON.json") 37 | } 38 | 39 | val gitlabWithCancelledPipelineJSON by lazy { 40 | loadJSON("gitlabWithCancelledPipelineDangerJSON.json") 41 | } 42 | 43 | private fun loadJSON(named: String): String { 44 | return this.javaClass.classLoader.getResource(named).readText() 45 | } 46 | } 47 | } -------------------------------------------------------------------------------- /danger-kotlin-library/src/test/resources/bitbucketCloudDangerJSON.json: -------------------------------------------------------------------------------- 1 | { 2 | "danger": { 3 | "git": { 4 | "modified_files": [ 5 | ".gitignore" 6 | ], 7 | "created_files": [ 8 | "banana", 9 | ".babelrc" 10 | ], 11 | "deleted_files": [ 12 | ".babelrc.example", 13 | "jest.eslint.config.js" 14 | ], 15 | "commits": [ 16 | { 17 | "sha": "d6725486c38d46a33e76f622cf24b9a388c8d13d", 18 | "parents": [ 19 | "c62ada76533a2de045d4c6062988ba84df140729" 20 | ], 21 | "author": { 22 | "email": "foo@bar.com", 23 | "name": "danger", 24 | "date": "2018-02-24T03:19:01.000Z" 25 | }, 26 | "committer": { 27 | "email": "foo@bar.com", 28 | "name": "danger", 29 | "date": "2018-02-24T03:19:01.000Z" 30 | }, 31 | "message": "Modify and remove files", 32 | "tree": null, 33 | "url": "fake://host/artsy/emission/commits/d6725486c38d46a33e76f622cf24b9a388c8d13d" 34 | }, 35 | { 36 | "sha": "c62ada76533a2de045d4c6062988ba84df140729", 37 | "parents": [ 38 | "8942a1f75e4c95df836f19ef681d20a87da2ee20" 39 | ], 40 | "author": { 41 | "email": "foo@bar.com", 42 | "name": "danger", 43 | "date": "2018-02-17T10:38:02.000Z" 44 | }, 45 | "committer": { 46 | "email": "foo@bar.com", 47 | "name": "danger", 48 | "date": "2018-02-17T10:38:02.000Z" 49 | }, 50 | "message": "add banana", 51 | "tree": null, 52 | "url": "fake://host/artsy/emission/commits/c62ada76533a2de045d4c6062988ba84df140729" 53 | } 54 | ] 55 | }, 56 | "bitbucket_cloud": { 57 | "metadata": { 58 | "repoSlug": "artsy/emission", 59 | "pullRequestID": "327" 60 | }, 61 | "pr":{ 62 | "description":"test", 63 | "title":"test", 64 | "close_source_branch":true, 65 | "reviewers":[ 66 | { 67 | "display_name":"Danger", 68 | "uuid":"danger", 69 | "nickname":"danger", 70 | "account_id":"danger" 71 | } 72 | ], 73 | "id":1, 74 | "destination":{ 75 | "commit":{ 76 | "hash":"test" 77 | }, 78 | "repository":{ 79 | "name":"test", 80 | "full_name":"test", 81 | "uuid":"test" 82 | }, 83 | "branch":{ 84 | "name":"destination" 85 | } 86 | }, 87 | "created_on":"2022-04-07T15:19:56.209142+00:00", 88 | "summary":{ 89 | "raw":"test", 90 | "markup":"markdown", 91 | "html":"test" 92 | }, 93 | "source":{ 94 | "commit":{ 95 | "hash":"test" 96 | }, 97 | "repository":{ 98 | "name":"test", 99 | "full_name":"test", 100 | "uuid":"test" 101 | }, 102 | "branch":{ 103 | "name":"source" 104 | } 105 | }, 106 | "comment_count":1, 107 | "state":"OPEN", 108 | "task_count":0, 109 | "participants":[ 110 | { 111 | "role":"REVIEWER", 112 | "user":{ 113 | "display_name":"Danger", 114 | "uuid":"danger", 115 | "nickname":"danger", 116 | "account_id":"danger" 117 | }, 118 | "approved":false 119 | } 120 | ], 121 | "reason":"", 122 | "updated_on":"2022-04-09T12:51:23.384574+00:00", 123 | "author":{ 124 | "display_name":"Foo Bar", 125 | "uuid":"test", 126 | "nickname":"foo.bar", 127 | "account_id":"test" 128 | }, 129 | "merge_commit":null, 130 | "closed_by":null 131 | }, 132 | "commits": [ 133 | { 134 | "hash":"test", 135 | "author":{ 136 | "raw": "test", 137 | "user":{ 138 | "display_name":"Foo Bar", 139 | "uuid":"test", 140 | "nickname":"foo.bar", 141 | "account_id":"test" 142 | } 143 | }, 144 | "message": "test", 145 | "date": "2022-04-07T15:18:09+00:00" 146 | } 147 | ], 148 | "comments": [ 149 | { 150 | "deleted":false, 151 | "content":{ 152 | "markup":"markdown", 153 | "html":"test", 154 | "raw":"test" 155 | }, 156 | "created_on":"2022-04-07T15:25:25.184212+00:00", 157 | "user":{ 158 | "display_name":"Danger", 159 | "uuid":"danger", 160 | "nickname":"danger", 161 | "account_id":"danger" 162 | }, 163 | "updated_on":"2022-04-07T15:25:25.184261+00:00", 164 | "id":1 165 | } 166 | ], 167 | "activities": [ 168 | { 169 | "comment": { 170 | "deleted":false, 171 | "content":{ 172 | "markup":"markdown", 173 | "html":"test", 174 | "raw":"test" 175 | }, 176 | "created_on":"2022-04-07T15:25:25.184212+00:00", 177 | "user":{ 178 | "display_name":"Danger", 179 | "uuid":"danger", 180 | "nickname":"danger", 181 | "account_id":"danger" 182 | }, 183 | "updated_on":"2022-04-07T15:25:25.184261+00:00", 184 | "id":1 185 | } 186 | } 187 | ], 188 | "issues": [ 189 | { 190 | "key": "JRA-11", 191 | "url": "https://jira.atlassian.com/browse/JRA-11" 192 | }, 193 | { 194 | "key": "JRA-9", 195 | "url": "https://jira.atlassian.com/browse/JRA-9" 196 | } 197 | ] 198 | }, 199 | "settings": { 200 | "github": { 201 | "accessToken": "12345", 202 | "additionalHeaders": {} 203 | }, 204 | "cliArgs": {} 205 | } 206 | } 207 | } -------------------------------------------------------------------------------- /danger-kotlin-library/src/test/resources/gitlabDangerJSON.json: -------------------------------------------------------------------------------- 1 | { 2 | "danger": { 3 | "git": { 4 | "modified_files": [ 5 | "static/source/swift/guides/getting_started.html.slim" 6 | ], 7 | "created_files": [], 8 | "deleted_files": [], 9 | "commits": [ 10 | { 11 | "sha": "621bc3348549e51c5bd6ea9f094913e9e4667c7b", 12 | "author": { 13 | "name": "Franco Meloni", 14 | "email": "franco.meloni91@gmail.com", 15 | "date": "2019-04-10T21:56:43.000Z" 16 | }, 17 | "committer": { 18 | "name": "Franco Meloni", 19 | "email": "franco.meloni91@gmail.com", 20 | "date": "2019-04-10T21:56:43.000Z" 21 | }, 22 | "message": "Update getting_started.html.slim", 23 | "parents": [], 24 | "url": "https://gitlab.com/danger-systems/danger.systems/commit/621bc3348549e51c5bd6ea9f094913e9e4667c7b", 25 | "tree": null 26 | } 27 | ] 28 | }, 29 | "gitlab": { 30 | "metadata": { 31 | "pullRequestID": "182", 32 | "repoSlug": "danger-systems/danger.systems" 33 | }, 34 | "mr": { 35 | "id": 27469633, 36 | "iid": 182, 37 | "project_id": 1620437, 38 | "title": "Update getting_started.html.slim", 39 | "description": "Updating it to avoid problems like https://github.com/danger/swift/issues/221", 40 | "state": "merged", 41 | "created_at": "2019-04-10T21:57:45.346Z", 42 | "updated_at": "2019-04-11T00:37:22.460Z", 43 | "merged_by": { 44 | "id": 377669, 45 | "name": "Orta", 46 | "username": "orta", 47 | "state": "active", 48 | "avatar_url": "https://secure.gravatar.com/avatar/f116cb3be23153ec08b94e8bd4dbcfeb?s=80&d=identicon", 49 | "web_url": "https://gitlab.com/orta" 50 | }, 51 | "merged_at": "2019-04-11T00:37:22.492Z", 52 | "closed_by": null, 53 | "closed_at": null, 54 | "target_branch": "master", 55 | "source_branch": "patch-2", 56 | "user_notes_count": 0, 57 | "upvotes": 0, 58 | "downvotes": 0, 59 | "assignee": { 60 | "id": 377669, 61 | "name": "Orta", 62 | "username": "orta", 63 | "state": "active", 64 | "avatar_url": "https://secure.gravatar.com/avatar/f116cb3be23153ec08b94e8bd4dbcfeb?s=80&d=identicon", 65 | "web_url": "https://gitlab.com/orta" 66 | }, 67 | "author": { 68 | "id": 3331525, 69 | "name": "Franco Meloni", 70 | "username": "f-meloni", 71 | "state": "active", 72 | "avatar_url": "https://secure.gravatar.com/avatar/3d90e967de2beab6d44cfadbb4976b87?s=80&d=identicon", 73 | "web_url": "https://gitlab.com/f-meloni" 74 | }, 75 | "assignees": [], 76 | "source_project_id": 10132593, 77 | "target_project_id": 1620437, 78 | "labels": [], 79 | "work_in_progress": false, 80 | "milestone": { 81 | "id": 1, 82 | "iid": 2, 83 | "project_id": 1000, 84 | "title": "Test Milestone", 85 | "description": "Test Description", 86 | "state": "closed", 87 | "start_date": "2019-04-10T21:57:45.346Z", 88 | "created_at": "2019-04-10T21:57:45.346Z", 89 | "updated_at": "2019-04-10T21:57:45.346Z", 90 | "due_date": "2019-06-10T00:00:00.000Z", 91 | "web_url": "https://gitlab.com/milestone" 92 | }, 93 | "merge_when_pipeline_succeeds": false, 94 | "merge_status": "can_be_merged", 95 | "sha": "621bc3348549e51c5bd6ea9f094913e9e4667c7b", 96 | "merge_commit_sha": "377a24fb7a0f30364f089f7bca67752a8b61f477", 97 | "discussion_locked": null, 98 | "should_remove_source_branch": null, 99 | "force_remove_source_branch": true, 100 | "allow_collaboration": false, 101 | "allow_maintainer_to_push": false, 102 | "reference": "!182", 103 | "web_url": "https://gitlab.com/danger-systems/danger.systems/merge_requests/182", 104 | "time_stats": { 105 | "time_estimate": 0, 106 | "total_time_spent": 0, 107 | "human_time_estimate": null, 108 | "human_total_time_spent": null 109 | }, 110 | "squash": false, 111 | "subscribed": false, 112 | "changes_count": "1", 113 | "latest_build_started_at": "2021-04-30T14:35:00.103+02:00", 114 | "latest_build_finished_at": "2019-04-11T00:33:22.492Z", 115 | "first_deployed_to_production_at": "2019-04-11T00:30:22.492Z", 116 | "pipeline": null, 117 | "head_pipeline": { 118 | "id": 50, 119 | "sha": "621bc3348549e51c5bd6ea9f094913e9e4667c7b", 120 | "ref": "ef28580bb2a00d985bffe4a4ce3fe09fdb12283f", 121 | "status": "success", 122 | "web_url": "https://gitlab.com/danger-systems/danger.systems/pipeline/621bc3348549e51c5bd6ea9f094913e9e4667c7b" 123 | }, 124 | "diff_refs": { 125 | "base_sha": "ef28580bb2a00d985bffe4a4ce3fe09fdb12283f", 126 | "head_sha": "621bc3348549e51c5bd6ea9f094913e9e4667c7b", 127 | "start_sha": "ef28580bb2a00d985bffe4a4ce3fe09fdb12283f" 128 | }, 129 | "merge_error": null, 130 | "user": { 131 | "can_merge": false 132 | }, 133 | "approvals_before_merge": 1 134 | }, 135 | "commits": [ 136 | { 137 | "id": "621bc3348549e51c5bd6ea9f094913e9e4667c7b", 138 | "short_id": "621bc334", 139 | "created_at": "2019-04-10T21:56:43.000Z", 140 | "parent_ids": [], 141 | "title": "Update getting_started.html.slim", 142 | "message": "Update getting_started.html.slim", 143 | "author_name": "Franco Meloni", 144 | "author_email": "franco.meloni91@gmail.com", 145 | "authored_date": "2019-04-10T21:56:43.000Z", 146 | "committer_name": "Franco Meloni", 147 | "committer_email": "franco.meloni91@gmail.com", 148 | "committed_date": "2019-04-10T21:56:43.000Z" 149 | } 150 | ] 151 | }, 152 | "settings": { 153 | "github": { 154 | "accessToken": "NO_T...", 155 | "additionalHeaders": {} 156 | }, 157 | "cliArgs": {} 158 | } 159 | } 160 | } -------------------------------------------------------------------------------- /danger-kotlin-library/src/test/resources/gitlabWithCancelledPipelineDangerJSON.json: -------------------------------------------------------------------------------- 1 | { 2 | "danger": { 3 | "git": { 4 | "modified_files": [ 5 | "static/source/swift/guides/getting_started.html.slim" 6 | ], 7 | "created_files": [], 8 | "deleted_files": [], 9 | "commits": [ 10 | { 11 | "sha": "621bc3348549e51c5bd6ea9f094913e9e4667c7b", 12 | "author": { 13 | "name": "Franco Meloni", 14 | "email": "franco.meloni91@gmail.com", 15 | "date": "2019-04-10T21:56:43.000Z" 16 | }, 17 | "committer": { 18 | "name": "Franco Meloni", 19 | "email": "franco.meloni91@gmail.com", 20 | "date": "2019-04-10T21:56:43.000Z" 21 | }, 22 | "message": "Update getting_started.html.slim", 23 | "parents": [], 24 | "url": "https://gitlab.com/danger-systems/danger.systems/commit/621bc3348549e51c5bd6ea9f094913e9e4667c7b", 25 | "tree": null 26 | } 27 | ] 28 | }, 29 | "gitlab": { 30 | "metadata": { 31 | "pullRequestID": "182", 32 | "repoSlug": "danger-systems/danger.systems" 33 | }, 34 | "mr": { 35 | "id": 27469633, 36 | "iid": 182, 37 | "project_id": 1620437, 38 | "title": "Update getting_started.html.slim", 39 | "description": "Updating it to avoid problems like https://github.com/danger/swift/issues/221", 40 | "state": "merged", 41 | "created_at": "2019-04-10T21:57:45.346Z", 42 | "updated_at": "2019-04-11T00:37:22.460Z", 43 | "merged_by": { 44 | "id": 377669, 45 | "name": "Orta", 46 | "username": "orta", 47 | "state": "active", 48 | "avatar_url": "https://secure.gravatar.com/avatar/f116cb3be23153ec08b94e8bd4dbcfeb?s=80&d=identicon", 49 | "web_url": "https://gitlab.com/orta" 50 | }, 51 | "merged_at": "2019-04-11T00:37:22.492Z", 52 | "closed_by": null, 53 | "closed_at": null, 54 | "target_branch": "master", 55 | "source_branch": "patch-2", 56 | "user_notes_count": 0, 57 | "upvotes": 0, 58 | "downvotes": 0, 59 | "assignee": { 60 | "id": 377669, 61 | "name": "Orta", 62 | "username": "orta", 63 | "state": "active", 64 | "avatar_url": "https://secure.gravatar.com/avatar/f116cb3be23153ec08b94e8bd4dbcfeb?s=80&d=identicon", 65 | "web_url": "https://gitlab.com/orta" 66 | }, 67 | "author": { 68 | "id": 3331525, 69 | "name": "Franco Meloni", 70 | "username": "f-meloni", 71 | "state": "active", 72 | "avatar_url": "https://secure.gravatar.com/avatar/3d90e967de2beab6d44cfadbb4976b87?s=80&d=identicon", 73 | "web_url": "https://gitlab.com/f-meloni" 74 | }, 75 | "assignees": [], 76 | "source_project_id": 10132593, 77 | "target_project_id": 1620437, 78 | "labels": [], 79 | "work_in_progress": false, 80 | "milestone": { 81 | "id": 1, 82 | "iid": 2, 83 | "project_id": 1000, 84 | "title": "Test Milestone", 85 | "description": "Test Description", 86 | "state": "closed", 87 | "start_date": "2019-04-10T21:57:45.346Z", 88 | "created_at": "2019-04-10T21:57:45.346Z", 89 | "updated_at": "2019-04-10T21:57:45.346Z", 90 | "due_date": "2019-06-10T00:00:00.000Z", 91 | "web_url": "https://gitlab.com/milestone" 92 | }, 93 | "merge_when_pipeline_succeeds": false, 94 | "merge_status": "can_be_merged", 95 | "sha": "621bc3348549e51c5bd6ea9f094913e9e4667c7b", 96 | "merge_commit_sha": "377a24fb7a0f30364f089f7bca67752a8b61f477", 97 | "discussion_locked": null, 98 | "should_remove_source_branch": null, 99 | "force_remove_source_branch": true, 100 | "allow_collaboration": false, 101 | "allow_maintainer_to_push": false, 102 | "reference": "!182", 103 | "web_url": "https://gitlab.com/danger-systems/danger.systems/merge_requests/182", 104 | "time_stats": { 105 | "time_estimate": 0, 106 | "total_time_spent": 0, 107 | "human_time_estimate": null, 108 | "human_total_time_spent": null 109 | }, 110 | "squash": false, 111 | "subscribed": false, 112 | "changes_count": "1", 113 | "latest_build_started_at": "2021-04-30T14:35:00.103+02:00", 114 | "latest_build_finished_at": "2019-04-11T00:33:22.492Z", 115 | "first_deployed_to_production_at": "2019-04-11T00:30:22.492Z", 116 | "pipeline": null, 117 | "head_pipeline": { 118 | "id": 50, 119 | "sha": "621bc3348549e51c5bd6ea9f094913e9e4667c7b", 120 | "ref": "ef28580bb2a00d985bffe4a4ce3fe09fdb12283f", 121 | "status": "canceled", 122 | "web_url": "https://gitlab.com/danger-systems/danger.systems/pipeline/621bc3348549e51c5bd6ea9f094913e9e4667c7b" 123 | }, 124 | "diff_refs": { 125 | "base_sha": "ef28580bb2a00d985bffe4a4ce3fe09fdb12283f", 126 | "head_sha": "621bc3348549e51c5bd6ea9f094913e9e4667c7b", 127 | "start_sha": "ef28580bb2a00d985bffe4a4ce3fe09fdb12283f" 128 | }, 129 | "merge_error": null, 130 | "user": { 131 | "can_merge": false 132 | }, 133 | "approvals_before_merge": 1 134 | }, 135 | "commits": [ 136 | { 137 | "id": "621bc3348549e51c5bd6ea9f094913e9e4667c7b", 138 | "short_id": "621bc334", 139 | "created_at": "2019-04-10T21:56:43.000Z", 140 | "parent_ids": [], 141 | "title": "Update getting_started.html.slim", 142 | "message": "Update getting_started.html.slim", 143 | "author_name": "Franco Meloni", 144 | "author_email": "franco.meloni91@gmail.com", 145 | "authored_date": "2019-04-10T21:56:43.000Z", 146 | "committer_name": "Franco Meloni", 147 | "committer_email": "franco.meloni91@gmail.com", 148 | "committed_date": "2019-04-10T21:56:43.000Z" 149 | } 150 | ] 151 | }, 152 | "settings": { 153 | "github": { 154 | "accessToken": "NO_T...", 155 | "additionalHeaders": {} 156 | }, 157 | "cliArgs": {} 158 | } 159 | } 160 | } -------------------------------------------------------------------------------- /danger-kotlin-library/version.gradle: -------------------------------------------------------------------------------- 1 | group 'systems.danger' 2 | version '1.3.3' 3 | -------------------------------------------------------------------------------- /danger-kotlin-sample-plugin/build.gradle: -------------------------------------------------------------------------------- 1 | buildscript { 2 | repositories { 3 | mavenLocal() 4 | } 5 | 6 | dependencies { 7 | classpath "systems.danger:danger-plugin-installer:0.1-alpha" 8 | } 9 | } 10 | 11 | plugins { 12 | id 'org.jetbrains.kotlin.jvm' version '2.0.21' 13 | } 14 | 15 | apply plugin: 'danger-kotlin-plugin-installer' 16 | 17 | group 'systems.danger' 18 | version 'sample' 19 | 20 | repositories { 21 | mavenCentral() 22 | } 23 | 24 | dangerPlugin { 25 | outputJar = "${buildDir}/libs/danger-kotlin-sample-plugin-sample.jar" 26 | } 27 | 28 | dependencies { 29 | implementation "org.jetbrains.kotlin:kotlin-stdlib" 30 | implementation "systems.danger:danger-kotlin-sdk:1.2" 31 | } 32 | -------------------------------------------------------------------------------- /danger-kotlin-sample-plugin/gradle/wrapper/gradle-wrapper.properties: -------------------------------------------------------------------------------- 1 | distributionBase=GRADLE_USER_HOME 2 | distributionPath=wrapper/dists 3 | distributionUrl=https\://services.gradle.org/distributions/gradle-7.4-bin.zip 4 | zipStoreBase=GRADLE_USER_HOME 5 | zipStorePath=wrapper/dists 6 | -------------------------------------------------------------------------------- /danger-kotlin-sample-plugin/gradlew.bat: -------------------------------------------------------------------------------- 1 | @rem 2 | @rem Copyright 2015 the original author or authors. 3 | @rem 4 | @rem Licensed under the Apache License, Version 2.0 (the "License"); 5 | @rem you may not use this file except in compliance with the License. 6 | @rem You may obtain a copy of the License at 7 | @rem 8 | @rem https://www.apache.org/licenses/LICENSE-2.0 9 | @rem 10 | @rem Unless required by applicable law or agreed to in writing, software 11 | @rem distributed under the License is distributed on an "AS IS" BASIS, 12 | @rem WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | @rem See the License for the specific language governing permissions and 14 | @rem limitations under the License. 15 | @rem 16 | 17 | @if "%DEBUG%" == "" @echo off 18 | @rem ########################################################################## 19 | @rem 20 | @rem Gradle startup script for Windows 21 | @rem 22 | @rem ########################################################################## 23 | 24 | @rem Set local scope for the variables with windows NT shell 25 | if "%OS%"=="Windows_NT" setlocal 26 | 27 | set DIRNAME=%~dp0 28 | if "%DIRNAME%" == "" set DIRNAME=. 29 | set APP_BASE_NAME=%~n0 30 | set APP_HOME=%DIRNAME% 31 | 32 | @rem Resolve any "." and ".." in APP_HOME to make it shorter. 33 | for %%i in ("%APP_HOME%") do set APP_HOME=%%~fi 34 | 35 | @rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. 36 | set DEFAULT_JVM_OPTS="-Xmx64m" "-Xms64m" 37 | 38 | @rem Find java.exe 39 | if defined JAVA_HOME goto findJavaFromJavaHome 40 | 41 | set JAVA_EXE=java.exe 42 | %JAVA_EXE% -version >NUL 2>&1 43 | if "%ERRORLEVEL%" == "0" goto execute 44 | 45 | echo. 46 | echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. 47 | echo. 48 | echo Please set the JAVA_HOME variable in your environment to match the 49 | echo location of your Java installation. 50 | 51 | goto fail 52 | 53 | :findJavaFromJavaHome 54 | set JAVA_HOME=%JAVA_HOME:"=% 55 | set JAVA_EXE=%JAVA_HOME%/bin/java.exe 56 | 57 | if exist "%JAVA_EXE%" goto execute 58 | 59 | echo. 60 | echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME% 61 | echo. 62 | echo Please set the JAVA_HOME variable in your environment to match the 63 | echo location of your Java installation. 64 | 65 | goto fail 66 | 67 | :execute 68 | @rem Setup the command line 69 | 70 | set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar 71 | 72 | 73 | @rem Execute Gradle 74 | "%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %* 75 | 76 | :end 77 | @rem End local scope for the variables with windows NT shell 78 | if "%ERRORLEVEL%"=="0" goto mainEnd 79 | 80 | :fail 81 | rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of 82 | rem the _cmd.exe /c_ return code! 83 | if not "" == "%GRADLE_EXIT_CONSOLE%" exit 1 84 | exit /b 1 85 | 86 | :mainEnd 87 | if "%OS%"=="Windows_NT" endlocal 88 | 89 | :omega 90 | -------------------------------------------------------------------------------- /danger-kotlin-sample-plugin/settings.gradle: -------------------------------------------------------------------------------- 1 | include ":danger-kotlin-sample-plugin" -------------------------------------------------------------------------------- /danger-kotlin-sample-plugin/src/main/kotlin/systems/danger/samples/plugin/SamplePlugin.kt: -------------------------------------------------------------------------------- 1 | package systems.danger.samples.plugin 2 | 3 | import systems.danger.kotlin.sdk.DangerPlugin 4 | 5 | object SamplePlugin : DangerPlugin() { 6 | override val id: String 7 | get() = "systems.danger.kotlin.samplePlugin" 8 | 9 | fun myCustomCheck() { 10 | context.message("✅ Custom plugin successfully linked") 11 | } 12 | } 13 | -------------------------------------------------------------------------------- /danger-kotlin-sdk/build.gradle: -------------------------------------------------------------------------------- 1 | plugins { 2 | id 'org.jetbrains.kotlin.jvm' 3 | id 'maven-publish' 4 | id 'signing' 5 | } 6 | 7 | apply from: file('version.gradle') 8 | apply from: file('maven-publish.gradle') 9 | 10 | repositories { 11 | mavenCentral() 12 | } 13 | 14 | dependencies { 15 | implementation "org.jetbrains.kotlin:kotlin-stdlib-jdk8" 16 | } 17 | 18 | compileKotlin { 19 | kotlinOptions.jvmTarget = "1.8" 20 | } 21 | compileTestKotlin { 22 | kotlinOptions.jvmTarget = "1.8" 23 | } 24 | 25 | tasks.withType(JavaCompile).configureEach { 26 | sourceCompatibility = JavaVersion.VERSION_1_8 27 | targetCompatibility = JavaVersion.VERSION_1_8 28 | } 29 | -------------------------------------------------------------------------------- /danger-kotlin-sdk/gradle/wrapper/gradle-wrapper.properties: -------------------------------------------------------------------------------- 1 | #Sun Nov 03 23:08:58 GMT 2019 2 | distributionUrl=https\://services.gradle.org/distributions/gradle-8.10.2-all.zip 3 | distributionBase=GRADLE_USER_HOME 4 | distributionPath=wrapper/dists 5 | zipStorePath=wrapper/dists 6 | zipStoreBase=GRADLE_USER_HOME 7 | -------------------------------------------------------------------------------- /danger-kotlin-sdk/gradlew: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | 3 | # 4 | # Copyright © 2015-2021 the original authors. 5 | # 6 | # Licensed under the Apache License, Version 2.0 (the "License"); 7 | # you may not use this file except in compliance with the License. 8 | # You may obtain a copy of the License at 9 | # 10 | # https://www.apache.org/licenses/LICENSE-2.0 11 | # 12 | # Unless required by applicable law or agreed to in writing, software 13 | # distributed under the License is distributed on an "AS IS" BASIS, 14 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 15 | # See the License for the specific language governing permissions and 16 | # limitations under the License. 17 | # 18 | 19 | ############################################################################## 20 | # 21 | # Gradle start up script for POSIX generated by Gradle. 22 | # 23 | # Important for running: 24 | # 25 | # (1) You need a POSIX-compliant shell to run this script. If your /bin/sh is 26 | # noncompliant, but you have some other compliant shell such as ksh or 27 | # bash, then to run this script, type that shell name before the whole 28 | # command line, like: 29 | # 30 | # ksh Gradle 31 | # 32 | # Busybox and similar reduced shells will NOT work, because this script 33 | # requires all of these POSIX shell features: 34 | # * functions; 35 | # * expansions «$var», «${var}», «${var:-default}», «${var+SET}», 36 | # «${var#prefix}», «${var%suffix}», and «$( cmd )»; 37 | # * compound commands having a testable exit status, especially «case»; 38 | # * various built-in commands including «command», «set», and «ulimit». 39 | # 40 | # Important for patching: 41 | # 42 | # (2) This script targets any POSIX shell, so it avoids extensions provided 43 | # by Bash, Ksh, etc; in particular arrays are avoided. 44 | # 45 | # The "traditional" practice of packing multiple parameters into a 46 | # space-separated string is a well documented source of bugs and security 47 | # problems, so this is (mostly) avoided, by progressively accumulating 48 | # options in "$@", and eventually passing that to Java. 49 | # 50 | # Where the inherited environment variables (DEFAULT_JVM_OPTS, JAVA_OPTS, 51 | # and GRADLE_OPTS) rely on word-splitting, this is performed explicitly; 52 | # see the in-line comments for details. 53 | # 54 | # There are tweaks for specific operating systems such as AIX, CygWin, 55 | # Darwin, MinGW, and NonStop. 56 | # 57 | # (3) This script is generated from the Groovy template 58 | # https://github.com/gradle/gradle/blob/master/subprojects/plugins/src/main/resources/org/gradle/api/internal/plugins/unixStartScript.txt 59 | # within the Gradle project. 60 | # 61 | # You can find Gradle at https://github.com/gradle/gradle/. 62 | # 63 | ############################################################################## 64 | 65 | # Attempt to set APP_HOME 66 | 67 | # Resolve links: $0 may be a link 68 | app_path=$0 69 | 70 | # Need this for daisy-chained symlinks. 71 | while 72 | APP_HOME=${app_path%"${app_path##*/}"} # leaves a trailing /; empty if no leading path 73 | [ -h "$app_path" ] 74 | do 75 | ls=$( ls -ld "$app_path" ) 76 | link=${ls#*' -> '} 77 | case $link in #( 78 | /*) app_path=$link ;; #( 79 | *) app_path=$APP_HOME$link ;; 80 | esac 81 | done 82 | 83 | APP_HOME=$( cd "${APP_HOME:-./}" && pwd -P ) || exit 84 | 85 | APP_NAME="Gradle" 86 | APP_BASE_NAME=${0##*/} 87 | 88 | # Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. 89 | DEFAULT_JVM_OPTS='"-Xmx64m" "-Xms64m"' 90 | 91 | # Use the maximum available, or set MAX_FD != -1 to use that value. 92 | MAX_FD=maximum 93 | 94 | warn () { 95 | echo "$*" 96 | } >&2 97 | 98 | die () { 99 | echo 100 | echo "$*" 101 | echo 102 | exit 1 103 | } >&2 104 | 105 | # OS specific support (must be 'true' or 'false'). 106 | cygwin=false 107 | msys=false 108 | darwin=false 109 | nonstop=false 110 | case "$( uname )" in #( 111 | CYGWIN* ) cygwin=true ;; #( 112 | Darwin* ) darwin=true ;; #( 113 | MSYS* | MINGW* ) msys=true ;; #( 114 | NONSTOP* ) nonstop=true ;; 115 | esac 116 | 117 | CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar 118 | 119 | 120 | # Determine the Java command to use to start the JVM. 121 | if [ -n "$JAVA_HOME" ] ; then 122 | if [ -x "$JAVA_HOME/jre/sh/java" ] ; then 123 | # IBM's JDK on AIX uses strange locations for the executables 124 | JAVACMD=$JAVA_HOME/jre/sh/java 125 | else 126 | JAVACMD=$JAVA_HOME/bin/java 127 | fi 128 | if [ ! -x "$JAVACMD" ] ; then 129 | die "ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME 130 | 131 | Please set the JAVA_HOME variable in your environment to match the 132 | location of your Java installation." 133 | fi 134 | else 135 | JAVACMD=java 136 | which java >/dev/null 2>&1 || die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. 137 | 138 | Please set the JAVA_HOME variable in your environment to match the 139 | location of your Java installation." 140 | fi 141 | 142 | # Increase the maximum file descriptors if we can. 143 | if ! "$cygwin" && ! "$darwin" && ! "$nonstop" ; then 144 | case $MAX_FD in #( 145 | max*) 146 | MAX_FD=$( ulimit -H -n ) || 147 | warn "Could not query maximum file descriptor limit" 148 | esac 149 | case $MAX_FD in #( 150 | '' | soft) :;; #( 151 | *) 152 | ulimit -n "$MAX_FD" || 153 | warn "Could not set maximum file descriptor limit to $MAX_FD" 154 | esac 155 | fi 156 | 157 | # Collect all arguments for the java command, stacking in reverse order: 158 | # * args from the command line 159 | # * the main class name 160 | # * -classpath 161 | # * -D...appname settings 162 | # * --module-path (only if needed) 163 | # * DEFAULT_JVM_OPTS, JAVA_OPTS, and GRADLE_OPTS environment variables. 164 | 165 | # For Cygwin or MSYS, switch paths to Windows format before running java 166 | if "$cygwin" || "$msys" ; then 167 | APP_HOME=$( cygpath --path --mixed "$APP_HOME" ) 168 | CLASSPATH=$( cygpath --path --mixed "$CLASSPATH" ) 169 | 170 | JAVACMD=$( cygpath --unix "$JAVACMD" ) 171 | 172 | # Now convert the arguments - kludge to limit ourselves to /bin/sh 173 | for arg do 174 | if 175 | case $arg in #( 176 | -*) false ;; # don't mess with options #( 177 | /?*) t=${arg#/} t=/${t%%/*} # looks like a POSIX filepath 178 | [ -e "$t" ] ;; #( 179 | *) false ;; 180 | esac 181 | then 182 | arg=$( cygpath --path --ignore --mixed "$arg" ) 183 | fi 184 | # Roll the args list around exactly as many times as the number of 185 | # args, so each arg winds up back in the position where it started, but 186 | # possibly modified. 187 | # 188 | # NB: a `for` loop captures its iteration list before it begins, so 189 | # changing the positional parameters here affects neither the number of 190 | # iterations, nor the values presented in `arg`. 191 | shift # remove old arg 192 | set -- "$@" "$arg" # push replacement arg 193 | done 194 | fi 195 | 196 | # Collect all arguments for the java command; 197 | # * $DEFAULT_JVM_OPTS, $JAVA_OPTS, and $GRADLE_OPTS can contain fragments of 198 | # shell script including quotes and variable substitutions, so put them in 199 | # double quotes to make sure that they get re-expanded; and 200 | # * put everything else in single quotes, so that it's not re-expanded. 201 | 202 | set -- \ 203 | "-Dorg.gradle.appname=$APP_BASE_NAME" \ 204 | -classpath "$CLASSPATH" \ 205 | org.gradle.wrapper.GradleWrapperMain \ 206 | "$@" 207 | 208 | # Use "xargs" to parse quoted args. 209 | # 210 | # With -n1 it outputs one arg per line, with the quotes and backslashes removed. 211 | # 212 | # In Bash we could simply go: 213 | # 214 | # readarray ARGS < <( xargs -n1 <<<"$var" ) && 215 | # set -- "${ARGS[@]}" "$@" 216 | # 217 | # but POSIX shell has neither arrays nor command substitution, so instead we 218 | # post-process each arg (as a line of input to sed) to backslash-escape any 219 | # character that might be a shell metacharacter, then use eval to reverse 220 | # that process (while maintaining the separation between arguments), and wrap 221 | # the whole thing up as a single "set" statement. 222 | # 223 | # This will of course break if any of these variables contains a newline or 224 | # an unmatched quote. 225 | # 226 | 227 | eval "set -- $( 228 | printf '%s\n' "$DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS" | 229 | xargs -n1 | 230 | sed ' s~[^-[:alnum:]+,./:=@_]~\\&~g; ' | 231 | tr '\n' ' ' 232 | )" '"$@"' 233 | 234 | exec "$JAVACMD" "$@" 235 | -------------------------------------------------------------------------------- /danger-kotlin-sdk/gradlew.bat: -------------------------------------------------------------------------------- 1 | @rem 2 | @rem Copyright 2015 the original author or authors. 3 | @rem 4 | @rem Licensed under the Apache License, Version 2.0 (the "License"); 5 | @rem you may not use this file except in compliance with the License. 6 | @rem You may obtain a copy of the License at 7 | @rem 8 | @rem https://www.apache.org/licenses/LICENSE-2.0 9 | @rem 10 | @rem Unless required by applicable law or agreed to in writing, software 11 | @rem distributed under the License is distributed on an "AS IS" BASIS, 12 | @rem WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | @rem See the License for the specific language governing permissions and 14 | @rem limitations under the License. 15 | @rem 16 | 17 | @if "%DEBUG%" == "" @echo off 18 | @rem ########################################################################## 19 | @rem 20 | @rem Gradle startup script for Windows 21 | @rem 22 | @rem ########################################################################## 23 | 24 | @rem Set local scope for the variables with windows NT shell 25 | if "%OS%"=="Windows_NT" setlocal 26 | 27 | set DIRNAME=%~dp0 28 | if "%DIRNAME%" == "" set DIRNAME=. 29 | set APP_BASE_NAME=%~n0 30 | set APP_HOME=%DIRNAME% 31 | 32 | @rem Resolve any "." and ".." in APP_HOME to make it shorter. 33 | for %%i in ("%APP_HOME%") do set APP_HOME=%%~fi 34 | 35 | @rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. 36 | set DEFAULT_JVM_OPTS="-Xmx64m" "-Xms64m" 37 | 38 | @rem Find java.exe 39 | if defined JAVA_HOME goto findJavaFromJavaHome 40 | 41 | set JAVA_EXE=java.exe 42 | %JAVA_EXE% -version >NUL 2>&1 43 | if "%ERRORLEVEL%" == "0" goto execute 44 | 45 | echo. 46 | echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. 47 | echo. 48 | echo Please set the JAVA_HOME variable in your environment to match the 49 | echo location of your Java installation. 50 | 51 | goto fail 52 | 53 | :findJavaFromJavaHome 54 | set JAVA_HOME=%JAVA_HOME:"=% 55 | set JAVA_EXE=%JAVA_HOME%/bin/java.exe 56 | 57 | if exist "%JAVA_EXE%" goto execute 58 | 59 | echo. 60 | echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME% 61 | echo. 62 | echo Please set the JAVA_HOME variable in your environment to match the 63 | echo location of your Java installation. 64 | 65 | goto fail 66 | 67 | :execute 68 | @rem Setup the command line 69 | 70 | set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar 71 | 72 | 73 | @rem Execute Gradle 74 | "%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %* 75 | 76 | :end 77 | @rem End local scope for the variables with windows NT shell 78 | if "%ERRORLEVEL%"=="0" goto mainEnd 79 | 80 | :fail 81 | rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of 82 | rem the _cmd.exe /c_ return code! 83 | if not "" == "%GRADLE_EXIT_CONSOLE%" exit 1 84 | exit /b 1 85 | 86 | :mainEnd 87 | if "%OS%"=="Windows_NT" endlocal 88 | 89 | :omega 90 | -------------------------------------------------------------------------------- /danger-kotlin-sdk/maven-publish.gradle: -------------------------------------------------------------------------------- 1 | apply from: file('../secrets.gradle') 2 | 3 | task sourceJar(type: Jar) { 4 | archiveClassifier.set("sources") 5 | from sourceSets.main.allJava 6 | } 7 | 8 | task javadocJar(type: Jar, dependsOn: javadoc) { 9 | archiveClassifier.set("javadoc") 10 | from javadoc.destinationDir 11 | } 12 | 13 | signing { 14 | useGpgCmd() 15 | sign publishing.publications 16 | } 17 | 18 | publishing { 19 | repositories { 20 | maven { 21 | name = "Sonatype" 22 | def releasesRepoUrl = "https://oss.sonatype.org/service/local/staging/deploy/maven2" 23 | def snapshotsRepoUrl = "https://oss.sonatype.org/content/repositories/snapshots" 24 | url = version.endsWith('SNAPSHOT') ? snapshotsRepoUrl : releasesRepoUrl 25 | credentials { 26 | username = loadSecret("SONATYPE_USER") 27 | password = loadSecret("SONATYPE_PASS") 28 | } 29 | } 30 | } 31 | 32 | publications { 33 | maven(MavenPublication) { 34 | from components.java 35 | artifact sourceJar 36 | artifact javadocJar 37 | pom { 38 | name = 'Danger Kotlin SDK' 39 | description = 'Develop your own plugin for Danger Kotlin' 40 | url = "https://github.com/danger/kotlin" 41 | licenses { 42 | license { 43 | name = 'MIT License' 44 | url = 'https://github.com/danger/kotlin/blob/master/LICENSE' 45 | } 46 | } 47 | developers { 48 | developer { 49 | id = 'gianluz' 50 | name = 'Gianluca Zuddas' 51 | } 52 | developer { 53 | id = 'f-meloni' 54 | name = 'Franco Meloni' 55 | } 56 | developer { 57 | id = 'danger' 58 | name = 'The Danger Community' 59 | } 60 | } 61 | scm { 62 | connection = 'scm:git:git://github.com/danger/kotlin.git' 63 | developerConnection = 'scm:git:ssh://github.com/danger/kotlin.git' 64 | url = 'https://github.com/danger/kotlin' 65 | } 66 | } 67 | } 68 | } 69 | 70 | gradle.taskGraph.whenReady { taskGraph -> 71 | if (taskGraph.allTasks.any { it instanceof Sign }) { 72 | allprojects { 73 | ext."signing.gnupg.keyName" = loadSecret("GPG_KEY_ID") 74 | ext."signing.gnupg.passphrase" = loadSecret("GPG_PASSPHRASE") 75 | } 76 | } 77 | } 78 | } 79 | -------------------------------------------------------------------------------- /danger-kotlin-sdk/src/main/kotlin/systems/danger/kotlin/sdk/DangerKotlinAPI.kt: -------------------------------------------------------------------------------- 1 | package systems.danger.kotlin.sdk 2 | 3 | /** 4 | * Defines the API to post the Danger results on your Pull Request 5 | * 6 | * @constructor Create empty Danger context 7 | */ 8 | interface DangerContext { 9 | 10 | /** 11 | * Adds an inline message message to the Danger report 12 | * 13 | * @param message the standard message 14 | */ 15 | fun message(message: String) 16 | 17 | /** 18 | * Adds an inline message message to the Danger report 19 | * 20 | * @param message the standard message 21 | * @param file the path to the target file 22 | * @param line the line number into the target file 23 | */ 24 | fun message(message: String, file: String, line: Int) 25 | 26 | /** 27 | * Adds an inline markdown message to the Danger report 28 | * 29 | * @param message the markdown formatted message 30 | */ 31 | fun markdown(message: String) 32 | 33 | /** 34 | * Adds an inline markdown message to the Danger report 35 | * 36 | * @param message the markdown formatted message 37 | * @param file the path to the target file 38 | * @param line the line number into the target file 39 | */ 40 | fun markdown(message: String, file: String, line: Int) 41 | 42 | /** 43 | * Adds an inline warning message to the Danger report 44 | * 45 | * @param message the warning message 46 | */ 47 | fun warn(message: String) 48 | 49 | /** 50 | * Adds an inline warning message to the Danger report 51 | * 52 | * @param message the warning message 53 | * @param file the path to the target file 54 | * @param line the line number into the target file 55 | */ 56 | fun warn(message: String, file: String, line: Int) 57 | 58 | /** 59 | * Adds an inline fail message to the Danger report 60 | * 61 | * @param message the fail message 62 | */ 63 | fun fail(message: String) 64 | 65 | /** 66 | * Adds an inline fail message to the Danger report 67 | * 68 | * @param message the fail message 69 | * @param file the path to the target file 70 | * @param line the line number into the target file 71 | */ 72 | fun fail(message: String, file: String, line: Int) 73 | 74 | /** 75 | * Adds an inline suggested code message to the Danger report 76 | * 77 | * @param code the suggested code 78 | * @param file the path to the target file 79 | * @param line the line number into the target file 80 | */ 81 | fun suggest(code: String, file: String, line: Int) 82 | 83 | val fails: List 84 | val warnings: List 85 | val messages: List 86 | val markdowns: List 87 | } 88 | 89 | /** 90 | * Violation is any comment on your Pull Request 91 | * 92 | * @param message the violation message 93 | * @param file the path to the target file 94 | * @param line the line number into the target file 95 | * @constructor Create empty Violation 96 | */ 97 | data class Violation( 98 | val message: String, 99 | val file: String? = null, 100 | val line: Int? = null 101 | ) 102 | 103 | /** 104 | * Describe the Sdk, contains: 105 | * - [Sdk.API_VERSION] 106 | * - [Sdk.VERSION_NAME] 107 | * 108 | * @constructor Create empty Sdk 109 | */ 110 | object Sdk { 111 | const val VERSION_NAME = "1.1" 112 | const val API_VERSION = 2 113 | } 114 | 115 | /** 116 | * A DangerPlugin is a special object that contains utils methods to be executed into the Danger File. 117 | * To create a new plugin you need to extend this class as per example: 118 | * ``` 119 | * object MyDangerPlugin: DangerPlugin() { 120 | * override val id = "MyUniquePluginId" 121 | * 122 | * fun myUtilMethod() { 123 | * context.warn("This is a util method") 124 | * } 125 | * } 126 | * ``` 127 | * your plugin can be registered and executed into your DangerFile with: 128 | * ``` 129 | * register plugin MyDangerPlugin 130 | * ``` 131 | * and then used: 132 | * ``` 133 | * danger(args) { 134 | * MyDangerPlugin.myUtilMethod() 135 | * } 136 | * ``` 137 | * with this example a warning message is published on your Pull Request. 138 | * 139 | * @constructor Create empty Danger plugin 140 | */ 141 | abstract class DangerPlugin { 142 | companion object { 143 | const val DEVELOPED_WITH_API = Sdk.API_VERSION 144 | } 145 | 146 | // The plugin unique id 147 | abstract val id: String 148 | 149 | /** 150 | * The context that will be set by the danger runner. This wil be null if the plugin has not been registered in the 151 | * Dangerfile. 152 | */ 153 | var registeredContext : DangerContext? = null 154 | 155 | /** 156 | * Provides a backing getter around registeredContext to avoid null checks if we know the plugin must be registered 157 | * in the Dangerfile. 158 | * 159 | * @throws NullPointerException if the plugin was not registered in the Dangerfile. 160 | */ 161 | val context : DangerContext 162 | get() = registeredContext ?: throw NullPointerException( 163 | "DangerContext is null! Have you registered this plugin in your Dangerfile e.g. " + 164 | "'register plugin ${this::class.simpleName}'?" 165 | ) 166 | } 167 | -------------------------------------------------------------------------------- /danger-kotlin-sdk/version.gradle: -------------------------------------------------------------------------------- 1 | group 'systems.danger' 2 | version '1.2' 3 | -------------------------------------------------------------------------------- /danger-kotlin/build.gradle.kts: -------------------------------------------------------------------------------- 1 | plugins { 2 | kotlin("multiplatform") 3 | } 4 | 5 | kotlin { 6 | /* Targets configuration omitted. 7 | * To find out how to configure the targets, please follow the link: 8 | * https://kotlinlang.org/docs/reference/building-mpp-with-gradle.html#setting-up-targets */ 9 | 10 | val targetOS: String by project 11 | val buildTarget = if (project.hasProperty("targetOS")) { 12 | when (val osName = targetOS) { 13 | "macosX64" -> macosX64("runner") 14 | "linuxX64" -> linuxX64("runner") 15 | "macosArm64" -> macosArm64("runner") 16 | "mingwX64" -> mingwX64("runner") 17 | else -> throw GradleException("OS '$osName' is not supported.") 18 | } 19 | } else { 20 | when (val osName = System.getProperty("os.name")) { 21 | "Mac OS X" -> macosX64("runner") 22 | "Linux" -> linuxX64("runner") 23 | "Mac OS X Apple silicon" -> macosArm64("runner") 24 | "Windows 11" -> mingwX64("runner") 25 | else -> throw GradleException("OS '$osName' is not supported.") 26 | } 27 | } 28 | 29 | buildTarget.apply { 30 | binaries { 31 | executable() 32 | } 33 | } 34 | 35 | sourceSets { 36 | val runnerMain by getting { 37 | dependencies { 38 | implementation(kotlin("stdlib-common")) 39 | } 40 | } 41 | val runnerTest by getting { 42 | dependencies { 43 | implementation(kotlin("test-common")) 44 | implementation(kotlin("test-annotations-common")) 45 | } 46 | } 47 | all { 48 | languageSettings.optIn("kotlinx.cinterop.ExperimentalForeignApi") 49 | } 50 | } 51 | } 52 | -------------------------------------------------------------------------------- /danger-kotlin/gradle.properties: -------------------------------------------------------------------------------- 1 | kotlin.code.style=official 2 | kotlin.import.noCommonSourceSets=true -------------------------------------------------------------------------------- /danger-kotlin/gradle/wrapper/gradle-wrapper.properties: -------------------------------------------------------------------------------- 1 | distributionBase=GRADLE_USER_HOME 2 | distributionPath=wrapper/dists 3 | distributionUrl=https\://services.gradle.org/distributions/gradle-8.10.2-bin.zip 4 | zipStoreBase=GRADLE_USER_HOME 5 | zipStorePath=wrapper/dists 6 | -------------------------------------------------------------------------------- /danger-kotlin/src/runnerMain/kotlin/Main.kt: -------------------------------------------------------------------------------- 1 | import platform.posix.getenv 2 | import systems.danger.DangerKotlin 3 | import systems.danger.Log 4 | import systems.danger.cmd.Command 5 | import systems.danger.cmd.dangerjs.DangerJS 6 | 7 | const val PROCESS_DANGER_KOTLIN = "danger-kotlin" 8 | const val VERSION = "1.3.3" 9 | 10 | fun main(args: Array) { 11 | Log.isVerbose = args.contains("--verbose") || (getenv("DEBUG")?.toString()?.isNotEmpty() ?: false) 12 | Log.info("Starting Danger-Kotlin $VERSION with args '${args.joinToString(", ")}'", verbose = true) 13 | 14 | if (args.isNotEmpty()) { 15 | when { 16 | args.contains("--help") -> { 17 | Log.info("danger-kotlin [command]") 18 | Log.info("") 19 | Log.info("Commands:") 20 | Log.info(Command.values().joinToString("\n") { it.argument + "\t" + it.description }) 21 | } 22 | args.contains("--version") -> { 23 | Log.info(VERSION) 24 | } 25 | else -> { 26 | when (val command = Command.forArgument(args.first())) { 27 | Command.CI, Command.LOCAL, Command.PR -> { 28 | DangerJS.process(command, PROCESS_DANGER_KOTLIN, args.drop(1).map { if(it.contains(" ")) "'$it'" else it }) 29 | } 30 | Command.RUNNER -> DangerKotlin.run() 31 | else -> Log.error("Invalid command received: $command") 32 | } 33 | } 34 | } 35 | } else { 36 | DangerKotlin.run() 37 | } 38 | } 39 | -------------------------------------------------------------------------------- /danger-kotlin/src/runnerMain/kotlin/systems.danger/DangerKotlin.kt: -------------------------------------------------------------------------------- 1 | package systems.danger 2 | 3 | import systems.danger.cmd.dangerfile.DangerFile 4 | 5 | object DangerKotlin { 6 | private const val FILE_TMP_OUTPUT_JSON = "danger_out.json" 7 | 8 | fun run() { 9 | val dangerDSLPath = readLine() 10 | 11 | if (dangerDSLPath != null) { 12 | Log.info("Got Danger DSL path $dangerDSLPath", true) 13 | } else { 14 | Log.error("Didn't receive a DSL path") 15 | } 16 | 17 | dangerDSLPath?.removePrefix("danger://dsl/")?.stripEndLine()?.let { 18 | Log.info("Stripped DSL Path $it", true) 19 | with(DangerFile) { 20 | execute(it, FILE_TMP_OUTPUT_JSON) 21 | } 22 | 23 | printResult() 24 | } 25 | } 26 | 27 | private fun printResult() { 28 | println("danger-results:/$FILE_TMP_OUTPUT_JSON") 29 | } 30 | 31 | private fun String.stripEndLine(): String { 32 | return replaceAfter(".json", "") 33 | } 34 | } 35 | -------------------------------------------------------------------------------- /danger-kotlin/src/runnerMain/kotlin/systems.danger/Log.kt: -------------------------------------------------------------------------------- 1 | package systems.danger 2 | 3 | import kotlin.native.concurrent.ThreadLocal 4 | 5 | private object Color { 6 | private const val escape = '\u001B' 7 | const val default = "$escape[0;0m" 8 | const val red = "$escape[31m" 9 | const val yellow = "$escape[33m" 10 | } 11 | 12 | @ThreadLocal 13 | object Log { 14 | 15 | var isVerbose: Boolean = false 16 | 17 | fun info(message: String, verbose: Boolean = false) { 18 | printMessage(message, Color.default, verbose) 19 | } 20 | 21 | fun warning(message: String, verbose: Boolean = false) { 22 | printMessage("WARNING: $message", Color.yellow, verbose) 23 | } 24 | 25 | fun error(message: String, verbose: Boolean = false) { 26 | printMessage("ERROR: $message", Color.red, verbose) 27 | } 28 | 29 | private fun printMessage(message: String, color: String, verbose: Boolean) { 30 | if (!verbose || (verbose && this.isVerbose)) { 31 | println(color + message + Color.default) 32 | } 33 | } 34 | } 35 | -------------------------------------------------------------------------------- /danger-kotlin/src/runnerMain/kotlin/systems.danger/cmd/Cmd.kt: -------------------------------------------------------------------------------- 1 | package systems.danger.cmd 2 | 3 | import platform.posix.* 4 | import systems.danger.Log 5 | 6 | class Cmd { 7 | private lateinit var name: String 8 | private lateinit var args: Array 9 | 10 | fun name(name: String) = apply { 11 | this.name = name 12 | } 13 | 14 | fun args(vararg args: String) = apply { 15 | this.args = args 16 | } 17 | 18 | fun exec() { 19 | exec(true) 20 | } 21 | 22 | private fun exec(printCallLog: Boolean) { 23 | "$name ${args.joinToString(" ")}".apply { 24 | if (printCallLog) { 25 | Log.info("Executing $this - pid ${getpid()}") 26 | } 27 | }.also { 28 | val exitCode = system(it) 29 | 30 | if (exitCode != 0) { 31 | throw Exception("Command $it exited with code $exitCode") 32 | } 33 | } 34 | } 35 | } 36 | -------------------------------------------------------------------------------- /danger-kotlin/src/runnerMain/kotlin/systems.danger/cmd/Command.kt: -------------------------------------------------------------------------------- 1 | package systems.danger.cmd 2 | 3 | enum class Command(val argument: String) { 4 | CI("ci"), 5 | LOCAL("local"), 6 | PR("pr"), 7 | RUNNER("runner"); 8 | 9 | companion object { 10 | fun forArgument(argument: String) = values().find{ it.argument == argument } 11 | } 12 | 13 | val description: String 14 | get() { 15 | when (this) { 16 | Command.CI -> { 17 | return "Use this on CI" 18 | } 19 | Command.LOCAL -> { 20 | return "Use this to run danger against your local changes from master/main" 21 | } 22 | Command.PR -> { 23 | return "Run danger-kotlin locally against a PR" 24 | } 25 | Command.RUNNER -> { 26 | return "Triggers the Dangerfile evaluation (used mainly by DangerJS)" 27 | } 28 | } 29 | } 30 | } 31 | -------------------------------------------------------------------------------- /danger-kotlin/src/runnerMain/kotlin/systems.danger/cmd/dangerfile/DangerFile.kt: -------------------------------------------------------------------------------- 1 | package systems.danger.cmd.dangerfile 2 | 3 | import systems.danger.cmd.* 4 | import kotlinx.cinterop.CPointer 5 | import platform.posix.* 6 | import systems.danger.Log 7 | 8 | object DangerFile : DangerFileBridge { 9 | private const val DANGERFILE_EXTENSION = ".df.kts" 10 | private const val DANGERFILE = "Dangerfile$DANGERFILE_EXTENSION" 11 | private val platformExpectedLibLocations = setOf( 12 | "/usr/local", // x86 location 13 | "/opt/local", // Arm 14 | "/opt/homebrew", // Homebrew Arm 15 | "/usr", // Fallback 16 | ) 17 | 18 | override fun execute(inputJson: String, outputJson: String) { 19 | val dangerKotlinJarPath = platformExpectedLibLocations 20 | .map { "$it/lib/danger/danger-kotlin.jar" } 21 | .filter { access(it, F_OK) == 0 } 22 | .also { 23 | if (it.isEmpty()) { 24 | Log.error("lib/danger/danger-kotlin.jar not found in following location ${platformExpectedLibLocations.joinToString()}") 25 | exit(1) 26 | } 27 | }.first() 28 | 29 | val dangerfile = dangerfileParameter(inputJson) ?: DANGERFILE 30 | 31 | if (!dangerfile.endsWith(DANGERFILE_EXTENSION)) { 32 | Log.error("The Dangerfile is not valid, it must have '$DANGERFILE_EXTENSION' as extension") 33 | exit(1) 34 | } 35 | 36 | Log.info("Compiling Dangerfile $dangerfile", true) 37 | 38 | Cmd().name("kotlinc").args( 39 | "-script-templates", 40 | "systems.danger.kts.DangerFileScript", 41 | "-cp", 42 | dangerKotlinJarPath, 43 | "-script", 44 | dangerfile, 45 | inputJson, 46 | outputJson 47 | ).exec() 48 | } 49 | } 50 | 51 | private fun dangerfileParameter(inputJson: String): String? { 52 | var result: String? = null 53 | 54 | fopen(inputJson, "r")?.apply { 55 | do { 56 | val line = readLine(this)?.let { 57 | val trimmedLine = it.trim() 58 | if (trimmedLine.startsWith("\"dangerfile\":")) { 59 | val dangerFile = 60 | trimmedLine.removePrefix("\"dangerfile\": \"").removeSuffix("\"").removeSuffix("\",") 61 | result = dangerFile 62 | } 63 | } 64 | } while (line != null && result == null) 65 | }.also { 66 | fclose(it) 67 | } 68 | 69 | return result 70 | } 71 | 72 | private fun readLine(file: CPointer): String? { 73 | var ch = getc(file) 74 | var lineBuffer: Array = arrayOf() 75 | 76 | while ((ch != '\n'.code) && (ch != EOF)) { 77 | lineBuffer += ch.toChar() 78 | 79 | ch = getc(file) 80 | } 81 | 82 | return if (lineBuffer.isEmpty()) { 83 | null 84 | } else { 85 | lineBuffer.joinToString("") 86 | } 87 | } 88 | -------------------------------------------------------------------------------- /danger-kotlin/src/runnerMain/kotlin/systems.danger/cmd/dangerfile/DangerFileBridge.kt: -------------------------------------------------------------------------------- 1 | package systems.danger.cmd.dangerfile 2 | 3 | interface DangerFileBridge { 4 | fun execute(inputJson: String, outputJson: String) 5 | } 6 | -------------------------------------------------------------------------------- /danger-kotlin/src/runnerMain/kotlin/systems.danger/cmd/dangerjs/DangerJS.kt: -------------------------------------------------------------------------------- 1 | package systems.danger.cmd.dangerjs 2 | 3 | import systems.danger.Log 4 | import systems.danger.cmd.Cmd 5 | import systems.danger.cmd.Command 6 | 7 | object DangerJS: DangerJSBridge { 8 | override fun process(command: Command, processName: String, args: List) { 9 | Log.info("Launching Danger-JS", verbose = true) 10 | with(Cmd()) { 11 | val dangerJSArgumentIndex = args.indexOf("--danger-js-path") 12 | val dangerJSPath: String 13 | if (dangerJSArgumentIndex != -1 && args.count() > dangerJSArgumentIndex + 1) { 14 | dangerJSPath = args[dangerJSArgumentIndex + 1] 15 | } else { 16 | dangerJSPath = "$(which danger)" 17 | } 18 | name("$dangerJSPath ${command.argument} --process $processName --passURLForDSL") 19 | args(args.joinToString(" ")) 20 | exec() 21 | } 22 | } 23 | } 24 | 25 | -------------------------------------------------------------------------------- /danger-kotlin/src/runnerMain/kotlin/systems.danger/cmd/dangerjs/DangerJSBridge.kt: -------------------------------------------------------------------------------- 1 | package systems.danger.cmd.dangerjs 2 | 3 | import systems.danger.cmd.Command 4 | 5 | interface DangerJSBridge { 6 | fun process(command: Command, processName: String, args: List) 7 | } 8 | -------------------------------------------------------------------------------- /danger-plugin-installer/build.gradle: -------------------------------------------------------------------------------- 1 | plugins { 2 | id 'org.jetbrains.kotlin.jvm' 3 | id 'java-gradle-plugin' 4 | id 'maven-publish' 5 | } 6 | 7 | group 'systems.danger' 8 | version '0.1-alpha' 9 | 10 | repositories { 11 | mavenCentral() 12 | } 13 | 14 | gradlePlugin { 15 | plugins { 16 | simplePlugin { 17 | id = "danger-kotlin-plugin-installer" 18 | implementationClass = "systems.danger.kotlin.plugininstaller.PluginInstaller" 19 | } 20 | } 21 | } 22 | 23 | dependencies { 24 | testImplementation 'org.junit.jupiter:junit-jupiter-api:5.6.0' 25 | testRuntimeOnly 'org.junit.jupiter:junit-jupiter-engine' 26 | implementation "org.jetbrains.kotlin:kotlin-stdlib-jdk8" 27 | implementation gradleApi() 28 | } 29 | 30 | test { 31 | useJUnitPlatform() 32 | } 33 | -------------------------------------------------------------------------------- /danger-plugin-installer/src/main/kotlin/systems/danger/kotlin/plugininstaller/PluginInstaller.kt: -------------------------------------------------------------------------------- 1 | package systems.danger.kotlin.plugininstaller 2 | 3 | import org.gradle.api.Plugin 4 | import org.gradle.api.Project 5 | import java.io.File 6 | 7 | open class PluginInstallerExtension { 8 | var outputJar: String? = null 9 | } 10 | 11 | @SuppressWarnings("unused") 12 | class PluginInstaller : Plugin { 13 | override fun apply(target: Project) { 14 | val extension = target.extensions.create("dangerPlugin", PluginInstallerExtension::class.java) 15 | val dangerLibDir = File("/usr/local/lib/danger/libs") 16 | 17 | target.tasks.create("installDangerPlugin") { 18 | it.doLast { 19 | extension.outputJar?.let { outputJar -> 20 | val compiledJar = File(outputJar).takeIf { jar -> jar.isFile && jar.exists() } 21 | compiledJar?.let { jar -> 22 | jar.run { 23 | copyTo(File(dangerLibDir, jar.name), true) 24 | } 25 | } 26 | } 27 | } 28 | } 29 | } 30 | } -------------------------------------------------------------------------------- /dependencyVersions.gradle: -------------------------------------------------------------------------------- 1 | apply from: file('../shadow.gradle') 2 | 3 | def group(Closure closure) { 4 | closure.delegate = dependencies 5 | return closure 6 | } 7 | 8 | // Utils 9 | ext.utils = [ 10 | // Empty 11 | ] 12 | 13 | ext.utilsDependencies = group { 14 | // Empty 15 | } 16 | 17 | project.ext.artifactIdMoshi = 'moshi' 18 | project.ext.versionOkio = '1.16.0' 19 | project.ext.groupIdOkio = 'com.squareup.okio' 20 | project.ext.artifactIdOkio = 'okio' 21 | 22 | // Kotlin 23 | project.ext.versionKotlin = '2.0.21' 24 | project.ext.groupIdKotlin = 'org.jetbrains.kotlin' 25 | project.ext.groupIdKotlinx = 'org.jetbrains.kotlinx' 26 | project.ext.artifactIdKotlinMain = 'kotlin-main-kts' 27 | project.ext.artifactIdKotlinSerializationJson = 'kotlinx-serialization-json' 28 | project.ext.artifactIdKotlinDatetime = "kotlinx-datetime" 29 | project.ext.artifactIdKotlinCoroutines = "kotlinx-coroutines-core" 30 | project.ext.versionKotlinSerializationJson = '1.7.3' 31 | project.ext.versionKotlinDatetime = '0.6.1' 32 | project.ext.versionKotlinCoroutines = '1.9.0' 33 | 34 | ext.kotlin = [ 35 | mainKts: "$groupIdKotlin:$artifactIdKotlinMain:$versionKotlin", 36 | serialization: "$groupIdKotlinx:$artifactIdKotlinSerializationJson:$versionKotlinSerializationJson", 37 | datetime: "$groupIdKotlinx:$artifactIdKotlinDatetime:$versionKotlinDatetime", 38 | coroutines: "$groupIdKotlinx:$artifactIdKotlinCoroutines:$versionKotlinCoroutines" 39 | ] 40 | ext.kotlinDependencies = group { 41 | includeJar kotlin.mainKts 42 | includeRecursiveJar kotlin.serialization 43 | includeRecursiveJar kotlin.datetime 44 | includeRecursiveJar kotlin.coroutines 45 | } 46 | 47 | // Testing 48 | project.ext.versionJunit = '4.12' 49 | project.ext.groupIdJunit = 'junit' 50 | project.ext.artifactIdJunit = 'junit' 51 | project.ext.versionMockK = "1.10.0" 52 | project.ext.groupIdMockK = "io.mockk" 53 | project.ext.artifactIdMockK = "mockk" 54 | ext.testing = [ 55 | junit: "junit:junit:$versionJunit", 56 | mockK: "$groupIdMockK:$artifactIdMockK:$versionMockK" 57 | ] 58 | ext.testingDependencies = group { 59 | testImplementation testing.junit 60 | testImplementation testing.mockK 61 | } 62 | -------------------------------------------------------------------------------- /docs/guides/about_the_dangerfile.html.md: -------------------------------------------------------------------------------- 1 | --- 2 | title: About the Dangerfile 3 | subtitle: The Dangerfile 4 | layout: guide_kt 5 | order: 1 6 | blurb: Step two on using Danger in your app, how to work locally and nuances around working with files. 7 | --- 8 | 9 | ## Writing your Dangerfile 10 | 11 | All of Danger is built in Kotlin, aims to have 100% in-line docs. This means a lot of your exploration can be done inside Intellij Idea. 12 | This document aims to give you some high level knowledge on how to work on your `Dangerfile.df.kts`. 13 | 14 | ## Working on your Dangerfile 15 | 16 | There are two ways to locally work on your Dangerfile. These both rely on using an external API locally, so you may hit 17 | their API rate-limits or need to have authenticated request for private repos. In which case you can use an access token 18 | to do authenticated requests by exposing a token to Danger. 19 | 20 | ```sh 21 | export DANGER_GITHUB_API_TOKEN='xxxx' 22 | 23 | # or for BitBucket by username and password 24 | export DANGER_BITBUCKETSERVER_HOST='xxxx' DANGER_BITBUCKETSERVER_USERNAME='yyyy' DANGER_BITBUCKETSERVER_PASSWORD='zzzz' 25 | 26 | # or for BitBucket by username and personal access token 27 | export DANGER_BITBUCKETSERVER_HOST='xxxx' DANGER_BITBUCKETSERVER_USERNAME='yyyy' DANGER_BITBUCKETSERVER_TOKEN='zzzz' 28 | ``` 29 | 30 | Then the danger CLI will use authenticated API calls, which don't get hit by API limits. 31 | 32 | ### Using danger pr 33 | 34 | The command `danger-kotlin pr` expects an argument of a PR url, e.g: 35 | 36 | ```sh 37 | danger-kotlin pr https://github.com/danger/kotlin/pull/64 38 | ``` 39 | 40 | This will use your local `Dangerfile.df.kts` against the metadata of the linked PR. Danger will then output the results 41 | into your terminal, instead of inside the PR itself. 42 | 43 | This _will not_ post comments in that PR. 44 | 45 | ### Using `danger` and Faking being on a CI 46 | 47 | If you create an 48 | [appropriately scoped temporary api token](http://danger.systems/js/guides/getting_started.html#setting-up-an-access-token) 49 | for your GitHub account, this can be a good way to see if danger is suitable for you before integrating it into your CI 50 | system. 51 | 52 | You can manually trigger danger against a pull request on the command line by setting the following environmental 53 | variables: 54 | 55 | ```bash 56 | export DANGER_FAKE_CI="YEP" 57 | export DANGER_TEST_REPO='username/reponame' 58 | ``` 59 | 60 | Then you can run against a local branch that is attached to a pull-request, by running the following: 61 | 62 | ```bash 63 | git checkout branch-for-pr-1234 64 | DANGER_TEST_PR='1234' danger-kotlin ci 65 | ``` 66 | 67 | Assuming that your local file-system matches up to that branch for your code review, this will be a good approximation 68 | of how danger will work when you integrate it into your CI system. Note: this **will** leave a comment on the PR. 69 | 70 | ### Plugins 71 | 72 | You can use any external dependency by adding the following lines at the top of your Dangerfile.df.kts 73 | 74 | ```kotlin 75 | @file:Repository("https://repo.maven.apache.org") 76 | @file:DependsOn("groupId:artifactId:version") 77 | ``` 78 | 79 | In case you are importing a Danger Kotlin plugin, you will have to register it with: 80 | 81 | ```kotlin 82 | register plugin AndroidLint 83 | ``` 84 | 85 | ## Finding more info 86 | 87 | The [CHANGELOG][changelog] for Danger is kept entirely end-user focused, so if there is an aspect of the Dangerfile that 88 | you do not know, or looks confusing and there is nothing in the documentation - [check the CHANGELOG][changelog]. This 89 | is where we write-up why a change happened, and how it can affect Danger users. 90 | 91 | [changelog]: http://danger.systems/kotlin/changelog.html 92 | -------------------------------------------------------------------------------- /docs/tutorials/architecture.html.md: -------------------------------------------------------------------------------- 1 | --- 2 | title: Architecture 3 | subtitle: How is Danger Kotlin architected 4 | layout: guide_kt 5 | order: 1 6 | blurb: How do Danger Kotlin and JS interact? 7 | --- 8 | 9 | ## How does Danger Kotlin work? 10 | 11 | Danger provides an evaluation system for creating per-application rules. Basically, it is running arbitrary Kotlin with 12 | some extra PR metadata added in at runtime. 13 | 14 | Pulling this off though, is a bit of a thing. 15 | 16 | ## Setup 17 | 18 | Danger Kotlin is powered by Danger JS. Think of it as a Kotlin sandwich: `[Danger JS] -> [Danger Kotlin] -> [Danger JS]`. 19 | Danger JS first gets all the CI and Platform metadata, then passes that to Danger Kotlin, which returns the results of 20 | your Kotlin rules back to Danger JS. 21 | 22 | ``` 23 | +--------------------------------+ +----------------------+ +--------------------+ 24 | | | | | | | 25 | | ## Danger JS | | ## Danger Kotlin | | ## Danger JS | 26 | | | | | | | 27 | | Get GitHub/BitBucket/etc info | +----> | Setup plugins | +----> | Update PR info | 28 | | | | | | | 29 | | Transform into JSON | | Evaluate Dangerfile | | Pass / fail build | 30 | | | | | | | 31 | +--------------------------------+ +----------------------+ +--------------------+ 32 | ``` 33 | 34 | **Step 1: CI**. Danger JS needs to figure out what CI we're running on. You can see them all [in 35 | `source/ci_source/providers`][provs]. These use ENV VARs to figure out which CI `danger ci` is running on and validate 36 | whether it is a pull request. 37 | 38 | **Step 2: Platform**. Next, Danger JS needs to know which platform the code review is happening in. Today it's GitHub, BitBucket Server and GitLab. 39 | 40 | **Step 3: JSON DSL**. All the metadata from the above two steps are transformed into a JSON file, which is passed into 41 | Danger Kotlin. 42 | 43 | **Step 4: Kotlin Plugin Setup**. Danger has to prepare your code to be compiled, so any plugins need to be set up before 44 | compilation and runtime evaluation. 45 | 46 | **Step 5: Evaluation**. Most of the Danger Kotlin setup occurs when you run, `val danger = Danger(args)` in your 47 | `Dangerfile.df.kts` - it's nearly all smart JSON parsing into real Kotlin objects. The dangerfile uses `markdown`, 48 | `warning`, `fail` or `message` to pass results to a singleton. 49 | 50 | **Step 6: Passing Results**. The results from the evaluation are turned into JSON, and then passed back to Danger JS. 51 | 52 | **Step 6: Feedback**. Danger JS reads the results, then chooses whether to create/delete/edit any messages in the code 53 | review site. From the results of the run, Danger JS will then pass or fail the build. 54 | 55 | [provs]: https://github.com/danger/danger-js/tree/master/source/ci_source/providers 56 | -------------------------------------------------------------------------------- /docs/tutorials/fast_feedback.html.md: -------------------------------------------------------------------------------- 1 | --- 2 | title: Fast Feedback via Danger Local 3 | subtitle: Platformless 4 | layout: guide_kt 5 | order: 4 6 | blurb: How to use Danger to get per-commit feedback 7 | --- 8 | 9 | ## Before we get started 10 | 11 | This tutorial continues after "Getting Started" - it's not required that you have Danger Kotlin running on your CI 12 | though, but assumes some familiarity. 13 | 14 | ## Locality 15 | 16 | With Danger, the typical flow is to help you can check rules on CI and get feedback inside your PR. With Peril you can 17 | move those rules to run on an external server making feedback instant. `danger-kotlin local` provides a somewhat hybrid 18 | approach. 19 | 20 | `danger-kotlin local` provides a way to run a Dangerfile based on git-hooks. This let's you run rules while you are still in 21 | the same context as your work as opposed to later during the code review. Personally, I find this most useful on 22 | projects when I ship 90% of the code to it. 23 | 24 | ## How it works 25 | 26 | Where `danger-kotlin ci` uses information from the Pull Request to figure out what has changed, `danger-kotlin local` naively uses the 27 | local differences in git from master to the current commit to derive the runtime environment. This is naive because if 28 | you don't keep your master branch in-sync, then it will be checking across potentially many branches. 29 | 30 | Inside a Dangerfile `danger.github` and `danger.bitbucketServer` will be `nil`, so you can share a Dangerfile between 31 | `danger-kotlin local` and `danger-kotlin ci` as long as you verify that these objects exist before using them. 32 | 33 | When I thought about how I wanted to use `danger-kotlin local` on repos in the Danger org, I opted to make a separate 34 | Dangerfile for `danger-kotlin local` and import this at the end of the main Dangerfile. This new Dangerfile only contains rules 35 | which can run with just `danger.git`, e.g. CHANGELOG/README checks. I called it `Dangerfile.lite.kt`. 36 | -------------------------------------------------------------------------------- /docs/tutorials/plugin_development.html.md: -------------------------------------------------------------------------------- 1 | --- 2 | title: Plugin Developement 3 | subtitle: How to build a plugin for Danger Kotlin 4 | layout: guide_kt 5 | order: 2 6 | blurb: How do i make a plugin for Danger Kotlin? 7 | --- 8 | 9 | ## Introduction 10 | 11 | Danger provides an sdk for the plugin development. This sdk works with the principle of dependency inversion, 12 | that means you will be able to use the danger kotlin interfaces without import danger directly in your project. 13 | 14 | Everything you need is just IntelliJ IDEA and the kotlin environment setup on your machine. 15 | 16 | ## Setup 17 | 18 | **Step 1: Create a new project**. From IntelliJ IDEA create a new Project `File -> New -> Project` and choose a `Gradle` project with `Java` and `Koltin/JVM` 19 | 20 | **Step 2: Add the sdk as dependency**. In your `build.gradle` add the following dependency: 21 | ```groovy 22 | dependencies { 23 | implementation "systems.danger:danger-kotlin-sdk:1.2" 24 | } 25 | ``` 26 | 27 | **Step 3: Create your main plugin class**. Create your plugin class as follow: 28 | ```kotlin 29 | package com.test.myawesomeplugin 30 | 31 | import systems.danger.kotlin.sdk.DangerPlugin 32 | 33 | object MyAwesomeDangerPlugin : DangerPlugin() { 34 | override val id: String 35 | get() = this.javaClass.name 36 | 37 | fun helloPlugin() { 38 | context.message("Hello Danger Plugin!") 39 | } 40 | } 41 | ``` 42 | 43 | **Step 4: Build your plugin**. You can copy manually your compiled jar into `/usr/local/lib/danger/libs` or alternatively use the gradle plugin as follow: 44 | ```groovy 45 | buildscript { 46 | repositories { 47 | mavenLocal() 48 | } 49 | 50 | dependencies { 51 | classpath "systems.danger:danger-plugin-installer:0.1-alpha" 52 | } 53 | } 54 | 55 | apply plugin: 'danger-kotlin-plugin-installer' 56 | 57 | group 'com.test' 58 | version '0.0.1' 59 | 60 | dangerPlugin { 61 | outputJar = "${buildDir}/libs/my-awesome-plugin-0.0.1.jar" 62 | } 63 | ``` 64 | then invoke `./gradlew build` and `./gradlew installDangerPlugin`. 65 | 66 | **Step 5: Try your plugin with Danger**. Create your `Dangerfile.df.kts` and import your plugin. 67 | ```kotlin 68 | @file:DependsOn("my-awesome-plugin-0.0.1.jar") 69 | 70 | import com.test.myawesomeplugin.MyAwesomePlugin 71 | import systems.danger.kotlin.* 72 | 73 | register plugin MyAwesomePlugin 74 | 75 | val danger = Danger(args) 76 | 77 | MyAwesomePlugin.helloPlugin() 78 | ``` 79 | If danger is setup correctly you should see a message on your PR: `Hello Danger Plugin!` 80 | 81 | **Step 5: Maven publication**. Publish your plugin to maven central and add your plugin in [awesome-danger]. Then your `Dangerfile.df.kts` will be like: 82 | ```kotlin 83 | @file:DependsOn("com.test:my-awesome-plugin:version") 84 | //You can add more than one maven repository 85 | @file:Repository("http://url.to.maven.repo/repository") 86 | 87 | import com.test.myawesomeplugin.MyAwesomePlugin 88 | import systems.danger.kotlin.* 89 | 90 | register plugin MyAwesomePlugin 91 | 92 | val danger = Danger(args) 93 | 94 | MyAwesomePlugin.helloPlugin() 95 | ``` 96 | 97 | ## Resources 98 | 99 | Some plugins can be found in [awesome-danger] and an example plugin is also available [here] 100 | 101 | [awesome-danger]: https://github.com/danger/awesome-danger#kotlin-danger-kotlin 102 | [here]: https://github.com/danger/kotlin/danger-kotlin-sample-plugin 103 | -------------------------------------------------------------------------------- /docs/usage/bitbucket.html.md: -------------------------------------------------------------------------------- 1 | --- 2 | title: Danger + BitBucket Server 3 | subtitle: Dangerous bits 4 | layout: guide_kt 5 | order: 3 6 | blurb: An overview of using Danger with BitBucket Server, and some examples 7 | --- 8 | 9 | To use Danger Kotlin with BitBucket Server: you'll need to create a new account for Danger to use, then set the following 10 | environment variables on your CI: 11 | 12 | - `DANGER_BITBUCKETSERVER_HOST` = The root URL for your server, e.g. `https://bitbucket.mycompany.com`. 13 | - `DANGER_BITBUCKETSERVER_USERNAME` = The username for the account used to comment. 14 | 15 | Also you need to set password or 16 | [personal access token](https://confluence.atlassian.com/bitbucketserver/personal-access-tokens-939515499.html) by 17 | environment variables: 18 | 19 | - `DANGER_BITBUCKETSERVER_PASSWORD` = The password for the account used to comment. 20 | - `DANGER_BITBUCKETSERVER_TOKEN` = The personal access token for the account used to comment. 21 | 22 | Then in your Dangerfiles you will have a fully fleshed out `danger.bitbucketServer` object to work with. For example: 23 | 24 | ```kotlin 25 | val danger = Danger(args) 26 | 27 | val isAssignedToMe = danger.bitBucketServer.pullRequest.reviewers.map { it.user.name }.contains("orta") 28 | if (isAssignedToMe) { 29 | fail("You should probably assign someone else") 30 | } 31 | ``` 32 | 33 | The DSL is expansive, but the TLDR is: 34 | 35 | ```kotlin 36 | danger.bitbucketServer. 37 | 38 | /** The pull request and repository metadata */ 39 | metadata: RepoMetaData 40 | /** The PR metadata */ 41 | pullRequest: BitBucketServerPR 42 | /** The commits associated with the pull request */ 43 | commits: [BitBucketServerCommit] 44 | /** The comments on the pull request */ 45 | comments: [BitBucketServerPRActivity] 46 | /** The activities such as OPENING, CLOSING, MERGING or UPDATING a pull request */ 47 | activities: [BitBucketServerPRActivity] 48 | ``` 49 | 50 | Any example you can find that uses GitHub, will probably work in BitBucket Server, with a bit of DSL 51 | translation. 52 | 53 | In addition, it is possible to specify a proxy to be used for the requests using environmental variables. This is useful 54 | for debugging: 55 | 56 | ```bash 57 | export NODE_TLS_REJECT_UNAUTHORIZED=0 # Avoid certificate error 58 | 59 | export http_proxy=http://127.0.0.1:8080 60 | or 61 | export https_proxy=https://127.0.0.1:8080 62 | ``` 63 | -------------------------------------------------------------------------------- /docs/usage/culture.html.md: -------------------------------------------------------------------------------- 1 | --- 2 | title: Cultural Changes 3 | subtitle: Cultural Changes 4 | layout: guide_kt 5 | order: 0 6 | blurb: 7 | Discussing introducing Danger into a team, how you can use it to provide positive feedback and encourage adoption of 8 | new rules. 9 | --- 10 | 11 | ## Introducing Danger 12 | 13 | It can be easy to try and jump straight from no Dangerfile to a 200 line complex set of cultural rules. We'd advise 14 | against introducing a long list of rules for Danger all at once. In our experience, gradual integration works better. 15 | The entire team may have agreed on the changes, but slower adoption has worked better for teams new to working with 16 | Danger. 17 | 18 | At Artsy we've found that first just integrating Danger with a single simple rule (like checking for a CHANGELOG entry) 19 | then starting to introduce complexity piece-meal. Encouraging different contributors to make the changes has made it 20 | easier to go from "Ah, we shouldn't do that again" to "Oh, we could make a Danger rule for that" to "Here's the PR". 21 | 22 | That is your end goal, making it so that everyone feels like it's easy to add and amend the rules as a project evolves. 23 | Making dramatic changes erodes that feeling, making regular small ones improves it. 24 | 25 | ## Phrasing 26 | 27 | One of Danger's greatest features is that it can free individuals up on the team to stop being "the person who always 28 | requests more tests" on a PR. By moving a lot of the rote tasks in code review to a machine, you free up some mental 29 | space to concentrate on more important things. One of the downsides is that it is impossible to provide the same level 30 | of nuance in how you provide feedback. 31 | 32 | You should use Danger to provide impartial feedback. Consider how these messages come across: 33 | 34 | - You have not added a CHANGELOG entry. 35 | - There isn't a CHANGELOG entry. 36 | - No CHANGELOG entry. 37 | - This PR does not include a CHANGELOG entry. 38 | 39 | The first feels like a statement that someone has intentionally done something wrong, and Danger has caught them in the 40 | act. 41 | 42 | The second aims to feel a lot like a testing framework telling you "Test Suites: 104 passed, 2 failures". 43 | 44 | The third if done consistently can work out well. Terse entries can work well when you have a large series of rules, as 45 | it feels like a check list to do. 46 | 47 | The fourth is what we generally try to aim for, an impartial but polite mention about the state of submitted PR. 48 | 49 | You can find what fits for you and your team, but being blameless in your messaging is one way to ensure that someone's 50 | first and 500th interaction is still great. 51 | 52 | ## Positive Rules 53 | 54 | Danger doesn't have to be used to provide a checklist of TODOs which have to be done. You can make rules that celebrate 55 | achievements, for example: 56 | 57 | - Submit congratulations when your app has had a version bump. 58 | - Thank the author for reducing the dependency tree. 59 | - Highlighting when someone has removed more code than adding it. 60 | - If there are notably tricky areas of the codebase, pass along a thumbs up for daring to improve it. 61 | 62 | There's aren't contrived either - I've seen variations on all of these rules inside Dangerfiles. 63 | 64 | -------------------------------------------------------------------------------- /docs/usage/gitlab.html.md: -------------------------------------------------------------------------------- 1 | --- 2 | title: Danger + GitLab 3 | subtitle: Self-Hosted 4 | layout: guide_kt 5 | order: 5 6 | blurb: An overview of using Danger with GitLab, and some examples 7 | --- 8 | 9 | To use Danger Kotlin with GitLab: you'll need to create a new account for Danger to use, then set the following environment 10 | variables on your CI system: 11 | 12 | - `DANGER_GITLAB_HOST` = Defaults to `https://gitlab.com` but you can use it for your own url 13 | - `DANGER_GITLAB_API_TOKEN` = An access token for the account which will post comments 14 | 15 | Then in your Dangerfiles you will have a fully fleshed out `danger.gitlab` object to work with. For example: 16 | 17 | ```kotlin 18 | if (danger.gitlab.mr.title.contains("WIP")) { 19 | warn("PR is considered WIP") 20 | } 21 | ``` 22 | 23 | The DSL is expansive, you can see all the details inside the [Danger Kotlin Reference][ref] 24 | -------------------------------------------------------------------------------- /github-action/Dockerfile: -------------------------------------------------------------------------------- 1 | FROM ghcr.io/danger/danger-kotlin:1.3.3 2 | 3 | COPY entrypoint.sh /entrypoint.sh 4 | 5 | RUN chmod +x /entrypoint.sh 6 | 7 | ENTRYPOINT ["/entrypoint.sh"] 8 | -------------------------------------------------------------------------------- /github-action/entrypoint.sh: -------------------------------------------------------------------------------- 1 | #! /bin/bash 2 | 3 | set -e 4 | 5 | dangerFile=$1 6 | runMode=$2 7 | jobId=$3 8 | args=$4 9 | 10 | echo "Danger JS version:" 11 | danger --version 12 | echo "Danger Kotlin version:" 13 | danger-kotlin --version 14 | 15 | if [ -f "$dangerFile" ]; then 16 | danger-kotlin $runMode --dangerfile="$dangerFile" --id="$jobId" $args 17 | exit 0 18 | else 19 | echo "Danger file $dangerFile does not exist." 20 | exit 1 21 | fi 22 | -------------------------------------------------------------------------------- /gradle.properties: -------------------------------------------------------------------------------- 1 | kotlinVersion=2.0.21 2 | kotlin.code.style=official 3 | systemProp.org.gradle.internal.publish.checksums.insecure=true 4 | -------------------------------------------------------------------------------- /gradle/wrapper/gradle-wrapper.jar: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/danger/kotlin/2fc3cf363279b061be07fd82b28e405c2d53b922/gradle/wrapper/gradle-wrapper.jar -------------------------------------------------------------------------------- /gradle/wrapper/gradle-wrapper.properties: -------------------------------------------------------------------------------- 1 | distributionBase=GRADLE_USER_HOME 2 | distributionPath=wrapper/dists 3 | distributionUrl=https\://services.gradle.org/distributions/gradle-8.10.2-bin.zip 4 | zipStoreBase=GRADLE_USER_HOME 5 | zipStorePath=wrapper/dists 6 | -------------------------------------------------------------------------------- /gradlew: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | 3 | # 4 | # Copyright © 2015-2021 the original authors. 5 | # 6 | # Licensed under the Apache License, Version 2.0 (the "License"); 7 | # you may not use this file except in compliance with the License. 8 | # You may obtain a copy of the License at 9 | # 10 | # https://www.apache.org/licenses/LICENSE-2.0 11 | # 12 | # Unless required by applicable law or agreed to in writing, software 13 | # distributed under the License is distributed on an "AS IS" BASIS, 14 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 15 | # See the License for the specific language governing permissions and 16 | # limitations under the License. 17 | # 18 | 19 | ############################################################################## 20 | # 21 | # Gradle start up script for POSIX generated by Gradle. 22 | # 23 | # Important for running: 24 | # 25 | # (1) You need a POSIX-compliant shell to run this script. If your /bin/sh is 26 | # noncompliant, but you have some other compliant shell such as ksh or 27 | # bash, then to run this script, type that shell name before the whole 28 | # command line, like: 29 | # 30 | # ksh Gradle 31 | # 32 | # Busybox and similar reduced shells will NOT work, because this script 33 | # requires all of these POSIX shell features: 34 | # * functions; 35 | # * expansions «$var», «${var}», «${var:-default}», «${var+SET}», 36 | # «${var#prefix}», «${var%suffix}», and «$( cmd )»; 37 | # * compound commands having a testable exit status, especially «case»; 38 | # * various built-in commands including «command», «set», and «ulimit». 39 | # 40 | # Important for patching: 41 | # 42 | # (2) This script targets any POSIX shell, so it avoids extensions provided 43 | # by Bash, Ksh, etc; in particular arrays are avoided. 44 | # 45 | # The "traditional" practice of packing multiple parameters into a 46 | # space-separated string is a well documented source of bugs and security 47 | # problems, so this is (mostly) avoided, by progressively accumulating 48 | # options in "$@", and eventually passing that to Java. 49 | # 50 | # Where the inherited environment variables (DEFAULT_JVM_OPTS, JAVA_OPTS, 51 | # and GRADLE_OPTS) rely on word-splitting, this is performed explicitly; 52 | # see the in-line comments for details. 53 | # 54 | # There are tweaks for specific operating systems such as AIX, CygWin, 55 | # Darwin, MinGW, and NonStop. 56 | # 57 | # (3) This script is generated from the Groovy template 58 | # https://github.com/gradle/gradle/blob/master/subprojects/plugins/src/main/resources/org/gradle/api/internal/plugins/unixStartScript.txt 59 | # within the Gradle project. 60 | # 61 | # You can find Gradle at https://github.com/gradle/gradle/. 62 | # 63 | ############################################################################## 64 | 65 | # Attempt to set APP_HOME 66 | 67 | # Resolve links: $0 may be a link 68 | app_path=$0 69 | 70 | # Need this for daisy-chained symlinks. 71 | while 72 | APP_HOME=${app_path%"${app_path##*/}"} # leaves a trailing /; empty if no leading path 73 | [ -h "$app_path" ] 74 | do 75 | ls=$( ls -ld "$app_path" ) 76 | link=${ls#*' -> '} 77 | case $link in #( 78 | /*) app_path=$link ;; #( 79 | *) app_path=$APP_HOME$link ;; 80 | esac 81 | done 82 | 83 | APP_HOME=$( cd "${APP_HOME:-./}" && pwd -P ) || exit 84 | 85 | APP_NAME="Gradle" 86 | APP_BASE_NAME=${0##*/} 87 | 88 | # Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. 89 | DEFAULT_JVM_OPTS='"-Xmx64m" "-Xms64m"' 90 | 91 | # Use the maximum available, or set MAX_FD != -1 to use that value. 92 | MAX_FD=maximum 93 | 94 | warn () { 95 | echo "$*" 96 | } >&2 97 | 98 | die () { 99 | echo 100 | echo "$*" 101 | echo 102 | exit 1 103 | } >&2 104 | 105 | # OS specific support (must be 'true' or 'false'). 106 | cygwin=false 107 | msys=false 108 | darwin=false 109 | nonstop=false 110 | case "$( uname )" in #( 111 | CYGWIN* ) cygwin=true ;; #( 112 | Darwin* ) darwin=true ;; #( 113 | MSYS* | MINGW* ) msys=true ;; #( 114 | NONSTOP* ) nonstop=true ;; 115 | esac 116 | 117 | CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar 118 | 119 | 120 | # Determine the Java command to use to start the JVM. 121 | if [ -n "$JAVA_HOME" ] ; then 122 | if [ -x "$JAVA_HOME/jre/sh/java" ] ; then 123 | # IBM's JDK on AIX uses strange locations for the executables 124 | JAVACMD=$JAVA_HOME/jre/sh/java 125 | else 126 | JAVACMD=$JAVA_HOME/bin/java 127 | fi 128 | if [ ! -x "$JAVACMD" ] ; then 129 | die "ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME 130 | 131 | Please set the JAVA_HOME variable in your environment to match the 132 | location of your Java installation." 133 | fi 134 | else 135 | JAVACMD=java 136 | which java >/dev/null 2>&1 || die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. 137 | 138 | Please set the JAVA_HOME variable in your environment to match the 139 | location of your Java installation." 140 | fi 141 | 142 | # Increase the maximum file descriptors if we can. 143 | if ! "$cygwin" && ! "$darwin" && ! "$nonstop" ; then 144 | case $MAX_FD in #( 145 | max*) 146 | MAX_FD=$( ulimit -H -n ) || 147 | warn "Could not query maximum file descriptor limit" 148 | esac 149 | case $MAX_FD in #( 150 | '' | soft) :;; #( 151 | *) 152 | ulimit -n "$MAX_FD" || 153 | warn "Could not set maximum file descriptor limit to $MAX_FD" 154 | esac 155 | fi 156 | 157 | # Collect all arguments for the java command, stacking in reverse order: 158 | # * args from the command line 159 | # * the main class name 160 | # * -classpath 161 | # * -D...appname settings 162 | # * --module-path (only if needed) 163 | # * DEFAULT_JVM_OPTS, JAVA_OPTS, and GRADLE_OPTS environment variables. 164 | 165 | # For Cygwin or MSYS, switch paths to Windows format before running java 166 | if "$cygwin" || "$msys" ; then 167 | APP_HOME=$( cygpath --path --mixed "$APP_HOME" ) 168 | CLASSPATH=$( cygpath --path --mixed "$CLASSPATH" ) 169 | 170 | JAVACMD=$( cygpath --unix "$JAVACMD" ) 171 | 172 | # Now convert the arguments - kludge to limit ourselves to /bin/sh 173 | for arg do 174 | if 175 | case $arg in #( 176 | -*) false ;; # don't mess with options #( 177 | /?*) t=${arg#/} t=/${t%%/*} # looks like a POSIX filepath 178 | [ -e "$t" ] ;; #( 179 | *) false ;; 180 | esac 181 | then 182 | arg=$( cygpath --path --ignore --mixed "$arg" ) 183 | fi 184 | # Roll the args list around exactly as many times as the number of 185 | # args, so each arg winds up back in the position where it started, but 186 | # possibly modified. 187 | # 188 | # NB: a `for` loop captures its iteration list before it begins, so 189 | # changing the positional parameters here affects neither the number of 190 | # iterations, nor the values presented in `arg`. 191 | shift # remove old arg 192 | set -- "$@" "$arg" # push replacement arg 193 | done 194 | fi 195 | 196 | # Collect all arguments for the java command; 197 | # * $DEFAULT_JVM_OPTS, $JAVA_OPTS, and $GRADLE_OPTS can contain fragments of 198 | # shell script including quotes and variable substitutions, so put them in 199 | # double quotes to make sure that they get re-expanded; and 200 | # * put everything else in single quotes, so that it's not re-expanded. 201 | 202 | set -- \ 203 | "-Dorg.gradle.appname=$APP_BASE_NAME" \ 204 | -classpath "$CLASSPATH" \ 205 | org.gradle.wrapper.GradleWrapperMain \ 206 | "$@" 207 | 208 | # Use "xargs" to parse quoted args. 209 | # 210 | # With -n1 it outputs one arg per line, with the quotes and backslashes removed. 211 | # 212 | # In Bash we could simply go: 213 | # 214 | # readarray ARGS < <( xargs -n1 <<<"$var" ) && 215 | # set -- "${ARGS[@]}" "$@" 216 | # 217 | # but POSIX shell has neither arrays nor command substitution, so instead we 218 | # post-process each arg (as a line of input to sed) to backslash-escape any 219 | # character that might be a shell metacharacter, then use eval to reverse 220 | # that process (while maintaining the separation between arguments), and wrap 221 | # the whole thing up as a single "set" statement. 222 | # 223 | # This will of course break if any of these variables contains a newline or 224 | # an unmatched quote. 225 | # 226 | 227 | eval "set -- $( 228 | printf '%s\n' "$DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS" | 229 | xargs -n1 | 230 | sed ' s~[^-[:alnum:]+,./:=@_]~\\&~g; ' | 231 | tr '\n' ' ' 232 | )" '"$@"' 233 | 234 | exec "$JAVACMD" "$@" 235 | -------------------------------------------------------------------------------- /gradlew.bat: -------------------------------------------------------------------------------- 1 | @rem 2 | @rem Copyright 2015 the original author or authors. 3 | @rem 4 | @rem Licensed under the Apache License, Version 2.0 (the "License"); 5 | @rem you may not use this file except in compliance with the License. 6 | @rem You may obtain a copy of the License at 7 | @rem 8 | @rem https://www.apache.org/licenses/LICENSE-2.0 9 | @rem 10 | @rem Unless required by applicable law or agreed to in writing, software 11 | @rem distributed under the License is distributed on an "AS IS" BASIS, 12 | @rem WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | @rem See the License for the specific language governing permissions and 14 | @rem limitations under the License. 15 | @rem 16 | 17 | @if "%DEBUG%" == "" @echo off 18 | @rem ########################################################################## 19 | @rem 20 | @rem Gradle startup script for Windows 21 | @rem 22 | @rem ########################################################################## 23 | 24 | @rem Set local scope for the variables with windows NT shell 25 | if "%OS%"=="Windows_NT" setlocal 26 | 27 | set DIRNAME=%~dp0 28 | if "%DIRNAME%" == "" set DIRNAME=. 29 | set APP_BASE_NAME=%~n0 30 | set APP_HOME=%DIRNAME% 31 | 32 | @rem Resolve any "." and ".." in APP_HOME to make it shorter. 33 | for %%i in ("%APP_HOME%") do set APP_HOME=%%~fi 34 | 35 | @rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. 36 | set DEFAULT_JVM_OPTS="-Xmx64m" "-Xms64m" 37 | 38 | @rem Find java.exe 39 | if defined JAVA_HOME goto findJavaFromJavaHome 40 | 41 | set JAVA_EXE=java.exe 42 | %JAVA_EXE% -version >NUL 2>&1 43 | if "%ERRORLEVEL%" == "0" goto execute 44 | 45 | echo. 46 | echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. 47 | echo. 48 | echo Please set the JAVA_HOME variable in your environment to match the 49 | echo location of your Java installation. 50 | 51 | goto fail 52 | 53 | :findJavaFromJavaHome 54 | set JAVA_HOME=%JAVA_HOME:"=% 55 | set JAVA_EXE=%JAVA_HOME%/bin/java.exe 56 | 57 | if exist "%JAVA_EXE%" goto execute 58 | 59 | echo. 60 | echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME% 61 | echo. 62 | echo Please set the JAVA_HOME variable in your environment to match the 63 | echo location of your Java installation. 64 | 65 | goto fail 66 | 67 | :execute 68 | @rem Setup the command line 69 | 70 | set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar 71 | 72 | 73 | @rem Execute Gradle 74 | "%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %* 75 | 76 | :end 77 | @rem End local scope for the variables with windows NT shell 78 | if "%ERRORLEVEL%"=="0" goto mainEnd 79 | 80 | :fail 81 | rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of 82 | rem the _cmd.exe /c_ return code! 83 | if not "" == "%GRADLE_EXIT_CONSOLE%" exit 1 84 | exit /b 1 85 | 86 | :mainEnd 87 | if "%OS%"=="Windows_NT" endlocal 88 | 89 | :omega 90 | -------------------------------------------------------------------------------- /scripts/install.sh: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | 3 | VERSION=1.3.3 4 | 5 | while getopts v:h: flag 6 | do 7 | case "${flag}" in 8 | v) VERSION=${OPTARG};; 9 | esac 10 | done 11 | 12 | sudo -v && sudo="true" || sudo="" 13 | 14 | if ! [[ -x "$(command -v danger)" ]]; then 15 | if ! [[ -x "$(command -v npm)" ]]; then 16 | echo "Please install node js" 17 | exit 1 18 | fi 19 | 20 | echo "Installing danger" 21 | 22 | if [[ -n "$sudo" ]]; then 23 | sudo npm install -g danger 24 | else 25 | npm install -g danger 26 | fi 27 | fi 28 | 29 | if [[ -n "$sudo" && "$OSTYPE" != "darwin"* ]]; then 30 | sudo chmod -R a+rwx /usr/local/ 31 | fi 32 | 33 | if ! [[ -x "$(command -v kotlinc)" ]]; then 34 | echo "Installing kotlin compiler 2.0.21" 35 | curl -o kotlin-compiler.zip -L https://github.com/JetBrains/kotlin/releases/download/v2.0.21/kotlin-compiler-2.0.21.zip 36 | unzip -d /usr/local/ kotlin-compiler.zip 37 | echo 'export PATH=/usr/local/kotlinc/bin:$PATH' >> ~/.bash_profile 38 | rm -rf kotlin-compiler.zip 39 | fi 40 | 41 | if ! [[ -x "$(command -v gradle)" ]]; then 42 | echo "Installing gradle 8.10.2" 43 | curl -o gradle.zip -L https://downloads.gradle-dn.com/distributions/gradle-8.10.2-bin.zip 44 | mkdir /opt/gradle 45 | unzip -d /opt/gradle gradle.zip 46 | echo 'export PATH=/opt/gradle/gradle-8.10.2/bin:$PATH' >> ~/.bash_profile 47 | rm -rf gradle.zip 48 | fi 49 | 50 | git clone https://github.com/danger/kotlin.git --branch $VERSION --depth 1 _danger-kotlin 51 | cd _danger-kotlin && make install 52 | cd .. 53 | rm -rf _danger-kotlin 54 | -------------------------------------------------------------------------------- /scripts/release_changelog.sh: -------------------------------------------------------------------------------- 1 | TEXT=`cat CHANGELOG.md| sed -n "/##\ $VERSION/,/##/p"` 2 | 3 | TEXT=`echo "$TEXT" | sed '1d;$d' | sed 's/\[\]//g'` 4 | 5 | echo "gh release create $VERSION -n \"## What's Changed\r\n$TEXT\"" 6 | 7 | gh release create $VERSION -n "$TEXT" 8 | -------------------------------------------------------------------------------- /secrets.gradle: -------------------------------------------------------------------------------- 1 | ext.loadSecret = { key -> 2 | try { 3 | def secret = System.getenv(key) ?: loadLocalSecret(key) 4 | } catch (FileNotFoundException ignored) { 5 | System.out.println("Secret key $key not found. Falling back to default") 6 | def secret = "" 7 | } 8 | } 9 | 10 | def String loadLocalSecret(key) { 11 | def localSecrets = new Properties() 12 | file("secrets.properties").withInputStream { localSecrets.load(it) } 13 | return localSecrets.getProperty(key) 14 | } 15 | -------------------------------------------------------------------------------- /settings.gradle: -------------------------------------------------------------------------------- 1 | pluginManagement { 2 | repositories { 3 | gradlePluginPortal() 4 | mavenCentral() 5 | mavenLocal() 6 | } 7 | } 8 | 9 | include ':danger-kotlin' 10 | include ':danger-kotlin-library' 11 | include ':danger-kotlin-sdk' 12 | include ':danger-kotlin-kts' 13 | include ':danger-plugin-installer' -------------------------------------------------------------------------------- /shadow.gradle: -------------------------------------------------------------------------------- 1 | apply from: file('../configurations.gradle') 2 | shadowJar { 3 | configurations = [ 4 | project.configurations.includeJar, 5 | project.configurations.includeRecursiveJar 6 | ] 7 | } 8 | --------------------------------------------------------------------------------